classify admin
This commit is contained in:
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[docker-compose.yml]
|
||||||
|
indent_size = 4
|
||||||
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/vendor/
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# Laravel 4 specific
|
||||||
|
bootstrap/compiled.php
|
||||||
|
app/storage/
|
||||||
|
|
||||||
|
# Laravel 5 & Lumen specific
|
||||||
|
public/storage
|
||||||
|
public/hot
|
||||||
|
|
||||||
|
# Laravel 5 & Lumen specific with changed public path
|
||||||
|
public_html/storage
|
||||||
|
public_html/hot
|
||||||
|
|
||||||
|
storage/*.key
|
||||||
|
.env
|
||||||
|
Homestead.yaml
|
||||||
|
Homestead.json
|
||||||
|
/.vagrant
|
||||||
|
.phpunit.result.cache
|
||||||
|
/.idea/*
|
||||||
|
/storage/installed
|
||||||
|
|
||||||
|
#/.phpstorm.meta.php
|
||||||
|
#/_ide_helper.php
|
||||||
|
#/_ide_helper_models.php
|
||||||
|
.vscode
|
||||||
|
/qodana.yaml
|
||||||
|
/public/firebase-messaging-sw.js
|
||||||
|
/app/Swagger/*.php
|
||||||
28
.htaccess
Normal file
28
.htaccess
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
<IfModule mod_negotiation.c>
|
||||||
|
Options -MultiViews -Indexes
|
||||||
|
</IfModule>
|
||||||
|
#ErrorDocument 200 "Hello. This is your .htaccess file talking."
|
||||||
|
#RewriteRule ^ - [L,R=200]
|
||||||
|
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteRule ^(.*)$ public/$1 [L]
|
||||||
|
# Handle Authorization Header
|
||||||
|
RewriteCond %{HTTP:Authorization} .
|
||||||
|
|
||||||
|
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||||
|
|
||||||
|
# Redirect Trailing Slashes If Not A Folder...
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_URI} (.+)/$
|
||||||
|
RewriteRule ^ %1 [L,R=301]
|
||||||
|
|
||||||
|
# Send Requests To Front Controller...
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteRule ^ index.php [L]
|
||||||
|
|
||||||
|
Header always set Access-Control-Allow-Origin *
|
||||||
|
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
13
.well-known/apple-app-site-association
Normal file
13
.well-known/apple-app-site-association
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"applinks": {
|
||||||
|
"apps": [],
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"appID": "89C47N4UTZ.com.eclassify.wrteam",
|
||||||
|
"paths": [
|
||||||
|
"*","/??","/items-details"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
26
.well-known/assetlinks.json
Normal file
26
.well-known/assetlinks.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"relation": [
|
||||||
|
"delegate_permission/common.handle_all_urls"
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.eclassify.wrteam",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"B4:B3:AD:26:FA:94:07:B0:EA:CC:30:7E:65:B3:AB:14:B9:98:BB:AB:2F:2C:FF:81:73:9A:6C:22:DF:7C:8C:42"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"relation": [
|
||||||
|
"delegate_permission/common.handle_all_urls"
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"namespace": "android_app",
|
||||||
|
"package_name": "com.eclassify.wrteam",
|
||||||
|
"sha256_cert_fingerprints": [
|
||||||
|
"C8:7B:75:BD:A2:AF:6B:E2:93:50:AA:20:43:83:68:DE:AE:87:4B:1C:7A:B3:17:9E:CA:53:F9:BC:A7:9B:F2:39"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
51
app/Console/Commands/CustomAutoTranslate.php
Normal file
51
app/Console/Commands/CustomAutoTranslate.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Devaslanphp\AutoTranslate\Commands\AutoTranslate;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Stichoza\GoogleTranslate\GoogleTranslate;
|
||||||
|
|
||||||
|
class CustomAutoTranslate extends AutoTranslate
|
||||||
|
{
|
||||||
|
protected $signature = 'custom:auto-translate';
|
||||||
|
|
||||||
|
protected $description = 'This command will search everywhere in your code for translations and generate JSON files for you.';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$locales = config('auto-translate.locales');
|
||||||
|
$files = ['en.json', 'en_web.json', 'en_app.json']; // Add your JSON files here
|
||||||
|
|
||||||
|
foreach ($locales as $locale) {
|
||||||
|
foreach ($files as $file) {
|
||||||
|
try {
|
||||||
|
Artisan::call('translatable:export ' . $locale);
|
||||||
|
|
||||||
|
// Adjust the file path to handle different file names
|
||||||
|
$filePath = lang_path(str_replace('en', $locale, $file));
|
||||||
|
|
||||||
|
if (File::exists($filePath)) {
|
||||||
|
$this->info('Translating ' . $locale . ' for ' . $file . ', please wait...');
|
||||||
|
$results = [];
|
||||||
|
$localeFile = File::get($filePath);
|
||||||
|
$localeFileContent = array_keys(json_decode($localeFile, true));
|
||||||
|
$translator = new GoogleTranslate($locale);
|
||||||
|
$translator->setSource(config('app.fallback_locale'));
|
||||||
|
|
||||||
|
foreach ($localeFileContent as $key) {
|
||||||
|
$results[$key] = $translator->translate($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
File::put($filePath, json_encode($results, JSON_UNESCAPED_UNICODE));
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Error: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
app/Console/Commands/CustomTranslateMissing.php
Normal file
88
app/Console/Commands/CustomTranslateMissing.php
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Stichoza\GoogleTranslate\GoogleTranslate;
|
||||||
|
|
||||||
|
class CustomTranslateMissing extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'custom:translate-missing {type} {locale}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Translate missing keys in a specific JSON file based on the provided type and locale.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$type = $this->argument('type');
|
||||||
|
$locale = $this->argument('locale');
|
||||||
|
|
||||||
|
|
||||||
|
$base = config('auto-translate.base_locale', 'en');
|
||||||
|
|
||||||
|
|
||||||
|
$fileName = match ($type) {
|
||||||
|
'web' => 'en_web.json',
|
||||||
|
'panel' => 'en.json',
|
||||||
|
'app' => 'en_app.json',
|
||||||
|
default => $this->error('Invalid type specified.') && exit(Command::FAILURE),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$baseFilePath = lang_path($fileName);
|
||||||
|
$localeFilePath = match ($type) {
|
||||||
|
'web' => lang_path($locale . '_web.json'),
|
||||||
|
'panel' => lang_path($locale . '.json'),
|
||||||
|
'app' => lang_path($locale . '_app.json'),
|
||||||
|
default => $this->error('Invalid type specified.') && exit(Command::FAILURE),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (!File::exists($baseFilePath)) {
|
||||||
|
$this->error("Base file '{$baseFilePath}' not found.");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$baseTranslations = json_decode(File::get($baseFilePath), true);
|
||||||
|
$localeTranslations = File::exists($localeFilePath) ? json_decode(File::get($localeFilePath), true) : [];
|
||||||
|
|
||||||
|
|
||||||
|
$translator = new GoogleTranslate();
|
||||||
|
$translator->setSource($base);
|
||||||
|
$translator->setTarget($locale);
|
||||||
|
$newLocaleTranslations = [];
|
||||||
|
foreach ($baseTranslations as $key => $baseTranslation) {
|
||||||
|
try {
|
||||||
|
$translatedText = $translator->translate($baseTranslation);
|
||||||
|
$newLocaleTranslations[$key] = $translatedText;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Error: ' . $e->getMessage());
|
||||||
|
$newLocaleTranslations[$key] = $baseTranslation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
File::put($localeFilePath, json_encode($newLocaleTranslations, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
$this->info("Translation for type '{$type}' and locale '{$locale}' completed successfully.");
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
32
app/Console/Commands/NotifyExpiringItems.php
Normal file
32
app/Console/Commands/NotifyExpiringItems.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Services\ExpiringItemService;
|
||||||
|
|
||||||
|
class NotifyExpiringItems extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'notify:expiring-items';
|
||||||
|
protected $description = 'Send notifications for items expiring in 2 days.';
|
||||||
|
|
||||||
|
protected $expiringItemService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject the ExpiringItemService
|
||||||
|
*/
|
||||||
|
public function __construct(ExpiringItemService $expiringItemService)
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
$this->expiringItemService = $expiringItemService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->expiringItemService->notifyExpiringItems();
|
||||||
|
$this->info('Expiring Advertisement notifications sent successfully.');
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Console/Commands/NotifyExpiringPackages.php
Normal file
23
app/Console/Commands/NotifyExpiringPackages.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Services\ExpiringItemService;
|
||||||
|
|
||||||
|
class NotifyExpiringPackages extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'notify:expiring-packages';
|
||||||
|
protected $description = 'Send notifications for expiring packages.';
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ExpiringItemService $expiringItemService)
|
||||||
|
{
|
||||||
|
$expiringItemService->notifyExpiringPackages();
|
||||||
|
$this->info('Expiring packages notifications sent successfully.');
|
||||||
|
}
|
||||||
|
}
|
||||||
51
app/Console/Commands/ProcessQueue.php
Normal file
51
app/Console/Commands/ProcessQueue.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class ProcessQueue extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'queue:process
|
||||||
|
{--tries=3 : Number of times to attempt a job}
|
||||||
|
{--timeout=300 : The number of seconds a child process can run}
|
||||||
|
{--max-jobs=50 : Number of jobs to process before stopping}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Process queue jobs with stop-when-empty';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
// IMPORTANT:
|
||||||
|
// Do not rely on the `jobs` table count here.
|
||||||
|
// The default queue connection may be `redis`/`sqs`/etc, where there is no `jobs` table.
|
||||||
|
// `queue:work --stop-when-empty` will exit automatically if there are no jobs.
|
||||||
|
$connection = config('queue.default');
|
||||||
|
$this->info("Processing queue connection [{$connection}]...");
|
||||||
|
|
||||||
|
$this->call('queue:work', [
|
||||||
|
'connection' => $connection,
|
||||||
|
'--stop-when-empty' => true,
|
||||||
|
'--tries' => $this->option('tries'),
|
||||||
|
'--timeout' => $this->option('timeout'),
|
||||||
|
'--max-jobs' => $this->option('max-jobs'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
73
app/Console/Commands/SendFcmBatch.php
Normal file
73
app/Console/Commands/SendFcmBatch.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
|
||||||
|
class SendFcmBatch extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'send:fcm-batch {data}';
|
||||||
|
protected $description = 'Send FCM notifications in batch from background';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$data = json_decode($this->argument('data'), true);
|
||||||
|
|
||||||
|
$title = $data['title'] ?? '';
|
||||||
|
$message = $data['message'] ?? '';
|
||||||
|
$type = $data['type'] ?? 'notification';
|
||||||
|
$customBodyFields = $data['customBodyFields'] ?? [];
|
||||||
|
$sendToAll = $data['sendToAll'] ?? false;
|
||||||
|
$userIds = $data['userIds'] ?? [];
|
||||||
|
|
||||||
|
$this->info("🔔 Sending FCM notifications...");
|
||||||
|
|
||||||
|
// ✅ If sendToAll = true
|
||||||
|
if ($sendToAll) {
|
||||||
|
// Fetch tokens with user preference
|
||||||
|
$tokens = UserFcmToken::with('user')
|
||||||
|
->whereHas('user', fn($q) => $q->where('notification', 1))
|
||||||
|
->get(['fcm_token', 'platform_type']);
|
||||||
|
|
||||||
|
// Split tokens by platform
|
||||||
|
$androidIosTokens = $tokens->whereIn('platform_type', ['Android', 'iOS'])->pluck('fcm_token')->toArray();
|
||||||
|
$otherTokens = $tokens->whereNotIn('platform_type', ['Android', 'iOS'])->pluck('fcm_token')->toArray();
|
||||||
|
|
||||||
|
// ✅ Send Android/iOS via Topic
|
||||||
|
if (!empty($androidIosTokens)) {
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
[], $title, $message, $type, $customBodyFields, true
|
||||||
|
);
|
||||||
|
$this->info("📱 Topic-based notification sent to Android/iOS users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Send Others via Chunk (if any)
|
||||||
|
if (!empty($otherTokens)) {
|
||||||
|
collect($otherTokens)->chunk(500)->each(function ($chunk) use ($title, $message, $type, $customBodyFields) {
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$chunk->toArray(), $title, $message, $type, $customBodyFields, false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
$this->info("💻 Chunk-based notification sent to other platform users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ✅ Send to specific selected users
|
||||||
|
UserFcmToken::with('user')
|
||||||
|
->whereIn('user_id', $userIds)
|
||||||
|
->whereHas('user', fn($q) => $q->where('notification', 1))
|
||||||
|
->chunk(500, function ($tokens) use ($title, $message, $type, $customBodyFields) {
|
||||||
|
$fcmTokens = $tokens->pluck('fcm_token')->toArray();
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$fcmTokens, $title, $message, $type, $customBodyFields, false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->info("👥 Notifications sent to selected users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("✅ FCM notifications process completed successfully!");
|
||||||
|
}
|
||||||
|
}
|
||||||
45
app/Console/Kernel.php
Normal file
45
app/Console/Kernel.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console;
|
||||||
|
|
||||||
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
|
class Kernel extends ConsoleKernel
|
||||||
|
{
|
||||||
|
protected $commands = [
|
||||||
|
\App\Console\Commands\CustomAutoTranslate::class,
|
||||||
|
\App\Console\Commands\CustomTranslateMissing::class,
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* Define the application's command schedule.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function schedule(Schedule $schedule)
|
||||||
|
{
|
||||||
|
$schedule->command('notify:expiring-items')->daily();
|
||||||
|
$schedule->command('notify:expiring-packages')->daily();
|
||||||
|
|
||||||
|
// Process queue jobs every minute
|
||||||
|
// Using custom command that wraps queue:work for better reliability
|
||||||
|
// $schedule->command('inspire')->hourly();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the commands for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function commands()
|
||||||
|
{
|
||||||
|
$this->load(__DIR__.'/Commands');
|
||||||
|
|
||||||
|
require base_path('routes/console.php');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
50
app/Exceptions/Handler.php
Normal file
50
app/Exceptions/Handler.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class Handler extends ExceptionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A list of exception types with their corresponding custom log levels.
|
||||||
|
*
|
||||||
|
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||||
|
*/
|
||||||
|
protected $levels = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the exception types that are not reported.
|
||||||
|
*
|
||||||
|
* @var array<int, class-string<\Throwable>>
|
||||||
|
*/
|
||||||
|
protected $dontReport = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $dontFlash = [
|
||||||
|
'current_password',
|
||||||
|
'password',
|
||||||
|
'password_confirmation',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the exception handling callbacks for the application.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->reportable(function (Throwable $e) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Helpers/settings_helper.php
Normal file
28
app/Helpers/settings_helper.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\LanguageController;
|
||||||
|
use App\Models\Language;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use App\Models\Setting;
|
||||||
|
|
||||||
|
function current_language()
|
||||||
|
{
|
||||||
|
// if (Session::get('language') == 'en' || Session::get('language') == 'fr') {
|
||||||
|
// $lang = Session::get('language');
|
||||||
|
// Session::put('language', $lang);
|
||||||
|
// Session::put('locale', $lang);
|
||||||
|
// app()->setLocale(Session::get('locale'));
|
||||||
|
// } else {
|
||||||
|
// $lang = 'en';
|
||||||
|
// Session::put('language', $lang);
|
||||||
|
// Session::put('locale', $lang);
|
||||||
|
// app()->setLocale(Session::get('locale'));
|
||||||
|
// }
|
||||||
|
$lang = Session::get('locale');
|
||||||
|
app()->setLocale($lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_language()
|
||||||
|
{
|
||||||
|
return Language::get();
|
||||||
|
}
|
||||||
4670
app/Http/Controllers/ApiController.php
Normal file
4670
app/Http/Controllers/ApiController.php
Normal file
File diff suppressed because it is too large
Load Diff
40
app/Http/Controllers/Auth/ConfirmPasswordController.php
Normal file
40
app/Http/Controllers/Auth/ConfirmPasswordController.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Foundation\Auth\ConfirmsPasswords;
|
||||||
|
|
||||||
|
class ConfirmPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Confirm Password Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling password confirmations and
|
||||||
|
| uses a simple trait to include the behavior. You're free to explore
|
||||||
|
| this trait and override any functions that require customization.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use ConfirmsPasswords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users when the intended url fails.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = RouteServiceProvider::HOME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
22
app/Http/Controllers/Auth/ForgotPasswordController.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
|
||||||
|
|
||||||
|
class ForgotPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Reset Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling password reset emails and
|
||||||
|
| includes a trait which assists in sending these notifications from
|
||||||
|
| your application to your users. Feel free to explore this trait.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use SendsPasswordResetEmails;
|
||||||
|
}
|
||||||
76
app/Http/Controllers/Auth/LoginController.php
Normal file
76
app/Http/Controllers/Auth/LoginController.php
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Language;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
use Ramsey\Collection\Set;
|
||||||
|
|
||||||
|
class LoginController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Login Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller handles authenticating users for the application and
|
||||||
|
| redirecting them to your home screen. The controller uses a trait
|
||||||
|
| to conveniently provide its functionality to your applications.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after login.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = RouteServiceProvider::HOME;
|
||||||
|
|
||||||
|
/* Extended Function from AuthenticatesUsers */
|
||||||
|
protected function sendFailedLoginResponse(Request $request)
|
||||||
|
{
|
||||||
|
$user = User::where('email', $request->get('email'))->withTrashed()->first();
|
||||||
|
if (! empty($user->deleted_at)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
$this->username() => [trans('auth.user_inactive')],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
$this->username() => [trans('auth.failed')],
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set application locale after successful authentication.
|
||||||
|
*/
|
||||||
|
protected function authenticated(Request $request, $user)
|
||||||
|
{
|
||||||
|
// Prefer app default language stored in settings so admin sees default on login
|
||||||
|
// $defaultLanguage = \DB::table('settings')->where('name', 'default_language')->value('value');
|
||||||
|
$defaultLanguage = Setting::where('name', 'default_language')->value('value');
|
||||||
|
if ($defaultLanguage) {
|
||||||
|
// $language = \App\Models\Language::where('code', $defaultLanguage)->first();
|
||||||
|
$language = Language::where('code', $defaultLanguage)->first();
|
||||||
|
if ($language) {
|
||||||
|
\Session::put('locale', $language->code);
|
||||||
|
\Session::put('language', (object) $language->toArray());
|
||||||
|
app()->setLocale($language->code);
|
||||||
|
\Session::save();
|
||||||
|
} else {
|
||||||
|
\Session::put('locale', $defaultLanguage);
|
||||||
|
app()->setLocale($defaultLanguage);
|
||||||
|
\Session::save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
app/Http/Controllers/Auth/RegisterController.php
Normal file
69
app/Http/Controllers/Auth/RegisterController.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class RegisterController extends Controller {
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Register Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller handles the registration of new users as well as their
|
||||||
|
| validation and creation. By default this controller uses a trait to
|
||||||
|
| provide this functionality without requiring any additional code.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use RegistersUsers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after registration.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = RouteServiceProvider::HOME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
$this->middleware('guest');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a validator for an incoming registration request.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return \Illuminate\Contracts\Validation\Validator
|
||||||
|
*/
|
||||||
|
protected function validator(array $data) {
|
||||||
|
return Validator::make($data, [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||||
|
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new user instance after a valid registration.
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return User
|
||||||
|
*/
|
||||||
|
protected function create(array $data) {
|
||||||
|
return User::create([
|
||||||
|
'name' => $data['name'],
|
||||||
|
'email' => $data['email'],
|
||||||
|
'password' => Hash::make($data['password']),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Http/Controllers/Auth/ResetPasswordController.php
Normal file
30
app/Http/Controllers/Auth/ResetPasswordController.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||||
|
|
||||||
|
class ResetPasswordController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Password Reset Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling password reset requests
|
||||||
|
| and uses a simple trait to include this behavior. You're free to
|
||||||
|
| explore this trait and override any methods you wish to tweak.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use ResetsPasswords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after resetting their password.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = RouteServiceProvider::HOME;
|
||||||
|
}
|
||||||
42
app/Http/Controllers/Auth/VerificationController.php
Normal file
42
app/Http/Controllers/Auth/VerificationController.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Illuminate\Foundation\Auth\VerifiesEmails;
|
||||||
|
|
||||||
|
class VerificationController extends Controller
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Email Verification Controller
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This controller is responsible for handling email verification for any
|
||||||
|
| user that recently registered with the application. Emails may also
|
||||||
|
| be re-sent if the user didn't receive the original email message.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
use VerifiesEmails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where to redirect users after verification.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $redirectTo = RouteServiceProvider::HOME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('auth');
|
||||||
|
$this->middleware('signed')->only('verify');
|
||||||
|
$this->middleware('throttle:6,1')->only('verify', 'resend');
|
||||||
|
}
|
||||||
|
}
|
||||||
252
app/Http/Controllers/BlogController.php
Normal file
252
app/Http/Controllers/BlogController.php
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Blog;
|
||||||
|
use App\Models\BlogTranslation;
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\HelperService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use App\Jobs\SendFcmBatchJob;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Throwable;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
|
use function compact;
|
||||||
|
use function view;
|
||||||
|
|
||||||
|
class BlogController extends Controller {
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->uploadFolder = "blog";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['blog-list', 'blog-create', 'blog-delete', 'blog-update']);
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('blog.index',compact('languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create() {
|
||||||
|
ResponseService::noPermissionThenRedirect('blog-create');
|
||||||
|
$categories = Category::all();
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('blog.create', compact('categories','languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('blog-create');
|
||||||
|
$request->validate([
|
||||||
|
'title.1' => 'required',
|
||||||
|
'slug' => 'required',
|
||||||
|
'image' => 'required|mimes:jpg,jpeg,png|max:7168',
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
$data = [
|
||||||
|
'title' => $request->input('title')[1],
|
||||||
|
'slug' => HelperService::generateUniqueSlug(new Blog(), $request->input('slug')),
|
||||||
|
'description' => $request->input('blog_description')[1] ?? '',
|
||||||
|
'tags' => implode(',', $request->input('tags')[1] ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::compressAndUpload($request->file('image'), $this->uploadFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
$blog = Blog::create($data);
|
||||||
|
foreach ($request->input('languages', []) as $langId) {
|
||||||
|
if ($langId != 1) {
|
||||||
|
$translatedTitle = $request->input("title.$langId");
|
||||||
|
$translatedDesc = $request->input("blog_description.$langId");
|
||||||
|
$translatedTags = implode(',', $request->input("tags.$langId", []));
|
||||||
|
if ($translatedTitle || $translatedDesc || !empty($translatedTags)) {
|
||||||
|
BlogTranslation::create([
|
||||||
|
'blog_id' => $blog->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'title' => $translatedTitle,
|
||||||
|
'description' => $translatedDesc,
|
||||||
|
'tags' => $translatedTags,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$customBodyFields = [
|
||||||
|
'image' => $blog->image,
|
||||||
|
'blog_id' => $blog->id,
|
||||||
|
'type' => 'blog'
|
||||||
|
];
|
||||||
|
|
||||||
|
redirect(route('blog.index'))->with([
|
||||||
|
'success' => trans("Blog Added Successfully")
|
||||||
|
])->send();
|
||||||
|
// ResponseService::successRedirectResponse("Blog Added Successfully", route('blog.index'))->send();
|
||||||
|
if (ob_get_level() > 0) {
|
||||||
|
ob_end_flush();
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
if (function_exists('fastcgi_finish_request')) {
|
||||||
|
fastcgi_finish_request();
|
||||||
|
(new SendFcmBatchJob(
|
||||||
|
$blog->title,
|
||||||
|
"New blog uploaded by admin. Check it out!",
|
||||||
|
'blog',
|
||||||
|
$customBodyFields,
|
||||||
|
true,
|
||||||
|
[]
|
||||||
|
))->handle();
|
||||||
|
} else {
|
||||||
|
register_shutdown_function(function () use ($blog, $customBodyFields) {
|
||||||
|
try {
|
||||||
|
(new SendFcmBatchJob(
|
||||||
|
$blog->title,
|
||||||
|
"New blog uploaded by admin. Check it out!",
|
||||||
|
'blog',
|
||||||
|
$customBodyFields,
|
||||||
|
true,
|
||||||
|
[]
|
||||||
|
))->handle();
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error('Background notification job failed: ' . $th->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th, "BlogController->store");
|
||||||
|
ResponseService::errorRedirectResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function show(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('blog-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
|
||||||
|
|
||||||
|
$sql = Blog::with('category:id,name');
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql = $sql->sort($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
$no = 1;
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('blog-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('blog.edit', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('blog-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('blog.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['no'] = $no++;
|
||||||
|
$tempRow['created_at'] = Carbon::createFromFormat('Y-m-d H:i:s', $row->created_at)->format('d-m-y H:i:s');
|
||||||
|
$tempRow['updated_at'] = Carbon::createFromFormat('Y-m-d H:i:s', $row->updated_at)->format('d-m-y H:i:s');
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$tempRow['description'] = Str::limit(strip_tags($row->description), 200);
|
||||||
|
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('blog-update');
|
||||||
|
|
||||||
|
$blog = Blog::with('translations')->findOrFail($id);
|
||||||
|
$categories = Category::all();
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
|
||||||
|
// Get translations as keyed array by language_id
|
||||||
|
$translations = $blog->translations->keyBy('language_id');
|
||||||
|
return view('blog.edit', compact('blog', 'categories', 'languages', 'translations'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('blog-update');
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'title.1' => 'required',
|
||||||
|
'slug' => 'required',
|
||||||
|
'image' => 'nullable|mimes:jpg,jpeg,png|max:7168',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$blog = Blog::findOrFail($id);
|
||||||
|
$data = [
|
||||||
|
'title' => $request->input('title')[1],
|
||||||
|
'slug' => HelperService::generateUniqueSlug(new Blog(), $request->input('slug'), $blog->id),
|
||||||
|
'description' => $request->input('blog_description')[1] ?? '',
|
||||||
|
'tags' => implode(',', $request->input('tags')[1] ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::compressAndReplace($request->file('image'), $this->uploadFolder, $blog->getRawOriginal('image'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$blog->update($data);
|
||||||
|
|
||||||
|
foreach ($request->input('languages', []) as $langId) {
|
||||||
|
if ($langId != 1) {
|
||||||
|
$translatedTitle = $request->input("title.$langId");
|
||||||
|
$translatedDesc = $request->input("blog_description.$langId");
|
||||||
|
$translatedTags = $request->input("tags.$langId", []);
|
||||||
|
|
||||||
|
if ($translatedTitle || $translatedDesc || !empty($translatedTags)) {
|
||||||
|
BlogTranslation::updateOrCreate(
|
||||||
|
['blog_id' => $blog->id, 'language_id' => $langId],
|
||||||
|
[
|
||||||
|
'title' => $translatedTitle,
|
||||||
|
'description' => $translatedDesc,
|
||||||
|
'tags' => implode(',', $translatedTags),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successRedirectResponse("Blog Updated Successfully", route('blog.index'));
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th);
|
||||||
|
ResponseService::errorRedirectResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function destroy($id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('blog-delete');
|
||||||
|
try {
|
||||||
|
$blog = Blog::find($id);
|
||||||
|
FileService::delete($blog->getRawOriginal('image'));
|
||||||
|
$blog->delete();
|
||||||
|
ResponseService::successResponse('Blog delete successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse('Something Went Wrong ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
477
app/Http/Controllers/CategoryController.php
Normal file
477
app/Http/Controllers/CategoryController.php
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\CategoryTranslation;
|
||||||
|
use App\Models\CustomField;
|
||||||
|
use App\Models\CustomFieldCategory;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\HelperService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Database\QueryException;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Throwable;
|
||||||
|
use function compact;
|
||||||
|
use function view;
|
||||||
|
|
||||||
|
class CategoryController extends Controller {
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->uploadFolder = "category";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['category-list', 'category-create', 'category-update', 'category-delete']);
|
||||||
|
return view('category.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request) {
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
ResponseService::noPermissionThenRedirect('category-create');
|
||||||
|
$categories = Category::with('subcategories')->get();
|
||||||
|
return view('category.create', compact('categories', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('category-create');
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"name.$defaultLangId" => 'required|string|max:30',
|
||||||
|
'image' => 'required|mimes:jpg,jpeg,png|max:7168',
|
||||||
|
'parent_category_id' => 'nullable|integer',
|
||||||
|
"description.$defaultLangId" => 'nullable|string',
|
||||||
|
'slug' => [
|
||||||
|
'nullable',
|
||||||
|
'regex:/^[a-zA-Z0-9\-_]+$/'
|
||||||
|
],
|
||||||
|
'status' => 'required|boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["name.$langId"] = 'nullable|string|max:30';
|
||||||
|
$rules["description.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate($rules, [
|
||||||
|
'slug.regex' => 'Slug must be only English letters, numbers, hyphens (-), or underscores (_).'
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = [
|
||||||
|
'name' => $request->input("name.$defaultLangId"),
|
||||||
|
'description' => $request->input("description.$defaultLangId"),
|
||||||
|
'parent_category_id' => $request->parent_category_id,
|
||||||
|
'status' => $request->status,
|
||||||
|
'is_job_category' => $request->is_job_category ?? 0,
|
||||||
|
'price_optional' => $request->price_optional ?? 0,
|
||||||
|
];
|
||||||
|
$slug = trim($request->input('slug') ?? '');
|
||||||
|
$slug = preg_replace('/[^a-z0-9]+/i', '-', strtolower($slug));
|
||||||
|
$slug = trim($slug, '-');
|
||||||
|
if (empty($slug)) {
|
||||||
|
$slug = HelperService::generateRandomSlug();
|
||||||
|
}
|
||||||
|
$data['slug'] = HelperService::generateUniqueSlug(new Category, $slug);
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::compressAndUpload($request->file('image'), $this->uploadFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
$category = Category::create($data);
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedName = $request->input("name.$langId");
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
|
||||||
|
if (!empty($translatedName) || !empty($translatedDescription)) {
|
||||||
|
$category->translations()->create([
|
||||||
|
'name' => $translatedName ?? '',
|
||||||
|
'description' => $translatedDescription ?? null,
|
||||||
|
'language_id' => $langId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successRedirectResponse("Category Added Successfully");
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th);
|
||||||
|
ResponseService::errorRedirectResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function show(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('category-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'sequence');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
$sql = Category::without('translations')->withCount('subcategories')->withCount('custom_fields')->with('subcategories');
|
||||||
|
if ($id == "0") {
|
||||||
|
$sql->whereNull('parent_category_id');
|
||||||
|
} else {
|
||||||
|
$sql->where('parent_category_id', $id);
|
||||||
|
}
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
if ($sort !== 'advertisements_count') {
|
||||||
|
$sql->orderBy($sort, $order);
|
||||||
|
}
|
||||||
|
$result = $sql->get();
|
||||||
|
|
||||||
|
|
||||||
|
if ($sort === 'advertisements_count') {
|
||||||
|
$result = $result->sortBy(function ($category) {
|
||||||
|
return $category->all_items_count;
|
||||||
|
}, SORT_REGULAR, strtolower($order) === 'desc')->values();
|
||||||
|
|
||||||
|
$result = $result->slice($offset, $limit)->values();
|
||||||
|
} else {
|
||||||
|
$result = $result->slice($offset, $limit);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
$no = 1;
|
||||||
|
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('category-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('category.edit', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('category-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('category.destroy', $row->id));
|
||||||
|
}
|
||||||
|
if ($row->subcategories_count > 1) {
|
||||||
|
$operate .= BootstrapTableService::button('fa fa-list-ol',route('sub.category.order.change', $row->id),['btn-secondary']);
|
||||||
|
}
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['no'] = $no++;
|
||||||
|
$tempRow['subcategories_count'] = $row->subcategories_count . ' ' . __('Subcategories');
|
||||||
|
$tempRow['custom_fields_count'] = $row->custom_fields_count . ' ' . __('Custom Fields');
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$tempRow['advertisements_count'] = $row->all_items_count;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('category-update');
|
||||||
|
$category_data = Category::findOrFail($id);
|
||||||
|
|
||||||
|
// Initialize translations array with English (default) data
|
||||||
|
$translations = [];
|
||||||
|
$translations[1] = [
|
||||||
|
'name' => $category_data->name,
|
||||||
|
'description' => $category_data->description,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add other language translations
|
||||||
|
foreach ($category_data->translations as $translation) {
|
||||||
|
$translations[$translation->language_id] = [
|
||||||
|
'name' => $translation->name,
|
||||||
|
'description' => $translation->description,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$parent_category_data = Category::find($category_data->parent_category_id);
|
||||||
|
$parent_category = $parent_category_data->name ?? '';
|
||||||
|
$categories = Category::with('subcategories')->get();
|
||||||
|
// Fetch all languages including English
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('category.edit', compact('category_data', 'parent_category_data','parent_category', 'translations', 'languages','categories'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('category-update');
|
||||||
|
try {
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"name.$defaultLangId" => 'required|string|max:30',
|
||||||
|
'image' => 'nullable|mimes:jpg,jpeg,png|max:7168',
|
||||||
|
'parent_category_id' => 'nullable|integer',
|
||||||
|
"description.$defaultLangId" => 'nullable|string',
|
||||||
|
'slug' => [
|
||||||
|
'nullable',
|
||||||
|
'regex:/^[a-zA-Z0-9\-_]+$/'
|
||||||
|
],
|
||||||
|
'status' => 'required|boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["name.$langId"] = 'nullable|string|max:30';
|
||||||
|
$rules["description.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate($rules, [
|
||||||
|
'slug.regex' => 'Slug must be only English letters, numbers, hyphens (-), or underscores (_).'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$category = Category::find($id);
|
||||||
|
if ($request->parent_category_id == $category->id) {
|
||||||
|
return back()->withErrors(['parent_category' => 'A category cannot be set as its own parent.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $request->input("name.$defaultLangId"),
|
||||||
|
'description' => $request->input("description.$defaultLangId"),
|
||||||
|
'parent_category_id' => $request->parent_category_id,
|
||||||
|
'status' => $request->status,
|
||||||
|
'is_job_category' => $request->is_job_category ?? 0,
|
||||||
|
'price_optional' => $request->price_optional ?? 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::compressAndReplace($request->file('image'), $this->uploadFolder, $category->getRawOriginal('image'));
|
||||||
|
}
|
||||||
|
$slug = trim($request->input('slug') ?? '');
|
||||||
|
$slug = preg_replace('/[^a-z0-9]+/i', '-', strtolower($slug));
|
||||||
|
$slug = trim($slug, '-');
|
||||||
|
if (empty($slug)) {
|
||||||
|
$slug = HelperService::generateRandomSlug();
|
||||||
|
}
|
||||||
|
$data['slug'] = HelperService::generateUniqueSlug(new Category(), $slug, $category->id);
|
||||||
|
$category->update($data);
|
||||||
|
|
||||||
|
if ($request->has('is_job_category')) {
|
||||||
|
$category->subcategories()->update([
|
||||||
|
'is_job_category' => $request->is_job_category ? 1 : 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('price_optional')) {
|
||||||
|
$category->subcategories()->update([
|
||||||
|
'price_optional' => $request->price_optional ? 1 : 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedName = $request->input("name.$langId");
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
|
||||||
|
CategoryTranslation::updateOrCreate(
|
||||||
|
['category_id' => $category->id, 'language_id' => $langId],
|
||||||
|
[
|
||||||
|
'name' => $translatedName ?? '',
|
||||||
|
'description' => $translatedDescription ?? null
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successRedirectResponse("Category Updated Successfully", route('category.index'));
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th);
|
||||||
|
ResponseService::errorRedirectResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function destroy($id) {
|
||||||
|
// ResponseService::noPermissionThenSendJson('category-delete');
|
||||||
|
// try {
|
||||||
|
// $category = Category::withCount(['subcategories', 'custom_fields'])
|
||||||
|
// ->with('subcategories')
|
||||||
|
// ->findOrFail($id);
|
||||||
|
// if ($category->all_items_count > 0) {
|
||||||
|
// ResponseService::errorResponse('Cannot delete category. It has associated advertisements.');
|
||||||
|
// }
|
||||||
|
// if ($category->other_items_count > 0) {
|
||||||
|
// ResponseService::errorResponse(
|
||||||
|
// 'Cannot delete category. Delete non-active items first.'
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// if ($category->subcategories_count > 0 || $category->custom_fields_count > 0) {
|
||||||
|
// ResponseService::errorResponse('Failed to delete category', 'Cannot delete category. Remove associated subcategories and custom fields first.');
|
||||||
|
// }
|
||||||
|
// if ($category->delete()) {
|
||||||
|
// ResponseService::successResponse('Category delete successfully');
|
||||||
|
// }
|
||||||
|
// } catch (QueryException $th) {
|
||||||
|
// ResponseService::logErrorResponse($th, 'Failed to delete category', 'Cannot delete category. Remove associated subcategories and custom fields first.');
|
||||||
|
// ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
// } catch (Throwable $th) {
|
||||||
|
// ResponseService::logErrorResponse($th, "CategoryController -> delete");
|
||||||
|
// ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('category-delete');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$category = Category::withCount([
|
||||||
|
'subcategories',
|
||||||
|
'custom_fields',
|
||||||
|
'items as all_items_count',
|
||||||
|
'items as other_items_count' => function ($q) {
|
||||||
|
$q->where('status', '!=', 'active');
|
||||||
|
}
|
||||||
|
])->findOrFail($id);
|
||||||
|
|
||||||
|
if ($category->all_items_count > 0) {
|
||||||
|
return ResponseService::errorResponse(
|
||||||
|
'Cannot delete category. It has associated advertisements.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($category->other_items_count > 0) {
|
||||||
|
return ResponseService::errorResponse(
|
||||||
|
'Cannot delete category. Delete non-active items first.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($category->subcategories_count > 0 || $category->custom_fields_count > 0) {
|
||||||
|
return ResponseService::errorResponse(
|
||||||
|
'Cannot delete category. Remove associated subcategories and custom fields first.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$category->delete();
|
||||||
|
|
||||||
|
return ResponseService::successResponse('Category deleted successfully');
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'CategoryController -> destroy');
|
||||||
|
return ResponseService::errorResponse('Something went wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getSubCategories($id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('category-list');
|
||||||
|
$subcategories = Category::where('parent_category_id', $id)
|
||||||
|
->with('subcategories')
|
||||||
|
->withCount('custom_fields')
|
||||||
|
->withCount('subcategories')
|
||||||
|
->withCount('items')
|
||||||
|
->orderBy('sequence')
|
||||||
|
->get()
|
||||||
|
->map(function ($subcategory) {
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('category-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('category.edit', $subcategory->id));
|
||||||
|
}
|
||||||
|
if (Auth::user()->can('category-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('category.destroy', $subcategory->id));
|
||||||
|
}
|
||||||
|
if ($subcategory->subcategories_count > 1) {
|
||||||
|
$operate .= BootstrapTableService::button('fa fa-list-ol',route('sub.category.order.change',$subcategory->id),['btn-secondary']);
|
||||||
|
}
|
||||||
|
$subcategory->operate = $operate;
|
||||||
|
return $subcategory;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($subcategories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function customFields($id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('custom-field-list');
|
||||||
|
$category = Category::find($id);
|
||||||
|
$p_id = $category->parent_category_id;
|
||||||
|
$cat_id = $category->id;
|
||||||
|
$category_name = $category->name;
|
||||||
|
|
||||||
|
return view('category.custom-fields', compact('cat_id', 'category_name', 'p_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCategoryCustomFields(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('custom-field-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
|
||||||
|
$sql = CustomField::whereHas('categories', static function ($q) use ($id) {
|
||||||
|
$q->where('category_id', $id);
|
||||||
|
})->orderBy($sort, $order);
|
||||||
|
|
||||||
|
if (isset($request->search)) {
|
||||||
|
$sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql->take($limit);
|
||||||
|
$total = $sql->count();
|
||||||
|
$res = $sql->skip($offset)->take($limit)->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$rows = array();
|
||||||
|
$tempRow['type'] = '';
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($res as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
// $operate = BootstrapTableService::editButton(route('custom-fields.edit', $row->id));
|
||||||
|
$operate = BootstrapTableService::deleteButton(route('category.custom-fields.destroy', [$id, $row->id]));
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyCategoryCustomField($categoryID, $customFieldID) {
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenRedirect('custom-field-delete');
|
||||||
|
CustomFieldCategory::where(['category_id' => $categoryID, 'custom_field_id' => $customFieldID])->delete();
|
||||||
|
ResponseService::successResponse("Custom Field Deleted Successfully");
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "CategoryController -> destroyCategoryCustomField");
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function categoriesReOrder(Request $request) {
|
||||||
|
$categories = Category::whereNull('parent_category_id')->orderBy('sequence')->get();
|
||||||
|
return view('category.categories-order', compact('categories'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function subCategoriesReOrder(Request $request ,$id) {
|
||||||
|
$categories = Category::with('subcategories')->where('parent_category_id', $id)->orderBy('sequence')->get();
|
||||||
|
return view('category.sub-categories-order', compact('categories'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateOrder(Request $request) {
|
||||||
|
$request->validate([
|
||||||
|
'order' => 'required'
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
$order = json_decode($request->input('order'), true);
|
||||||
|
$data = [];
|
||||||
|
foreach ($order as $index => $id) {
|
||||||
|
$data[] = [
|
||||||
|
'id' => $id,
|
||||||
|
'sequence' => $index + 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
Category::upsert($data, ['id'], ['sequence']);
|
||||||
|
ResponseService::successResponse("Order Updated Successfully");
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th);
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
224
app/Http/Controllers/Controller.php
Normal file
224
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\ContactUs;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Controller as BaseController;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/*Create Method which are common across the system*/
|
||||||
|
|
||||||
|
class Controller extends BaseController
|
||||||
|
{
|
||||||
|
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||||
|
|
||||||
|
public function changeRowOrder(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'data' => 'required|array',
|
||||||
|
'table' => 'required|string',
|
||||||
|
'column' => 'nullable',
|
||||||
|
]);
|
||||||
|
$column = $request->column ?? "sequence";
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
foreach ($request->data as $index => $row) {
|
||||||
|
$data[] = [
|
||||||
|
'id' => $row['id'],
|
||||||
|
(string)$column => $index
|
||||||
|
];
|
||||||
|
}
|
||||||
|
DB::table($request->table)->upsert($data, ['id'], [(string)$column]);
|
||||||
|
ResponseService::successResponse("Order Changed Successfully");
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changeStatus(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|numeric',
|
||||||
|
'status' => 'required|boolean',
|
||||||
|
'table' => 'required|string',
|
||||||
|
'column' => 'nullable',
|
||||||
|
]);
|
||||||
|
$column = $request->column ?? "status";
|
||||||
|
|
||||||
|
//Special case for deleted_at column
|
||||||
|
if ($column == "deleted_at") {
|
||||||
|
//If status is active then deleted_At will be empty otherwise it will have the current time
|
||||||
|
$request->status = ($request->status) ? null : now();
|
||||||
|
}
|
||||||
|
DB::table($request->table)->where('id', $request->id)->update([(string)$column => $request->status]);
|
||||||
|
|
||||||
|
if ($request->table === 'categories') {
|
||||||
|
$category = DB::table('categories')->where('id', $request->id)->first();
|
||||||
|
|
||||||
|
if (!$category) {
|
||||||
|
return ResponseService::errorResponse("Category not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If trying to activate a category but its parent is inactive
|
||||||
|
if ($request->status && $category->parent_category_id) {
|
||||||
|
$parent = DB::table('categories')->where('id', $category->parent_category_id)->first();
|
||||||
|
if ($parent && !$parent->status) {
|
||||||
|
return ResponseService::errorResponse("Cannot activate subcategory while parent is inactive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the category itself
|
||||||
|
DB::table('categories')->where('id', $request->id)->update([$column => $request->status]);
|
||||||
|
|
||||||
|
// If status = 0, recursively deactivate all subcategories
|
||||||
|
if (!$request->status) {
|
||||||
|
$this->deactivateSubcategories($request->id, $column);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return ResponseService::successResponse("status updated successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->table === 'items') {
|
||||||
|
$item = DB::table('items')->where('id', $request->id)->first();
|
||||||
|
if ($item) {
|
||||||
|
$user = DB::table('users')->where('id', $item->user_id)->first();
|
||||||
|
if ($user) {
|
||||||
|
$userToken = DB::table('user_fcm_tokens')
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->pluck('fcm_token')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (!empty($userToken)) {
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$userToken,
|
||||||
|
'About ' . $item->name,
|
||||||
|
"Your Advertisement is " . (is_null($request->status) ? 'Active' : 'Inactive') . " by Admin",
|
||||||
|
'item-update',
|
||||||
|
['id' => $request->id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResponseService::successResponse("Status Updated Successfully");
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function readLanguageFile() {
|
||||||
|
// try {
|
||||||
|
// // https://medium.com/@serhii.matrunchyk/using-laravel-localization-with-javascript-and-vuejs-23064d0c210e
|
||||||
|
// header('Content-Type: text/javascript');
|
||||||
|
// // $labels = Cache::remember('lang.js', 3600, static function () {
|
||||||
|
// // $lang = app()->getLocale();
|
||||||
|
// $lang = Session::get('language');
|
||||||
|
// // $lang = app()->getLocale();
|
||||||
|
// $test = $lang->code ?? "en";
|
||||||
|
// $files = resource_path('lang/' . $test . '.json');
|
||||||
|
// // return File::get($files);
|
||||||
|
// // });]
|
||||||
|
// echo('window.languageLabels = ' . File::get($files));
|
||||||
|
// http_response_code(200);
|
||||||
|
// exit();
|
||||||
|
// } catch (Throwable $th) {
|
||||||
|
// ResponseService::errorResponse($th);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public function readLanguageFile()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
header('Content-Type: text/javascript');
|
||||||
|
|
||||||
|
$lang = Session::get('language');
|
||||||
|
$code = $lang->code ?? 'en';
|
||||||
|
|
||||||
|
$file = resource_path("lang/{$code}.json");
|
||||||
|
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
echo 'window.languageLabels = {};';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = File::get($file);
|
||||||
|
|
||||||
|
// Validate JSON
|
||||||
|
json_decode($json);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
echo 'window.languageLabels = {};';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "window.languageLabels = {$json};";
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
echo 'window.languageLabels = {};';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contactUsUIndex()
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('user-queries-list');
|
||||||
|
return view('contact-us');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contactUsShow(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('user-queries-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->input('sort', 'sequence');
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = ContactUs::orderBy($sort, $order);
|
||||||
|
|
||||||
|
if ($sort !== 'created_at') {
|
||||||
|
$sql->orderBy('created_at', 'desc');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['search'])) {
|
||||||
|
$search = $_GET['search'];
|
||||||
|
$sql->where('id', 'LIKE', "%$search%")
|
||||||
|
->orwhere('name', 'LIKE', "%$search%")
|
||||||
|
->orwhere('subject', 'LIKE', "%$search%")
|
||||||
|
->orwhere('message', 'LIKE', "%$search%");
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$rows[] = $row->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
private function deactivateSubcategories($parentId, $column = 'status')
|
||||||
|
{
|
||||||
|
$subcategories = DB::table('categories')->where('parent_category_id', $parentId)->get();
|
||||||
|
|
||||||
|
foreach ($subcategories as $sub) {
|
||||||
|
DB::table('categories')->where('id', $sub->id)->update([$column => 0]);
|
||||||
|
$this->deactivateSubcategories($sub->id, $column); // recursive call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
170
app/Http/Controllers/CurrencyController.php
Normal file
170
app/Http/Controllers/CurrencyController.php
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Country;
|
||||||
|
use App\Models\Currency;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
// use Illuminate\Support\Facades\Request;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class CurrencyController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['currency-list', 'currency-create', 'currency-update', 'currency-delete']);
|
||||||
|
$currencies = Currency::all();
|
||||||
|
|
||||||
|
return view('currency.index', compact('currencies'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('currency-create');
|
||||||
|
$countries = Country::all();
|
||||||
|
|
||||||
|
return view('currency.create', compact('countries'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('currency-list');
|
||||||
|
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = Currency::with('country:id,name');
|
||||||
|
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
|
||||||
|
$sql->sort($sort, $order)
|
||||||
|
->skip($offset)
|
||||||
|
->take($limit);
|
||||||
|
|
||||||
|
$result = $sql->get();
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
|
||||||
|
if (Auth::user()->can('currency-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('currency.edit', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('currency-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('currency.destroy', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'total' => $total,
|
||||||
|
'rows' => $rows,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('currency-create');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'iso_code' => 'required|string|unique:currencies,iso_code',
|
||||||
|
'name' => 'required|string|unique:currencies,name',
|
||||||
|
'symbol' => 'required|string',
|
||||||
|
'symbol_position' => 'required|in:left,right',
|
||||||
|
'country_id' => 'required|integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Currency::create([
|
||||||
|
'iso_code' => strtoupper($request->iso_code),
|
||||||
|
'name' => $request->name,
|
||||||
|
'symbol' => $request->symbol,
|
||||||
|
'symbol_position' => $request->symbol_position,
|
||||||
|
'country_id' => $request->country_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return ResponseService::successResponse(
|
||||||
|
__('Currency Created Successfully'),
|
||||||
|
);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Currency Controller -> store');
|
||||||
|
ResponseService::errorResponse(__($th->getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('currency-update');
|
||||||
|
$currency = Currency::findOrFail($id);
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
$countries = Country::all();
|
||||||
|
|
||||||
|
return view('currency.edit', compact('currency', 'languages', 'countries'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('currency-update');
|
||||||
|
|
||||||
|
// dd($request->all());
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required',
|
||||||
|
'symbol' => 'required',
|
||||||
|
'iso_code' => 'required',
|
||||||
|
'symbol_position' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$currency = Currency::findOrFail($id);
|
||||||
|
|
||||||
|
$data = $request->all();
|
||||||
|
|
||||||
|
$currency->update($data);
|
||||||
|
|
||||||
|
return ResponseService::successRedirectResponse(
|
||||||
|
__('Currency Updated Successfully'),
|
||||||
|
route('currency.index')
|
||||||
|
);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Currency Controller -> update');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('currency-delete');
|
||||||
|
try {
|
||||||
|
$currency = Currency::findOrFail($id);
|
||||||
|
$currency->delete();
|
||||||
|
ResponseService::successResponse('Currency Deleted Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Currency Controller -> destroy');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1638
app/Http/Controllers/CustomFieldController.php
Normal file
1638
app/Http/Controllers/CustomFieldController.php
Normal file
File diff suppressed because it is too large
Load Diff
274
app/Http/Controllers/CustomersController.php
Normal file
274
app/Http/Controllers/CustomersController.php
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Package;
|
||||||
|
use App\Models\PaymentTransaction;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use App\Models\UserPurchasedPackage;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\HelperService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class CustomersController extends Controller {
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['customer-list', 'customer-update']);
|
||||||
|
$packages = Package::all()->where('status', 1);
|
||||||
|
$settings = Setting::whereIn('name', ['currency_symbol', 'currency_symbol_position','free_ad_listing'])
|
||||||
|
->pluck('value', 'name');
|
||||||
|
$currency_symbol = $settings['currency_symbol'] ?? '';
|
||||||
|
$currency_symbol_position = $settings['currency_symbol_position'] ?? '';
|
||||||
|
$free_ad_listing = $settings['free_ad_listing'] ?? '';
|
||||||
|
$itemListingPackage = $packages->filter(function ($data) {
|
||||||
|
return $data->type == "item_listing";
|
||||||
|
});
|
||||||
|
$advertisementPackage = $packages->filter(function ($data) {
|
||||||
|
return $data->type == "advertisement";
|
||||||
|
});
|
||||||
|
|
||||||
|
return view('customer.index', compact('packages', 'itemListingPackage', 'advertisementPackage','currency_symbol','currency_symbol_position','free_ad_listing'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request) {
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('customer-update');
|
||||||
|
User::where('id', $request->id)->update(['status' => $request->status]);
|
||||||
|
$message = $request->status ? "Customer Activated Successfully" : "Customer Deactivated Successfully";
|
||||||
|
ResponseService::successResponse($message);
|
||||||
|
} catch (Throwable) {
|
||||||
|
ResponseService::errorRedirectResponse('Something Went Wrong ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request) {
|
||||||
|
ResponseService::noAnyPermissionThenSendJson(['customer-list','notification-list', 'notification-create', 'notification-update', 'notification-delete']);
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
if ($request->notification_list) {
|
||||||
|
$sql = User::role('User')->orderBy($sort, $order)->has('fcm_tokens')->where('notification', 1);
|
||||||
|
} else {
|
||||||
|
$sql = User::role('User')->orderBy($sort, $order)->withCount('items')->withTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
$no = 1;
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['no'] = $no++;
|
||||||
|
$tempRow['status'] = empty($row->deleted_at);
|
||||||
|
$tempRow['is_verified'] = $row->is_verified;
|
||||||
|
$tempRow['auto_approve_advertisement'] = $row->auto_approve_item;
|
||||||
|
|
||||||
|
if (config('app.demo_mode')) {
|
||||||
|
// Get the first two digits, Apply enough asterisks to cover the middle numbers , Get the last two digits;
|
||||||
|
if (!empty($row->mobile)) {
|
||||||
|
$tempRow['mobile'] = substr($row->mobile, 0, 3) . str_repeat('*', (strlen($row->mobile) - 5)) . substr($row->mobile, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($row->email)) {
|
||||||
|
$tempRow['email'] = substr($row->email, 0, 3) . '****' . substr($row->email, strpos($row->email, "@"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$operate = BootstrapTableService::button(
|
||||||
|
'fa fa-cart-plus',
|
||||||
|
route('customer.assign.package', $row->id),
|
||||||
|
['btn-outline-danger', 'assign_package'],
|
||||||
|
[
|
||||||
|
'title' => __("Assign Package"),
|
||||||
|
"data-bs-target" => "#assignPackageModal",
|
||||||
|
"data-bs-toggle" => "modal"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$operate .= BootstrapTableService::button(
|
||||||
|
'fa fa-minus-circle',
|
||||||
|
'#',
|
||||||
|
['btn-outline-primary', 'manage_packages', 'ms-1'],
|
||||||
|
[
|
||||||
|
'title' => __("cancel Packages"),
|
||||||
|
"data-bs-target" => "#managePackagesModal",
|
||||||
|
"data-bs-toggle" => "modal",
|
||||||
|
"data-user-id" => $row->id
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assignPackage(Request $request) {
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'package_id' => 'required',
|
||||||
|
'payment_gateway' => 'required|in:cash,cheque',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
ResponseService::noPermissionThenSendJson('customer-list');
|
||||||
|
$user = User::find($request->user_id);
|
||||||
|
if (empty($user)) {
|
||||||
|
ResponseService::errorResponse('User is not Active');
|
||||||
|
}
|
||||||
|
$package = Package::findOrFail($request->package_id);
|
||||||
|
// Create a new payment transaction
|
||||||
|
$paymentTransaction = PaymentTransaction::create([
|
||||||
|
'user_id' => $request->user_id,
|
||||||
|
'package_id' => $request->package_id,
|
||||||
|
'amount' => $package->final_price,
|
||||||
|
'order_id' => null,
|
||||||
|
'payment_gateway' => $request->payment_gateway,
|
||||||
|
'payment_status' => 'succeed',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create a new user purchased package record
|
||||||
|
$userPackage = UserPurchasedPackage::create([
|
||||||
|
'user_id' => $request->user_id,
|
||||||
|
'package_id' => $request->package_id,
|
||||||
|
'start_date' => Carbon::now(),
|
||||||
|
'end_date' => $package->duration == "unlimited" ? null :Carbon::now()->addDays($package->duration),
|
||||||
|
'total_limit' => $package->item_limit == "unlimited" ? null : $package->item_limit,
|
||||||
|
'used_limit' => 0,
|
||||||
|
'payment_transactions_id' => $paymentTransaction->id,
|
||||||
|
'listing_duration_type' => $package->listing_duration_type,
|
||||||
|
'listing_duration_days' => $package->listing_duration_days,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user_token = UserFcmToken::where('user_id', $request->user_id)
|
||||||
|
->pluck('fcm_token')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (!empty($user_token)) {
|
||||||
|
$title = "Package Assigned";
|
||||||
|
$message = "A new subscription package has been assigned to your account by the administrator.";
|
||||||
|
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$user_token,
|
||||||
|
$title,
|
||||||
|
$message,
|
||||||
|
"package-assigned",
|
||||||
|
['id' => $userPackage->id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('Package assigned to user Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollback();
|
||||||
|
ResponseService::logErrorResponse($th, "CustomersController --> assignPackage");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActivePackages(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('customer-list');
|
||||||
|
try {
|
||||||
|
$userId = $request->user_id;
|
||||||
|
if (empty($userId)) {
|
||||||
|
ResponseService::errorResponse('User ID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$activePackages = UserPurchasedPackage::where('user_id', $userId)
|
||||||
|
->whereDate('start_date', '<=', date('Y-m-d'))
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereDate('end_date', '>', date('Y-m-d'))->orWhereNull('end_date');
|
||||||
|
})
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereColumn('used_limit', '<', 'total_limit')->orWhereNull('total_limit');
|
||||||
|
})
|
||||||
|
->with(['package' => function($q) {
|
||||||
|
$q->select('id', 'name', 'type', 'duration', 'item_limit');
|
||||||
|
}])
|
||||||
|
->orderBy('end_date', 'asc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$packages = [];
|
||||||
|
foreach ($activePackages as $pkg) {
|
||||||
|
$packages[] = [
|
||||||
|
'id' => $pkg->id,
|
||||||
|
'package_name' => $pkg->package->name ?? '',
|
||||||
|
'package_type' => $pkg->package->type ?? '',
|
||||||
|
'start_date' => $pkg->start_date,
|
||||||
|
'end_date' => $pkg->end_date ?? __('Unlimited'),
|
||||||
|
'total_limit' => $pkg->total_limit ?? __('Unlimited'),
|
||||||
|
'used_limit' => $pkg->used_limit,
|
||||||
|
'remaining_limit' => $pkg->remaining_item_limit,
|
||||||
|
'remaining_days' => $pkg->remaining_days,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse(__('Data Fetched Successfully'), $packages);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "CustomersController --> getActivePackages");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancelPackage(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('customer-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'package_id' => 'required|exists:user_purchased_packages,id',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
$userPackage = UserPurchasedPackage::findOrFail($request->package_id);
|
||||||
|
|
||||||
|
// Set end_date to today to cancel the package
|
||||||
|
$userPackage->end_date = date('Y-m-d');
|
||||||
|
$userPackage->save();
|
||||||
|
$user_token = UserFcmToken::where('user_id', $userPackage->user_id)
|
||||||
|
->pluck('fcm_token')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (!empty($user_token)) {
|
||||||
|
$title = "Subscription Cancelled";
|
||||||
|
$message = "Your subscription has been cancelled by the administrator.";
|
||||||
|
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$user_token,
|
||||||
|
$title,
|
||||||
|
$message,
|
||||||
|
"package-cancelled",
|
||||||
|
['id' => $userPackage->id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse(__('Package cancelled successfully'));
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollback();
|
||||||
|
ResponseService::logErrorResponse($th, "CustomersController --> cancelPackage");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
244
app/Http/Controllers/FaqController.php
Normal file
244
app/Http/Controllers/FaqController.php
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\Faq;
|
||||||
|
use App\Models\FaqTranslation;
|
||||||
|
use App\Models\Language;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class FaqController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['faq-create','faq-list','faq-update','faq-delete']);
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('faq.create',compact('languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('faq-create');
|
||||||
|
|
||||||
|
$question = $request->input('question');
|
||||||
|
$answer = $request->input('answer');
|
||||||
|
$baseRules = [
|
||||||
|
"question.1" => 'required|string',
|
||||||
|
"answer.1" => 'required|string',
|
||||||
|
'answer.*' => 'nullable|string|required_with:question.*',
|
||||||
|
];
|
||||||
|
$messages = [
|
||||||
|
"question.1.required" => "Please enter the question in English.",
|
||||||
|
"answer.1.required" => "Please enter the answer in English.",
|
||||||
|
"answer.*.required_with" => "The answer field is required when the question is present."
|
||||||
|
];
|
||||||
|
$validator = Validator::make($request->all(), $baseRules, $messages);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// Store main FAQ in English
|
||||||
|
$faq = Faq::create([
|
||||||
|
'question' => $question[1],
|
||||||
|
'answer' => $answer[1],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Store other language translations
|
||||||
|
foreach ($question as $langId => $qText) {
|
||||||
|
if ($langId == 1 || empty($qText)) continue;
|
||||||
|
|
||||||
|
$translatedAnswer = $answer[$langId] ?? null;
|
||||||
|
//if (!$translatedAnswer) continue;
|
||||||
|
|
||||||
|
$language = Language::find($langId);
|
||||||
|
if ($language) {
|
||||||
|
FaqTranslation::create([
|
||||||
|
'faq_id' => $faq->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'question' => $qText,
|
||||||
|
'answer' => $translatedAnswer,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseService::successResponse('FAQ created successfully');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "Faq Controller -> store");
|
||||||
|
return ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
|
||||||
|
ResponseService::noPermissionThenSendJson('faq-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'sequence');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
|
||||||
|
$sql = Faq::with('translations')->orderBy($sort, $order);
|
||||||
|
|
||||||
|
if (!empty($_GET['search'])) {
|
||||||
|
$search = $_GET['search'];
|
||||||
|
$sql->where('id', 'LIKE', "%$search%")->orwhere('question', 'LIKE', "%$search%")->orwhere('answer', 'LIKE', "%$search%");
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('faq-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('faq.update', $row->id), true, '#editModal', 'faqEvents', $row->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('faq-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('faq.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$tempRow['translations'] = $row->translations->map(function ($t) {
|
||||||
|
return [
|
||||||
|
'language_id' => $t->language_id,
|
||||||
|
'question' => $t->question,
|
||||||
|
'answer' =>$t->answer
|
||||||
|
];
|
||||||
|
}) ?? [];
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "FaqController --> show");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('faq-update');
|
||||||
|
|
||||||
|
$question = $request->input('question');
|
||||||
|
$answer = $request->input('answer');
|
||||||
|
|
||||||
|
// Validate English (language_id = 1)
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'question.1' => 'required|string',
|
||||||
|
'answer.1' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$faq = Faq::findOrFail($id);
|
||||||
|
|
||||||
|
// Update default English values
|
||||||
|
$faq->update([
|
||||||
|
'question' => $question[1],
|
||||||
|
'answer' => $answer[1],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Loop through translations
|
||||||
|
foreach ($question as $langId => $qText) {
|
||||||
|
if ($langId == 1) continue;
|
||||||
|
|
||||||
|
$translatedAnswer = $answer[$langId] ?? null;
|
||||||
|
|
||||||
|
// Skip empty translations
|
||||||
|
if (empty($qText) || empty($translatedAnswer)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$language = Language::find($langId);
|
||||||
|
if ($language) {
|
||||||
|
// Check if translation exists
|
||||||
|
$translation = FaqTranslation::where('faq_id', $faq->id)
|
||||||
|
->where('language_id', $langId)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($translation) {
|
||||||
|
// Update existing
|
||||||
|
$translation->update([
|
||||||
|
'question' => $qText,
|
||||||
|
'answer' => $translatedAnswer,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Create new
|
||||||
|
FaqTranslation::create([
|
||||||
|
'faq_id' => $faq->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'question' => $qText,
|
||||||
|
'answer' => $translatedAnswer,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseService::successResponse('FAQ updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "Faq Controller -> update");
|
||||||
|
return ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('faq-delete');
|
||||||
|
Faq::findOrFail($id)->delete();
|
||||||
|
ResponseService::successResponse('FAQ delete successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "Faq Controller -> destroy");
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
223
app/Http/Controllers/FeatureSectionController.php
Normal file
223
app/Http/Controllers/FeatureSectionController.php
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\FeatureSection;
|
||||||
|
use App\Models\FeatureSectionTranslation;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\HelperService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class FeatureSectionController extends Controller {
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['feature-section-list', 'feature-section-create', 'feature-section-update', 'feature-section-delete']);
|
||||||
|
$categories = Category::get();
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('feature_section.index', compact('categories','languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('feature-section-create');
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"title.$defaultLangId" => 'required|string',
|
||||||
|
"description.$defaultLangId" => 'nullable|string',
|
||||||
|
'slug' => 'required',
|
||||||
|
'filter' => 'required|in:most_liked,most_viewed,price_criteria,category_criteria,featured_ads',
|
||||||
|
'style' => 'required|in:style_1,style_2,style_3,style_4',
|
||||||
|
'min_price' => 'required_if:filter,price_criteria',
|
||||||
|
'max_price' => 'required_if:filter,price_criteria',
|
||||||
|
'category_id' => 'required_if:filter,category_criteria',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["title.$langId"] = 'nullable|string';
|
||||||
|
$rules["description.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), $rules);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = [
|
||||||
|
'title' => $request->input("title.$defaultLangId"),
|
||||||
|
'description' => $request->input("description.$defaultLangId"),
|
||||||
|
'slug' => $request->slug,
|
||||||
|
'filter' => $request->filter,
|
||||||
|
'style' => $request->style,
|
||||||
|
'sequence' => FeatureSection::max('sequence') + 1
|
||||||
|
];
|
||||||
|
|
||||||
|
$data['slug'] = HelperService::generateUniqueSlug(new FeatureSection(), $request->slug);
|
||||||
|
|
||||||
|
if ($request->filter == "price_criteria") {
|
||||||
|
$data['min_price'] = $request->min_price;
|
||||||
|
$data['max_price'] = $request->max_price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filter == "category_criteria") {
|
||||||
|
$data['value'] = !empty($request->category_id) ? implode(',', $request->category_id) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$featureSection = FeatureSection::create($data);
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedTitle = $request->input("title.$langId");
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
|
||||||
|
if (!empty($translatedTitle) || !empty($translatedDescription)) {
|
||||||
|
FeatureSectionTranslation::create([
|
||||||
|
'feature_section_id' => $featureSection->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'name' => $translatedTitle ?? '',
|
||||||
|
'description' => $translatedDescription ?? '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResponseService::successResponse('Feature Section Added Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "FeaturedSection Controller -> store");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('feature-section-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'sequence');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
$sql = FeatureSection::with('translations');
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->orderBy($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('feature-section-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('feature-section.update', $row->id), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('feature-section-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('feature-section.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('feature-section-update');
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"title.$defaultLangId" => 'required|string',
|
||||||
|
"description.$defaultLangId" => 'nullable|string',
|
||||||
|
'slug' => 'required',
|
||||||
|
'filter' => 'required|in:most_liked,most_viewed,price_criteria,category_criteria,featured_ads',
|
||||||
|
'style' => 'required|in:style_1,style_2,style_3,style_4',
|
||||||
|
'min_price' => 'required_if:filter,price_criteria',
|
||||||
|
'max_price' => 'required_if:filter,price_criteria',
|
||||||
|
'category_id' => 'required_if:filter,category_criteria',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["title.$langId"] = 'nullable|string';
|
||||||
|
$rules["description.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), $rules);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$feature_section = FeatureSection::findOrFail($id);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'title' => $request->input("title.$defaultLangId"),
|
||||||
|
'description' => $request->input("description.$defaultLangId"),
|
||||||
|
'slug' => $request->slug,
|
||||||
|
'filter' => $request->filter,
|
||||||
|
'style' => $request->style,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->filter == "price_criteria") {
|
||||||
|
$data['min_price'] = $request->min_price;
|
||||||
|
$data['max_price'] = $request->max_price;
|
||||||
|
} else {
|
||||||
|
$data['min_price'] = null;
|
||||||
|
$data['max_price'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filter == "category_criteria") {
|
||||||
|
$data['value'] = !empty($request->category_id) ? implode(',', $request->category_id) : '';
|
||||||
|
} else {
|
||||||
|
$data['value'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['slug'] = HelperService::generateUniqueSlug(new FeatureSection(), $request->slug, $feature_section->id);
|
||||||
|
$feature_section->update($data);
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedTitle = $request->input("title.$langId");
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
|
||||||
|
FeatureSectionTranslation::updateOrCreate(
|
||||||
|
[
|
||||||
|
'feature_section_id' => $feature_section->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => $translatedTitle ?? '',
|
||||||
|
'description' => $translatedDescription ?? '',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ResponseService::successResponse('Feature Section Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "FeaturedSection Controller -> update");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id) {
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('feature-section-delete');
|
||||||
|
FeatureSection::findOrFail($id)->delete();
|
||||||
|
ResponseService::successResponse('Feature Section delete successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "FeaturedSection Controller -> destroy");
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
app/Http/Controllers/HomeController.php
Normal file
120
app/Http/Controllers/HomeController.php
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\CustomField;
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class HomeController extends Controller {
|
||||||
|
/**
|
||||||
|
* Create a new controller instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
$this->middleware('auth');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
$items = Item::select('id','name','price','latitude','longitude','city','state','country','image')->where('clicks','>',0)->where('status', 'approved')->inRandomOrder()->limit(50)->get();
|
||||||
|
$categories = Category::withCount('items')->with('translations')->whereHas('items')->get();
|
||||||
|
|
||||||
|
$category_name = array();
|
||||||
|
$category_item_count = array();
|
||||||
|
|
||||||
|
foreach ($categories as $value) {
|
||||||
|
$category_name[] = "'" . $value->translated_name . "'";
|
||||||
|
$category_item_count[] = $value->items_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categories_count = Category::count();
|
||||||
|
$user_count = User::role('User')->withTrashed()->count();
|
||||||
|
$item_count = Item::withTrashed()->count();
|
||||||
|
$custom_field_count = CustomField::count();
|
||||||
|
// $items = Item::all();
|
||||||
|
return view('home', compact('category_item_count', 'category_name', 'categories_count', 'item_count', 'user_count', 'custom_field_count','items'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changePasswordIndex() {
|
||||||
|
return view('change_password.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function changePasswordUpdate(Request $request) {
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'old_password' => 'required',
|
||||||
|
'new_password' => 'required|min:8',
|
||||||
|
'confirm_password' => 'required|same:new_password',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$user = Auth::user();
|
||||||
|
if (!Hash::check($request->old_password, Auth::user()->password)) {
|
||||||
|
ResponseService::errorResponse("Incorrect old password");
|
||||||
|
}
|
||||||
|
$user->password = Hash::make($request->confirm_password);
|
||||||
|
$user->update();
|
||||||
|
ResponseService::successResponse('Password Change Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "HomeController --> changePasswordUpdate");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function changeProfileIndex() {
|
||||||
|
return view('change_profile.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changeProfileUpdate(Request $request) {
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required',
|
||||||
|
'email' => 'required|email|unique:users,email,' . Auth::user()->id,
|
||||||
|
'profile' => 'nullable|mimes:jpeg,jpg,png'
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$user = Auth::user();
|
||||||
|
$data = [
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email
|
||||||
|
];
|
||||||
|
if ($request->hasFile('profile')) {
|
||||||
|
$data['profile'] = $request->file('profile')->store('admin_profile', 'public');
|
||||||
|
}
|
||||||
|
$user->update($data);
|
||||||
|
ResponseService::successResponse('Profile Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "HomeController --> updateProfile");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
public function getMapsData()
|
||||||
|
{
|
||||||
|
$apiKey = env('PLACE_API_KEY');
|
||||||
|
|
||||||
|
$url = "https://maps.googleapis.com/maps/api/js?" . http_build_query([
|
||||||
|
'libraries' => 'places',
|
||||||
|
'key' => $apiKey, // Use the API key from the .env file
|
||||||
|
// Add any other parameters you need here
|
||||||
|
]);
|
||||||
|
|
||||||
|
return file_get_contents($url);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
app/Http/Controllers/InstallerController.php
Normal file
77
app/Http/Controllers/InstallerController.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use dacoto\EnvSet\Facades\EnvSet;
|
||||||
|
use dacoto\LaravelWizardInstaller\Controllers\InstallFolderController;
|
||||||
|
use dacoto\LaravelWizardInstaller\Controllers\InstallServerController;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Routing\Controller;
|
||||||
|
|
||||||
|
class InstallerController extends Controller {
|
||||||
|
public function purchaseCodeIndex() {
|
||||||
|
if (!(new InstallServerController())->check() || !(new InstallFolderController())->check()) {
|
||||||
|
return redirect()->route('LaravelWizardInstaller::install.folders');
|
||||||
|
}
|
||||||
|
return view('vendor.installer.steps.purchase-code');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function checkPurchaseCode(Request $request) {
|
||||||
|
try {
|
||||||
|
$app_url = (string)url('/');
|
||||||
|
$app_url = preg_replace('#^https?://#i', '', $app_url);
|
||||||
|
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt_array($curl, array(
|
||||||
|
CURLOPT_URL => 'https://validator.wrteam.in/eclassify_validator?purchase_code=' . $request->input('purchase_code') . '&domain_url=' . $app_url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_MAXREDIRS => 10,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4
|
||||||
|
));
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
$response = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
if ($response['error']) {
|
||||||
|
return view('installer::steps.purchase-code', ['error' => $response["message"]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvSet::setKey('APPSECRET', $request->input('purchase_code'));
|
||||||
|
EnvSet::save();
|
||||||
|
return redirect()->route('install.php-function.index');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$values = [
|
||||||
|
'purchase_code' => $request->get("purchase_code"),
|
||||||
|
];
|
||||||
|
return view('vendor.installer.steps.purchase-code', ['values' => $values, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phpFunctionIndex() {
|
||||||
|
if (!(new InstallServerController())->check() || !(new InstallFolderController())->check()) {
|
||||||
|
return redirect()->route('LaravelWizardInstaller::install.purchase_code');
|
||||||
|
}
|
||||||
|
return view('vendor.installer.steps.symlink_basedir_check', [
|
||||||
|
'result' => $this->checkSymlink(),
|
||||||
|
'baseDir' =>$this->checkBaseDir()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkSymlink(): bool
|
||||||
|
{
|
||||||
|
return function_exists('symlink');
|
||||||
|
}
|
||||||
|
public function checkBaseDir(): bool
|
||||||
|
{
|
||||||
|
$openBaseDir = ini_get('open_basedir');
|
||||||
|
if ($openBaseDir) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
894
app/Http/Controllers/ItemController.php
Normal file
894
app/Http/Controllers/ItemController.php
Normal file
@@ -0,0 +1,894 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\City;
|
||||||
|
use App\Models\Country;
|
||||||
|
use App\Models\Currency;
|
||||||
|
use App\Models\CustomField;
|
||||||
|
use App\Models\CustomFieldCategory;
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\ItemCustomFieldValue;
|
||||||
|
use App\Models\ItemImages;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\State;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\HelperService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Str;
|
||||||
|
use Throwable;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
|
class ItemController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['advertisement-list', 'advertisement-update', 'advertisement-delete']);
|
||||||
|
$countries = Country::all();
|
||||||
|
$categories = Category::all();
|
||||||
|
|
||||||
|
return view('items.index', compact('countries', 'categories'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($status, Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
$sql = Item::with(['custom_fields', 'category:id,name', 'user:id,name,profile', 'gallery_images', 'featured_items'])->withTrashed();
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
if (! empty($request->filter)) {
|
||||||
|
$filters = json_decode($request->filter, false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
if (is_object($filters) && count((array) $filters) > 0) {
|
||||||
|
// Handle status_not separately if present
|
||||||
|
$hasStatusNot = isset($filters->status_not);
|
||||||
|
$statusNotValue = null;
|
||||||
|
|
||||||
|
if ($hasStatusNot) {
|
||||||
|
$statusNotValue = $filters->status_not;
|
||||||
|
$sql = $sql->where('status', '!=', $statusNotValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build remaining filters object (excluding status_not)
|
||||||
|
$remainingFilters = [];
|
||||||
|
foreach ($filters as $key => $value) {
|
||||||
|
if ($key !== 'status_not') {
|
||||||
|
$remainingFilters[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply remaining filters (status, country, state, city, featured_status, etc.)
|
||||||
|
if (! empty($remainingFilters)) {
|
||||||
|
$sql = $sql->filter((object) $remainingFilters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql = $sql->sort($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
$itemCustomFieldValues = ItemCustomFieldValue::whereIn('item_id', $result->pluck('id'))->get();
|
||||||
|
foreach ($result as $row) {
|
||||||
|
/* Merged ItemCustomFieldValue's data to main data */
|
||||||
|
$itemCustomFieldValue = $itemCustomFieldValues->filter(function ($data) use ($row) {
|
||||||
|
return $data->item_id == $row->id;
|
||||||
|
});
|
||||||
|
$featured_status = $row->featured_items->isNotEmpty() ? 'Featured' : 'Not-Featured';
|
||||||
|
$row->custom_fields = collect($row->custom_fields)->map(function ($customField) use ($itemCustomFieldValue) {
|
||||||
|
$customField['value'] = $itemCustomFieldValue->first(function ($data) use ($customField) {
|
||||||
|
return $data->custom_field_id == $customField->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($customField->type == 'fileinput' && ! empty($customField['value']->value)) {
|
||||||
|
if (! is_array($customField->value)) {
|
||||||
|
$customField['value'] = ! empty($customField->value) ? [url(Storage::url($customField->value))] : [];
|
||||||
|
} else {
|
||||||
|
$customField['value'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $customField;
|
||||||
|
});
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if (count($row->custom_fields) > 0 && Auth::user()->can('advertisement-list')) {
|
||||||
|
// View Custom Field
|
||||||
|
$operate .= BootstrapTableService::button('fa fa-eye', '#', ['editdata', 'btn-light-danger '], ['title' => __('View'), 'data-bs-target' => '#editModal', 'data-bs-toggle' => 'modal']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($row->status !== 'sold out' && Auth::user()->can('advertisement-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('advertisement.approval', $row->id), true, '#editStatusModal', 'edit-status', $row->id);
|
||||||
|
}
|
||||||
|
if (Auth::user()->can('advertisement-update')) {
|
||||||
|
$operate .= BootstrapTableService::button('fa fa-wrench', route('advertisement.edit', $row->id), ['btn', 'btn-light-warning'], ['title' => __('Advertisement Update')]);
|
||||||
|
}
|
||||||
|
if (Auth::user()->can('advertisement-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('advertisement.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['active_status'] = empty($row->deleted_at); // IF deleted_at is empty then status is true else false
|
||||||
|
$tempRow['featured_status'] = $featured_status;
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'ItemController --> show');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateItemApproval(Request $request, $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-update');
|
||||||
|
|
||||||
|
$item = Item::with('user')->withTrashed()->findOrFail($id);
|
||||||
|
|
||||||
|
$data = $request->except(['created_at']);
|
||||||
|
|
||||||
|
// Handle rejected reason
|
||||||
|
$data['rejected_reason'] =
|
||||||
|
in_array($request->status, ['soft rejected', 'permanent rejected'])
|
||||||
|
? $request->rejected_reason
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// ✅ Update created_at ONLY when approved
|
||||||
|
if ($request->status === 'approved') {
|
||||||
|
$data['created_at'] = Carbon::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
$item->update($data);
|
||||||
|
|
||||||
|
$user_token = UserFcmToken::where('user_id', $item->user->id)
|
||||||
|
->pluck('fcm_token')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (!empty($user_token)) {
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$user_token,
|
||||||
|
'About ' . $item->name,
|
||||||
|
'Your Advertisement is ' . ucfirst($request->status),
|
||||||
|
'item-update',
|
||||||
|
['id' => $item->id]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse('Advertisement Status Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'ItemController ->updateItemApproval');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-delete');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$item = Item::with('gallery_images')->withTrashed()->findOrFail($id);
|
||||||
|
foreach ($item->gallery_images as $gallery_image) {
|
||||||
|
FileService::delete($gallery_image->getRawOriginal('image'));
|
||||||
|
}
|
||||||
|
FileService::delete($item->getRawOriginal('image'));
|
||||||
|
|
||||||
|
$item->forceDelete();
|
||||||
|
|
||||||
|
ResponseService::successResponse('Advertisement deleted successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse('Something went wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestedItem()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['advertisement-list', 'advertisement-update', 'advertisement-delete']);
|
||||||
|
$countries = Country::all();
|
||||||
|
$cities = City::all();
|
||||||
|
|
||||||
|
return view('items.requested_item', compact('countries', 'cities'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchState(Request $request)
|
||||||
|
{
|
||||||
|
$countryName = trim($request->query('country_name'));
|
||||||
|
if ($countryName == 'All') {
|
||||||
|
return response()->json(['message' => 'Success', 'data' => []]);
|
||||||
|
}
|
||||||
|
$country = Country::where('name', $countryName)->first();
|
||||||
|
|
||||||
|
if (! $country) {
|
||||||
|
return response()->json(['message' => 'Success', 'data' => []]);
|
||||||
|
}
|
||||||
|
$states = State::where('country_id', $country->id)->get();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Success', 'data' => $states]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchCities(Request $request)
|
||||||
|
{
|
||||||
|
$stateName = trim($request->query('state_name'));
|
||||||
|
if ($stateName == 'All') {
|
||||||
|
return response()->json(['message' => 'Success', 'data' => []]);
|
||||||
|
}
|
||||||
|
$state = State::where('name', $stateName)->first();
|
||||||
|
if (! $state) {
|
||||||
|
return response()->json(['message' => 'Success', 'data' => []]);
|
||||||
|
}
|
||||||
|
$cities = City::where('state_id', $state->id)->get();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Success', 'data' => $cities]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editForm($id)
|
||||||
|
{
|
||||||
|
$item = Item::with(
|
||||||
|
'user:id,name,email,mobile,profile,country_code',
|
||||||
|
'category.custom_fields', // get custom fields from category
|
||||||
|
'gallery_images:id,image,item_id',
|
||||||
|
'featured_items',
|
||||||
|
'favourites',
|
||||||
|
'item_custom_field_values.custom_field',
|
||||||
|
'area',
|
||||||
|
'currency:id,name'
|
||||||
|
)->findOrFail($id);
|
||||||
|
$categories = Category::whereNull('parent_category_id')
|
||||||
|
->with([
|
||||||
|
'custom_fields',
|
||||||
|
'subcategories',
|
||||||
|
'subcategories.custom_fields',
|
||||||
|
'subcategories.subcategories',
|
||||||
|
'subcategories.subcategories.custom_fields',
|
||||||
|
'subcategories.subcategories.subcategories',
|
||||||
|
'subcategories.subcategories.subcategories.custom_fields',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories.custom_fields',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories.subcategories',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories.subcategories.custom_fields',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories.subcategories.subcategories',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories.subcategories.subcategories.custom_fields',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories.subcategories.subcategories.subcategories',
|
||||||
|
'subcategories.subcategories.subcategories.subcategories.subcategories.subcategories.subcategories.custom_fields',
|
||||||
|
])
|
||||||
|
->get();
|
||||||
|
// $categories=[];
|
||||||
|
|
||||||
|
$currencies = Currency::all();
|
||||||
|
|
||||||
|
$all_categories_till_parent = [];
|
||||||
|
|
||||||
|
$categoryId = $item->category_id; // assume it's integer
|
||||||
|
if ($categoryId) {
|
||||||
|
$all_categories_till_parent[] = $categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($categoryId) {
|
||||||
|
$parent = Category::without('translations')->where('id', $categoryId)->value('parent_category_id');
|
||||||
|
if ($parent) {
|
||||||
|
$all_categories_till_parent[] = $parent;
|
||||||
|
$categoryId = $parent;
|
||||||
|
} else {
|
||||||
|
$categoryId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$all_categories_till_parent = array_unique($all_categories_till_parent);
|
||||||
|
|
||||||
|
$customFieldCategories = CustomFieldCategory::with('custom_fields')
|
||||||
|
->whereIn('category_id', $all_categories_till_parent)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$savedValues = ItemCustomFieldValue::where('item_id', $item->id)->get()->keyBy('custom_field_id');
|
||||||
|
$custom_fields = $customFieldCategories->map(function ($relation) use ($savedValues) {
|
||||||
|
$field = $relation->custom_fields;
|
||||||
|
if (! $field) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $savedValues->get($field->id)->value ?? null;
|
||||||
|
|
||||||
|
if ($field->type === 'fileinput') {
|
||||||
|
$field->value = $value ? [url(Storage::url($value))] : [];
|
||||||
|
} else {
|
||||||
|
if (is_array($value)) {
|
||||||
|
if (in_array($field->type, ['textbox', 'number'])) {
|
||||||
|
$field->value = implode(', ', $value);
|
||||||
|
} else {
|
||||||
|
$field->value = $value;
|
||||||
|
}
|
||||||
|
} elseif (is_string($value)) {
|
||||||
|
$decodedValue = json_decode($value, true);
|
||||||
|
if (is_array($decodedValue)) {
|
||||||
|
if (in_array($field->type, ['textbox', 'number'])) {
|
||||||
|
$field->value = implode(', ', $decodedValue);
|
||||||
|
} else {
|
||||||
|
$field->value = $decodedValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$field->value = $decodedValue ?? $value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$field->value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (in_array($field->type, ['dropdown', 'radio'])) {
|
||||||
|
if (is_array($field->value)) {
|
||||||
|
$field->value = count($field->value) > 0 ? (string) $field->value[0] : '';
|
||||||
|
} elseif (is_object($field->value)) {
|
||||||
|
$field->value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $field;
|
||||||
|
})->filter();
|
||||||
|
$countries = Country::all();
|
||||||
|
$selected_category = [$item->category_id];
|
||||||
|
|
||||||
|
return view('items.update', compact('item', 'categories', 'custom_fields', 'selected_category', 'countries', 'currencies'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'slug' => 'nullable|regex:/^[a-z0-9-]+$/',
|
||||||
|
'description' => 'nullable|string',
|
||||||
|
'latitude' => 'nullable',
|
||||||
|
'longitude' => 'nullable',
|
||||||
|
'address' => 'nullable',
|
||||||
|
'contact' => 'nullable',
|
||||||
|
'image' => 'nullable|mimes:jpeg,jpg,png|max:7168',
|
||||||
|
'custom_fields' => 'nullable',
|
||||||
|
'custom_field_files' => 'nullable|array',
|
||||||
|
'custom_field_files.*' => 'nullable|mimes:jpeg,png,jpg,pdf,doc|max:7168',
|
||||||
|
'gallery_images' => 'nullable|array',
|
||||||
|
'admin_edit_reason' => 'required|string|max:1000',
|
||||||
|
'currency_id' => 'nullable|exists:currencies,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// dd($request->all());
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return back()->withErrors($validator)->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
// try {
|
||||||
|
$item = Item::findOrFail($id);
|
||||||
|
|
||||||
|
$category = Category::findOrFail($request->category_id);
|
||||||
|
$isJobCategory = $category->is_job_category;
|
||||||
|
$isPriceOptional = $category->price_optional;
|
||||||
|
|
||||||
|
if ($isJobCategory || $isPriceOptional) {
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'min_salary' => 'nullable|numeric|min:0',
|
||||||
|
'max_salary' => 'nullable|numeric|gte:min_salary',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'price' => 'required|numeric|min:0',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$customFieldCategories = CustomFieldCategory::with('custom_fields')
|
||||||
|
->where('category_id', $request->category_id)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$customFieldErrors = [];
|
||||||
|
foreach ($customFieldCategories as $relation) {
|
||||||
|
$field = $relation->custom_fields;
|
||||||
|
if (empty($field) || $field->required != 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$fieldId = $field->id;
|
||||||
|
$fieldLabel = $field->name;
|
||||||
|
|
||||||
|
if (in_array($field->type, ['textbox', 'number', 'dropdown', 'radio'])) {
|
||||||
|
if (empty($request->input("custom_fields.$fieldId"))) {
|
||||||
|
$customFieldErrors["custom_fields.$fieldId"] = "The $fieldLabel field is required.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field->type === 'checkbox') {
|
||||||
|
if (! is_array($request->input("custom_fields.$fieldId")) || empty($request->input("custom_fields.$fieldId"))) {
|
||||||
|
$customFieldErrors["custom_fields.$fieldId"] = "The $fieldLabel field is required.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field->type === 'fileinput') {
|
||||||
|
$existing = ItemCustomFieldValue::where([
|
||||||
|
'item_id' => $id,
|
||||||
|
'custom_field_id' => $fieldId,
|
||||||
|
])->first();
|
||||||
|
|
||||||
|
if (! $request->hasFile("custom_field_files.$fieldId") && empty($existing?->value)) {
|
||||||
|
$customFieldErrors["custom_field_files.$fieldId"] = "The $fieldLabel file is required.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! empty($customFieldErrors)) {
|
||||||
|
return back()->withErrors($customFieldErrors)->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array_merge($request->all(), [
|
||||||
|
'is_edited_by_admin' => 1,
|
||||||
|
'admin_edit_reason' => $request->admin_edit_reason,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// $data['slug'] = $uniqueSlug;
|
||||||
|
// Address data from map selection
|
||||||
|
$data['address'] = $request->input('address') ?? $request->input('address_input') ?? '';
|
||||||
|
$data['country'] = $request->input('country_input') ?? '';
|
||||||
|
$data['state'] = $request->input('state_input') ?? '';
|
||||||
|
$data['city'] = $request->input('city_input') ?? '';
|
||||||
|
$data['latitude'] = $request->input('latitude');
|
||||||
|
$data['longitude'] = $request->input('longitude');
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::compressAndReplace($request->file('image'), 'item_images', $item->getRawOriginal('image'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldCategoryId = $item->category_id;
|
||||||
|
$newCategoryId = $request->category_id;
|
||||||
|
|
||||||
|
$isCategoryChanged = $oldCategoryId != $newCategoryId;
|
||||||
|
$oldCustomFieldValues = ItemCustomFieldValue::where('item_id', $item->id)->get();
|
||||||
|
foreach ($oldCustomFieldValues as $fieldValue) {
|
||||||
|
$customField = CustomField::find($fieldValue->custom_field_id);
|
||||||
|
if ($customField && $customField->type === 'file') {
|
||||||
|
$rawFilePath = $fieldValue->getRawOriginal('value');
|
||||||
|
if ($customField && $customField->type === 'file' && ! empty($rawFilePath)) {
|
||||||
|
FileService::delete($rawFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($isCategoryChanged) {
|
||||||
|
ItemCustomFieldValue::where('item_id', $item->id)->delete();
|
||||||
|
}
|
||||||
|
$item->update($data);
|
||||||
|
if ($request->custom_fields) {
|
||||||
|
foreach ($request->custom_fields as $key => $custom_field) {
|
||||||
|
$value = is_array($custom_field) ? $custom_field : [$custom_field];
|
||||||
|
ItemCustomFieldValue::updateOrCreate(
|
||||||
|
[
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'custom_field_id' => $key,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'value' => json_encode($value, JSON_THROW_ON_ERROR),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($request->hasFile('custom_field_files')) {
|
||||||
|
$itemCustomFieldValues = [];
|
||||||
|
foreach ($request->file('custom_field_files') as $key => $file) {
|
||||||
|
$value = ItemCustomFieldValue::where(['item_id' => $item->id, 'custom_field_id' => $key])->first();
|
||||||
|
|
||||||
|
$path = $value
|
||||||
|
? FileService::replace($file, 'custom_fields_files', $value->getRawOriginal('value'))
|
||||||
|
: FileService::upload($file, 'custom_fields_files');
|
||||||
|
|
||||||
|
$itemCustomFieldValues[] = [
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'custom_field_id' => $key,
|
||||||
|
'value' => $path,
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($itemCustomFieldValues)) {
|
||||||
|
ItemCustomFieldValue::upsert($itemCustomFieldValues, ['item_id', 'custom_field_id'], ['value', 'updated_at']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($request->hasFile('gallery_images')) {
|
||||||
|
$galleryImages = [];
|
||||||
|
foreach ($request->file('gallery_images') as $file) {
|
||||||
|
$galleryImages[] = [
|
||||||
|
'image' => FileService::compressAndUpload($file, 'item_images', true),
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'created_at' => time(),
|
||||||
|
'updated_at' => time(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (count($galleryImages) > 0) {
|
||||||
|
ItemImages::insert($galleryImages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom field files
|
||||||
|
foreach ($request->allFiles() as $key => $file) {
|
||||||
|
if (Str::startsWith($key, 'custom_fields.')) {
|
||||||
|
$customFieldId = Str::after($key, 'custom_fields.');
|
||||||
|
$value = ItemCustomFieldValue::where(['item_id' => $item->id, 'custom_field_id' => $customFieldId])->first();
|
||||||
|
if ($value) {
|
||||||
|
$filePath = FileService::replace($file, 'custom_fields_files', $value->getRawOriginal('value'));
|
||||||
|
} else {
|
||||||
|
$filePath = FileService::upload($file, 'custom_fields_files');
|
||||||
|
}
|
||||||
|
ItemCustomFieldValue::updateOrCreate(
|
||||||
|
['item_id' => $item->id, 'custom_field_id' => $customFieldId],
|
||||||
|
['value' => $filePath, 'updated_at' => now()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! empty($request->delete_item_image_id)) {
|
||||||
|
// dd($request->delete_item_image_id);
|
||||||
|
$itemImageIds = $request->delete_item_image_id;
|
||||||
|
foreach (ItemImages::whereIn('id', $itemImageIds)->get() as $itemImage) {
|
||||||
|
FileService::delete($itemImage->getRawOriginal('image'));
|
||||||
|
$itemImage->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
$user_token = UserFcmToken::where('user_id', $item->user->id)->pluck('fcm_token')->toArray();
|
||||||
|
if (! empty($user_token)) {
|
||||||
|
NotificationService::sendFcmNotification($user_token, 'About ' . $item->name, 'Your Advertisement is edited by admin', 'item-edit', ['id' => $request->id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successRedirectResponse('Advertisement Updated Successfully', route('advertisement.index'));
|
||||||
|
// } catch (Throwable $th) {
|
||||||
|
// DB::rollBack();
|
||||||
|
// report($th);
|
||||||
|
|
||||||
|
// return redirect()->back()->with('error', 'An error occurred while updating the Advertisement.');
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCustomFields(Request $request, $categoryId)
|
||||||
|
{
|
||||||
|
|
||||||
|
$categoryIds = $this->getParentCategoryIds($categoryId);
|
||||||
|
$category = Category::find($categoryId);
|
||||||
|
$customFields = CustomField::with('translations')
|
||||||
|
->whereHas('custom_field_category', function ($q) use ($categoryIds) {
|
||||||
|
$q->whereIn('category_id', $categoryIds);
|
||||||
|
})
|
||||||
|
->where('status', 1)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'fields' => $customFields,
|
||||||
|
'is_job_category' => $category->is_job_category,
|
||||||
|
'price_optional' => $category->price_optional,
|
||||||
|
'category_ids' => $categoryIds,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getParentCategoryIds($categoryId, &$ids = [])
|
||||||
|
{
|
||||||
|
$category = Category::find($categoryId);
|
||||||
|
|
||||||
|
if ($category) {
|
||||||
|
$ids[] = $category->id;
|
||||||
|
if ($category->parent_category_id) {
|
||||||
|
$this->getParentCategoryIds($category->parent_category_id, $ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_reverse($ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['advertisement-create']);
|
||||||
|
|
||||||
|
// No need to load categories here, they'll be loaded via AJAX
|
||||||
|
$countries = Country::all();
|
||||||
|
$adminUserEmail = Setting::getValue('admin_user_email', '');
|
||||||
|
$adminUserPassword = Setting::getValue('admin_user_password', '');
|
||||||
|
$currencies = Currency::all();
|
||||||
|
// $states = State::get();
|
||||||
|
// $cities = City::get();
|
||||||
|
|
||||||
|
return view('items.create', compact('countries', 'adminUserEmail', 'adminUserPassword', 'currencies'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParentCategories(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-create');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$page = $request->input('page', 1);
|
||||||
|
$perPage = $request->input('per_page', 10);
|
||||||
|
|
||||||
|
$categories = Category::whereNull('parent_category_id')
|
||||||
|
->where('status', 1)
|
||||||
|
->orderBy('sequence', 'ASC')
|
||||||
|
->withCount(['subcategories' => function ($q) {
|
||||||
|
$q->where('status', 1);
|
||||||
|
}])
|
||||||
|
->skip(($page - 1) * $perPage)
|
||||||
|
->take($perPage + 1)
|
||||||
|
->get(['id', 'name', 'status', 'image']);
|
||||||
|
|
||||||
|
$hasMore = $categories->count() > $perPage;
|
||||||
|
$categories = $categories->take($perPage);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Success',
|
||||||
|
'data' => $categories,
|
||||||
|
'has_more' => $hasMore,
|
||||||
|
'current_page' => $page,
|
||||||
|
]);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'ItemController -> getParentCategories');
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Error loading categories'], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubCategories(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-create');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'category_id' => 'required|integer',
|
||||||
|
'page' => 'nullable|integer|min:1',
|
||||||
|
'per_page' => 'nullable|integer|min:1|max:50',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return response()->json(['message' => 'Validation error', 'errors' => $validator->errors()], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$page = $request->input('page', 1);
|
||||||
|
$perPage = $request->input('per_page', 10);
|
||||||
|
|
||||||
|
$subcategories = Category::where('parent_category_id', $request->category_id)
|
||||||
|
->where('status', 1)
|
||||||
|
->orderBy('sequence', 'ASC')
|
||||||
|
->withCount(['subcategories' => function ($q) {
|
||||||
|
$q->where('status', 1);
|
||||||
|
}])
|
||||||
|
->skip(($page - 1) * $perPage)
|
||||||
|
->take($perPage + 1)
|
||||||
|
->get(['id', 'name', 'parent_category_id', 'status', 'image']);
|
||||||
|
|
||||||
|
$hasMore = $subcategories->count() > $perPage;
|
||||||
|
$subcategories = $subcategories->take($perPage);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Success',
|
||||||
|
'data' => $subcategories,
|
||||||
|
'has_more' => $hasMore,
|
||||||
|
'current_page' => $page,
|
||||||
|
]);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'ItemController -> getSubCategories');
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Error loading subcategories'], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-create');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'slug' => 'nullable|regex:/^[a-z0-9-]+$/',
|
||||||
|
'description' => 'required|string',
|
||||||
|
'latitude' => 'required',
|
||||||
|
'longitude' => 'required',
|
||||||
|
'address' => 'nullable',
|
||||||
|
'contact' => 'nullable',
|
||||||
|
'image' => 'required|mimes:jpeg,jpg,png|max:7168',
|
||||||
|
'custom_fields' => 'nullable',
|
||||||
|
'custom_field_files' => 'nullable|array',
|
||||||
|
'custom_field_files.*' => 'nullable|mimes:jpeg,png,jpg,pdf,doc|max:7168',
|
||||||
|
'gallery_images' => 'nullable|array',
|
||||||
|
'gallery_images.*' => 'nullable|mimes:jpeg,png,jpg|max:7168',
|
||||||
|
'video_link' => 'nullable|url',
|
||||||
|
'category_id' => 'required|integer',
|
||||||
|
'currency_id' => 'nullable|integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
$errorMessage = $validator->errors()->first();
|
||||||
|
|
||||||
|
return ResponseService::errorRedirectWithToast($errorMessage, $request->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
try {
|
||||||
|
$category = Category::findOrFail($request->category_id);
|
||||||
|
$isJobCategory = $category->is_job_category;
|
||||||
|
$isPriceOptional = $category->price_optional;
|
||||||
|
|
||||||
|
if ($isJobCategory || $isPriceOptional) {
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'min_salary' => 'nullable|numeric|min:0',
|
||||||
|
'max_salary' => 'nullable|numeric|gte:min_salary',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'price' => 'required|numeric|min:0',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
DB::rollBack();
|
||||||
|
$errorMessage = $validator->errors()->first();
|
||||||
|
|
||||||
|
return ResponseService::errorRedirectWithToast($errorMessage, $request->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
$customFieldCategories = CustomFieldCategory::with('custom_fields')
|
||||||
|
->where('category_id', $request->category_id)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$customFieldErrors = [];
|
||||||
|
foreach ($customFieldCategories as $relation) {
|
||||||
|
$field = $relation->custom_fields;
|
||||||
|
if (empty($field) || $field->required != 1 || $field->status != 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldId = $field->id;
|
||||||
|
$fieldLabel = $field->name;
|
||||||
|
|
||||||
|
if (in_array($field->type, ['textbox', 'number', 'dropdown', 'radio'])) {
|
||||||
|
if (empty($request->input("custom_fields.$fieldId"))) {
|
||||||
|
$customFieldErrors["custom_fields.$fieldId"] = "The $fieldLabel field is required.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field->type === 'checkbox') {
|
||||||
|
if (! is_array($request->input("custom_fields.$fieldId")) || empty($request->input("custom_fields.$fieldId"))) {
|
||||||
|
$customFieldErrors["custom_fields.$fieldId"] = "The $fieldLabel field is required.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field->type === 'fileinput') {
|
||||||
|
if (! $request->hasFile("custom_field_files.$fieldId")) {
|
||||||
|
$customFieldErrors["custom_field_files.$fieldId"] = "The $fieldLabel file is required.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($customFieldErrors)) {
|
||||||
|
DB::rollBack();
|
||||||
|
$errorMessage = reset($customFieldErrors); // Get first error message
|
||||||
|
|
||||||
|
return ResponseService::errorRedirectWithToast($errorMessage, $request->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
$slug = trim($request->input('slug') ?? '');
|
||||||
|
$slug = preg_replace('/[^a-z0-9]+/i', '-', strtolower($slug));
|
||||||
|
$slug = trim($slug, '-');
|
||||||
|
if (empty($slug)) {
|
||||||
|
$slug = HelperService::generateRandomSlug();
|
||||||
|
}
|
||||||
|
$uniqueSlug = HelperService::generateUniqueSlug(new Item, $slug);
|
||||||
|
|
||||||
|
$userEmail = Setting::where('name', 'admin_user_email')->value('value');
|
||||||
|
$userPassword = Setting::where('name', 'admin_user_password')->value('value');
|
||||||
|
if (empty($userEmail) && empty($userPassword)) {
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
return ResponseService::errorRedirectWithToast('Add user details in the setting first.', $request->all());
|
||||||
|
}
|
||||||
|
$user = User::withTrashed()->where('email', $userEmail)->first();
|
||||||
|
|
||||||
|
if (! $user || $user->trashed()) {
|
||||||
|
DB::rollBack();
|
||||||
|
|
||||||
|
return ResponseService::errorRedirectWithToast('User not found.', $request->all());
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $request->name,
|
||||||
|
'slug' => $uniqueSlug,
|
||||||
|
'description' => $request->description,
|
||||||
|
'address' => $request->input('address') ?? $request->input('address_input') ?? '',
|
||||||
|
'country' => $request->input('country_input') ?? '',
|
||||||
|
'state' => $request->input('state_input') ?? '',
|
||||||
|
'city' => $request->input('city_input') ?? '',
|
||||||
|
'latitude' => $request->input('latitude'),
|
||||||
|
'longitude' => $request->input('longitude'),
|
||||||
|
'contact' => $request->contact ?? $user->contact,
|
||||||
|
'category_id' => $request->category_id,
|
||||||
|
'price' => $request->price,
|
||||||
|
'min_salary' => $request->min_salary,
|
||||||
|
'max_salary' => $request->max_salary,
|
||||||
|
'video_link' => $request->video_link,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'status' => 'approved',
|
||||||
|
'active' => 'active',
|
||||||
|
'currency_id' => $request->currency_id ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::compressAndUpload($request->file('image'), 'item_images', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = Item::create($data);
|
||||||
|
|
||||||
|
if ($request->custom_fields) {
|
||||||
|
foreach ($request->custom_fields as $key => $custom_field) {
|
||||||
|
$value = is_array($custom_field) ? $custom_field : [$custom_field];
|
||||||
|
ItemCustomFieldValue::create([
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'custom_field_id' => $key,
|
||||||
|
'value' => json_encode($value, JSON_THROW_ON_ERROR),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('custom_field_files')) {
|
||||||
|
foreach ($request->file('custom_field_files') as $key => $file) {
|
||||||
|
$path = FileService::upload($file, 'custom_fields_files');
|
||||||
|
ItemCustomFieldValue::create([
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'custom_field_id' => $key,
|
||||||
|
'value' => $path,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('gallery_images')) {
|
||||||
|
$galleryImages = [];
|
||||||
|
foreach ($request->file('gallery_images') as $file) {
|
||||||
|
$galleryImages[] = [
|
||||||
|
'image' => FileService::compressAndUpload($file, 'item_images', true),
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'created_at' => time(),
|
||||||
|
'updated_at' => time(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (count($galleryImages) > 0) {
|
||||||
|
ItemImages::insert($galleryImages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom field files from direct custom_fields input
|
||||||
|
foreach ($request->allFiles() as $key => $file) {
|
||||||
|
if (Str::startsWith($key, 'custom_fields.')) {
|
||||||
|
$customFieldId = Str::after($key, 'custom_fields.');
|
||||||
|
$filePath = FileService::upload($file, 'custom_fields_files');
|
||||||
|
ItemCustomFieldValue::create([
|
||||||
|
'item_id' => $item->id,
|
||||||
|
'custom_field_id' => $customFieldId,
|
||||||
|
'value' => $filePath,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successRedirectResponse('Advertisement Created Successfully', route('advertisement.index'));
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th, 'ItemController -> store', 'An error occurred while creating the Advertisement.', false);
|
||||||
|
|
||||||
|
return ResponseService::errorRedirectWithToast('An error occurred while creating the Advertisement.', $request->all());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
474
app/Http/Controllers/LanguageController.php
Normal file
474
app/Http/Controllers/LanguageController.php
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Language;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use File;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class LanguageController extends Controller
|
||||||
|
{
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->uploadFolder = 'language';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required',
|
||||||
|
'name_in_english' => 'required|regex:/^[\pL\s]+$/u',
|
||||||
|
'code' => 'required|unique:languages,code',
|
||||||
|
'rtl' => 'nullable',
|
||||||
|
'image' => 'required|mimes:jpeg,png,jpg,svg|max:7168',
|
||||||
|
'country_code' => 'nullable',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$data = $request->all();
|
||||||
|
$data['rtl'] = $request->rtl == 'on';
|
||||||
|
|
||||||
|
if ($request->hasFile('panel_file')) {
|
||||||
|
$data['panel_file'] = FileService::uploadLanguageFile($request->file('panel_file'), $request->code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('app_file')) {
|
||||||
|
$data['app_file'] = FileService::uploadLanguageFile($request->file('app_file'), $request->code . '_app');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('web_file')) {
|
||||||
|
$data['web_file'] = FileService::uploadLanguageFile($request->file('web_file'), $request->code . '_web');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::upload($request->file('image'), $this->uploadFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
Language::create($data);
|
||||||
|
CachingService::removeCache(config('constants.CACHE.LANGUAGE'));
|
||||||
|
ResponseService::successResponse('Language Successfully Added');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th, 'Language Controller -> Store');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = Language::orderBy($sort, $order);
|
||||||
|
|
||||||
|
if (! empty($_GET['search'])) {
|
||||||
|
$search = $_GET['search'];
|
||||||
|
$sql->where('id', 'LIKE', "%$search%")->orwhere('code', 'LIKE', "%$search%")->orwhere('name', 'LIKE', "%$search%");
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['rtl_text'] = ($row->rtl == 1) ? 'Yes' : 'No';
|
||||||
|
$operate = '';
|
||||||
|
if ($row->code != 'en') {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('language.update', $row->id), true);
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('language.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$dropdownItems = [
|
||||||
|
[
|
||||||
|
'icon' => '',
|
||||||
|
'url' => route('languageedit', [$row->id, 'type' => 'panel']),
|
||||||
|
'text' => trans('Edit Panel Json'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'icon' => '',
|
||||||
|
'url' => route('languageedit', [$row->id, 'type' => 'web']),
|
||||||
|
'text' => trans('Edit Web Json'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'icon' => '',
|
||||||
|
'url' => route('languageedit', [$row->id, 'type' => 'app']),
|
||||||
|
'text' => trans('Edit App Json'),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$operate .= BootstrapTableService::dropdown('fas fa-ellipsis-v', $dropdownItems);
|
||||||
|
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required',
|
||||||
|
'name_in_english' => 'required|regex:/^[\pL\s]+$/u',
|
||||||
|
'code' => 'required|unique:languages,code,' . $id,
|
||||||
|
'rtl' => 'nullable|boolean',
|
||||||
|
'app_file' => 'nullable|mimes:json',
|
||||||
|
'panel_file' => 'nullable|mimes:json',
|
||||||
|
'web_file' => 'nullable|mimes:json',
|
||||||
|
'image' => 'nullable|mimes:jpeg,png,jpg,svg|max:7168',
|
||||||
|
'country_code' => 'nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$language = Language::findOrFail($id);
|
||||||
|
|
||||||
|
$oldCode = $language->code;
|
||||||
|
$newCode = $request->input('code');
|
||||||
|
$defaultCode = Setting::where('name', 'default_language')->value('value');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$data = $request->only([
|
||||||
|
'name',
|
||||||
|
'name_in_english',
|
||||||
|
'code',
|
||||||
|
'country_code',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Preserve RTL unless changed
|
||||||
|
if ($request->has('rtl')) {
|
||||||
|
$data['rtl'] = (bool) $request->rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($data['is_default']);
|
||||||
|
|
||||||
|
if ($request->hasFile('panel_file')) {
|
||||||
|
$data['panel_file'] = FileService::uploadLanguageFile(
|
||||||
|
$request->file('panel_file'),
|
||||||
|
$oldCode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('app_file')) {
|
||||||
|
$data['app_file'] = FileService::uploadLanguageFile(
|
||||||
|
$request->file('app_file'),
|
||||||
|
$oldCode . '_app'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('web_file')) {
|
||||||
|
$data['web_file'] = FileService::uploadLanguageFile(
|
||||||
|
$request->file('web_file'),
|
||||||
|
$oldCode . '_web'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::replace(
|
||||||
|
$request->file('image'),
|
||||||
|
$this->uploadFolder,
|
||||||
|
$language->getRawOriginal('image')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($oldCode !== $newCode) {
|
||||||
|
FileService::renameLanguageFiles($oldCode, $newCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($defaultCode === $oldCode) {
|
||||||
|
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['name' => 'default_language'],
|
||||||
|
['value' => $newCode, 'type' => 'string']
|
||||||
|
);
|
||||||
|
|
||||||
|
Session::forget('locale');
|
||||||
|
Session::put('locale', $newCode);
|
||||||
|
Session::save();
|
||||||
|
|
||||||
|
app()->setLocale($newCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Session::get('locale') === $oldCode) {
|
||||||
|
Session::put('locale', $newCode);
|
||||||
|
app()->setLocale($newCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
$language->update($data);
|
||||||
|
|
||||||
|
CachingService::removeCache(config('constants.CACHE.LANGUAGE'));
|
||||||
|
|
||||||
|
return ResponseService::successResponse('Language Updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Language Controller --> update');
|
||||||
|
return ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// if (!has_permissions('delete', 'property')) {
|
||||||
|
// return redirect()->back()->with('error', PERMISSION_ERROR_MSG);
|
||||||
|
// }
|
||||||
|
$language = Language::findOrFail($id);
|
||||||
|
$setting = \DB::table('settings')->where('name', 'default_language')->first();
|
||||||
|
if ($language->code === $setting->value) {
|
||||||
|
ResponseService::errorResponse('You can not delete default language');
|
||||||
|
}
|
||||||
|
|
||||||
|
$language->delete();
|
||||||
|
|
||||||
|
FileService::deleteLanguageFile($language->app_file);
|
||||||
|
FileService::deleteLanguageFile($language->panel_file);
|
||||||
|
FileService::deleteLanguageFile($language->web_file);
|
||||||
|
FileService::delete($language->getRawOriginal('image'));
|
||||||
|
CachingService::removeCache(config('constants.CACHE.LANGUAGE'));
|
||||||
|
|
||||||
|
ResponseService::successResponse('Language Deleted successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th, 'Language Controller --> Destroy');
|
||||||
|
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLanguage($languageCode)
|
||||||
|
{
|
||||||
|
$language = Language::where('code', $languageCode)->firstOrFail();
|
||||||
|
|
||||||
|
Session::put('locale', $language->code);
|
||||||
|
Session::put('language', $language);
|
||||||
|
app()->setLocale($language->code);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function setDefaultLanguage(Request $request)
|
||||||
|
// {
|
||||||
|
// ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
|
||||||
|
// $request->validate([
|
||||||
|
// 'default_language' => 'required|exists:languages,code',
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// // Save default globally
|
||||||
|
// Setting::updateOrCreate(
|
||||||
|
// ['name' => 'default_language'],
|
||||||
|
// ['value' => $request->default_language, 'type' => 'string']
|
||||||
|
// );
|
||||||
|
// $language = Language::where('code', $request->default_language)->firstOrFail();
|
||||||
|
// Cache::forget('global_default_language');
|
||||||
|
// // Update current session too
|
||||||
|
// Session::put('locale', $request->default_language);
|
||||||
|
// Session::put('language', $language);
|
||||||
|
// Session::put('Default_langauge', $request->default_language);
|
||||||
|
// app()->setLocale($request->default_language);
|
||||||
|
|
||||||
|
// return redirect()->back()
|
||||||
|
// ->with('success', __('Default language updated successfully.'));
|
||||||
|
// }
|
||||||
|
public function setDefaultLanguage(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'default_language' => 'required|exists:languages,code',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 1. Save globally in the database
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['name' => 'default_language'],
|
||||||
|
['value' => $request->default_language, 'type' => 'string']
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. CRITICAL: Clear the Middleware cache
|
||||||
|
Cache::forget('global_default_language');
|
||||||
|
|
||||||
|
// 3. Update current user's session so the UI reflects the change immediately
|
||||||
|
$language = Language::where('code', $request->default_language)->firstOrFail();
|
||||||
|
|
||||||
|
Session::put('locale', $request->default_language);
|
||||||
|
Session::put('language', $language);
|
||||||
|
|
||||||
|
// Clear the CachingService cache if it has one (e.g., system_settings)
|
||||||
|
// Cache::forget('system_settings');
|
||||||
|
|
||||||
|
app()->setLocale($request->default_language);
|
||||||
|
|
||||||
|
return redirect()->back()
|
||||||
|
->with('success', __('Default language updated successfully.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function editlanguage(Request $request, $id, $type)
|
||||||
|
{
|
||||||
|
$language = Language::findOrFail($id);
|
||||||
|
$languageCode = $language->code ?? 'en';
|
||||||
|
// $name = $type;
|
||||||
|
|
||||||
|
if ($type == 'panel') {
|
||||||
|
$fileName = $language->panel_file ?: "{$languageCode}.json";
|
||||||
|
$defaultFile = base_path('resources/lang/en.json');
|
||||||
|
} elseif ($type == 'web') {
|
||||||
|
$fileName = $language->web_file ?: "{$languageCode}_web.json";
|
||||||
|
$defaultFile = base_path('resources/lang/en_web.json');
|
||||||
|
} elseif ($type == 'app') {
|
||||||
|
$fileName = $language->app_file ?: "{$languageCode}_app.json";
|
||||||
|
$defaultFile = base_path('resources/lang/en_app.json');
|
||||||
|
} else {
|
||||||
|
$fileName = 'en.json';
|
||||||
|
$defaultFile = base_path('resources/lang/en.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
$jsonFile = base_path("resources/lang/{$fileName}");
|
||||||
|
|
||||||
|
if (! File::exists($jsonFile)) {
|
||||||
|
if (File::exists($defaultFile)) {
|
||||||
|
$defaultContent = File::get($defaultFile);
|
||||||
|
} else {
|
||||||
|
$defaultContent = json_encode([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
File::put($jsonFile, $defaultContent);
|
||||||
|
|
||||||
|
if ($type == 'panel') {
|
||||||
|
$language->panel_file = $fileName;
|
||||||
|
} elseif ($type == 'web') {
|
||||||
|
$language->web_file = $fileName;
|
||||||
|
} elseif ($type == 'app') {
|
||||||
|
$language->app_file = $fileName;
|
||||||
|
}
|
||||||
|
$language->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$jsonContent = File::get($jsonFile);
|
||||||
|
|
||||||
|
$enContent = File::exists($defaultFile) ? json_decode(File::get($defaultFile), true) : [];
|
||||||
|
$targetContent = File::exists($jsonFile) ? json_decode(File::get($jsonFile), true) : [];
|
||||||
|
|
||||||
|
foreach ($enContent as $key => $value) {
|
||||||
|
if (! array_key_exists($key, $targetContent)) {
|
||||||
|
$targetContent[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File::put($jsonFile, json_encode($targetContent, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||||
|
$enLabels = json_decode($jsonContent, true);
|
||||||
|
|
||||||
|
return view('settings.languageedit', compact('enLabels', 'language', 'type'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatelanguage(Request $request, $id, $type)
|
||||||
|
{
|
||||||
|
$language = Language::findOrFail($id);
|
||||||
|
|
||||||
|
if ($type == 'panel') {
|
||||||
|
$jsonFile = base_path('resources/lang/' . $language->panel_file);
|
||||||
|
} elseif ($type == 'web') {
|
||||||
|
$jsonFile = base_path('resources/lang/' . $language->web_file);
|
||||||
|
} elseif ($type == 'app') {
|
||||||
|
$jsonFile = base_path('resources/lang/' . $language->app_file);
|
||||||
|
} else {
|
||||||
|
$jsonFile = base_path('resources/lang/en.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
$directory = dirname($jsonFile);
|
||||||
|
if (! File::exists($directory)) {
|
||||||
|
File::makeDirectory($directory, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! File::exists($jsonFile)) {
|
||||||
|
$defaultContent = [];
|
||||||
|
File::put($jsonFile, json_encode($defaultContent, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
$jsonContent = File::get($jsonFile);
|
||||||
|
$enLabels = json_decode($jsonContent, true);
|
||||||
|
|
||||||
|
$updatedLabels = $request->input('values');
|
||||||
|
$keys = array_keys($enLabels);
|
||||||
|
foreach ($keys as $index => $key) {
|
||||||
|
if (isset($updatedLabels[$index])) {
|
||||||
|
$enLabels[$key] = $updatedLabels[$index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File::put($jsonFile, json_encode($enLabels, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||||
|
|
||||||
|
ResponseService::successResponse('Json File updated successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function downloadJson($id, $type)
|
||||||
|
{
|
||||||
|
$language = Language::findOrFail($id);
|
||||||
|
$languageCode = $language->code ?? 'en';
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'panel':
|
||||||
|
$fileName = $language->panel_file ?: "{$languageCode}.json";
|
||||||
|
$defaultFile = base_path('resources/lang/en.json');
|
||||||
|
break;
|
||||||
|
case 'web':
|
||||||
|
$fileName = $language->web_file ?: "{$languageCode}_web.json";
|
||||||
|
$defaultFile = base_path('resources/lang/en_web.json');
|
||||||
|
break;
|
||||||
|
case 'app':
|
||||||
|
$fileName = $language->app_file ?: "{$languageCode}_app.json";
|
||||||
|
$defaultFile = base_path('resources/lang/en_app.json');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$jsonFile = base_path("resources/lang/{$fileName}");
|
||||||
|
|
||||||
|
// If file does not exist, create it from default
|
||||||
|
if (! File::exists($jsonFile)) {
|
||||||
|
$defaultContent = File::exists($defaultFile)
|
||||||
|
? File::get($defaultFile)
|
||||||
|
: json_encode([], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||||
|
|
||||||
|
File::put($jsonFile, $defaultContent);
|
||||||
|
|
||||||
|
// Save the file path in DB if not already set
|
||||||
|
if ($type == 'panel') {
|
||||||
|
$language->panel_file = $fileName;
|
||||||
|
} elseif ($type == 'web') {
|
||||||
|
$language->web_file = $fileName;
|
||||||
|
} elseif ($type == 'app') {
|
||||||
|
$language->app_file = $fileName;
|
||||||
|
}
|
||||||
|
$language->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! File::exists($jsonFile)) {
|
||||||
|
abort(404, 'File not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->download($jsonFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
187
app/Http/Controllers/NotificationController.php
Normal file
187
app/Http/Controllers/NotificationController.php
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Jobs\SendFcmBatchJob;
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\Notifications;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->uploadFolder = "notification";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['notification-list', 'notification-create', 'notification-update', 'notification-delete']);
|
||||||
|
$item_list = Item::where('status', 'approved')->getNonExpiredItems()->get();
|
||||||
|
return view('notification.index', compact('item_list'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('notification-create');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'file' => 'image|mimes:jpeg,png,jpg',
|
||||||
|
'send_to' => 'required|in:all,selected',
|
||||||
|
'user_id' => 'required_if:send_to,selected',
|
||||||
|
'title' => 'required',
|
||||||
|
'message' => 'required',
|
||||||
|
], [
|
||||||
|
'user_id.required_if' => __("Please select at least one user")
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$get_fcm_key = CachingService::getSystemSettings('fcm_key');
|
||||||
|
if (!empty($get_fcm_key->data)) {
|
||||||
|
ResponseService::errorResponse('Server FCM Key Is Missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
$notification = Notifications::create([
|
||||||
|
...$request->all(),
|
||||||
|
'image' => $request->hasFile('file')
|
||||||
|
? FileService::compressAndUpload($request->file('file'), $this->uploadFolder)
|
||||||
|
: '',
|
||||||
|
'user_id' => $request->send_to == "selected" ? $request->user_id : ''
|
||||||
|
]);
|
||||||
|
|
||||||
|
$customBodyFields = [
|
||||||
|
'notification_id' => $notification->id, // Add this line
|
||||||
|
'image' => $notification->image,
|
||||||
|
'item_id' => $notification->item_id,
|
||||||
|
];
|
||||||
|
$sendToAll = $request->send_to == 'all';
|
||||||
|
$userIds = $request->send_to == 'selected' ? explode(',', $request->user_id) : [];
|
||||||
|
$executeJob = function () use ($request, $customBodyFields, $sendToAll, $userIds) {
|
||||||
|
try {
|
||||||
|
$job = new SendFcmBatchJob(
|
||||||
|
$request->title,
|
||||||
|
$request->message,
|
||||||
|
'notification',
|
||||||
|
$customBodyFields,
|
||||||
|
$sendToAll,
|
||||||
|
$userIds
|
||||||
|
);
|
||||||
|
|
||||||
|
$job->handle();
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error('Background notification job failed', [
|
||||||
|
'message' => $th->getMessage(),
|
||||||
|
'file' => $th->getFile(),
|
||||||
|
'line' => $th->getLine(),
|
||||||
|
'trace' => $th->getTraceAsString()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ignore_user_abort(true);
|
||||||
|
set_time_limit(0);
|
||||||
|
response()->json([
|
||||||
|
'error' => false,
|
||||||
|
'message' => trans('Notification queued successfully. It will be sent in background.'),
|
||||||
|
'data' => $notification,
|
||||||
|
'code' => config('constants.RESPONSE_CODE.SUCCESS')
|
||||||
|
])->send();
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
ob_end_flush();
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
if (function_exists('fastcgi_finish_request')) {
|
||||||
|
fastcgi_finish_request();
|
||||||
|
$executeJob();
|
||||||
|
} else {
|
||||||
|
register_shutdown_function($executeJob);
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'NotificationController -> store');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('notification-delete');
|
||||||
|
$notification = Notifications::findOrFail($id);
|
||||||
|
$notification->delete();
|
||||||
|
FileService::delete($notification->getRawOriginal('image'));
|
||||||
|
ResponseService::successResponse('Notification Deleted successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'NotificationController -> destroy');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('notification-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = Notifications::where('id', '!=', 0)->orderBy($sort, $order);
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
|
||||||
|
if (Auth::user()->can('notification-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('notification.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function batchDelete(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('notification-delete');
|
||||||
|
try {
|
||||||
|
foreach (Notifications::whereIn('id', explode(',', $request->id))->get() as $row) {
|
||||||
|
$row->delete();
|
||||||
|
FileService::delete($row->getRawOriginal('image'));
|
||||||
|
}
|
||||||
|
ResponseService::successResponse("Notification deleted successfully");
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "NotificationController -> batchDelete");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
723
app/Http/Controllers/PackageController.php
Normal file
723
app/Http/Controllers/PackageController.php
Normal file
@@ -0,0 +1,723 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\Package;
|
||||||
|
use App\Models\PackageCategory;
|
||||||
|
use App\Models\PackageTranslation;
|
||||||
|
use App\Models\PaymentTransaction;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use App\Models\UserPurchasedPackage;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\HelperService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class PackageController extends Controller {
|
||||||
|
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->uploadFolder = 'packages';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['advertisement-listing-package-list', 'advertisement-listing-package-create', 'advertisement-listing-package-update', 'advertisement-listing-package-delete']);
|
||||||
|
$categories = Category::without('translations')
|
||||||
|
->where('status', 1)
|
||||||
|
->get()
|
||||||
|
->each->setAppends([]);
|
||||||
|
$categories = HelperService::buildNestedChildSubcategoryObject($categories);
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
$currency_symbol = CachingService::getSystemSettings('currency_symbol');
|
||||||
|
return view('packages.index', compact('categories', 'currency_symbol', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Request $request) {
|
||||||
|
ResponseService::noPermissionThenRedirect('advertisement-listing-package-create');
|
||||||
|
$categories = Category::without('translations')
|
||||||
|
->where('status', 1)
|
||||||
|
->get()
|
||||||
|
->each->setAppends([]);
|
||||||
|
$categories = HelperService::buildNestedChildSubcategoryObject($categories);
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
$currency_symbol = CachingService::getSystemSettings('currency_symbol');
|
||||||
|
$selected_categories = [];
|
||||||
|
$selected_all_categories = [];
|
||||||
|
return view('packages.create', compact('categories', 'currency_symbol', 'languages', 'selected_categories', 'selected_all_categories'));
|
||||||
|
}
|
||||||
|
public function store(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-listing-package-create');
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
// Support both new UI (`type`) and legacy UI (`package_types[]`)
|
||||||
|
$resolvedPackageType = $request->input('type');
|
||||||
|
if (empty($resolvedPackageType)) {
|
||||||
|
$resolvedPackageType = $request->input('package_types.0', 'item_listing');
|
||||||
|
}
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"name.$defaultLangId" => 'required|string',
|
||||||
|
'price' => 'required|numeric',
|
||||||
|
'discount_in_percentage' => 'required|numeric',
|
||||||
|
'final_price' => 'required|numeric',
|
||||||
|
'package_duration_type' => 'required|in:limited,unlimited',
|
||||||
|
'duration' => 'nullable|required_if:package_duration_type,limited|min:1',
|
||||||
|
'type' => 'required|in:item_listing,advertisement',
|
||||||
|
'icon' => 'required|mimes:jpeg,jpg,png|max:7168',
|
||||||
|
'is_global' => 'nullable|in:0,1',
|
||||||
|
'selected_categories' => 'required_unless:is_global,1|array|min:1',
|
||||||
|
'ads_item_limit_type' => 'required_if:type,item_listing|in:limited,unlimited',
|
||||||
|
'ads_item_limit' => 'required_if:ads_item_limit_type,limited',
|
||||||
|
'ads_listing_duration_type' => 'required_if:type,item_listing|in:standard,package,custom',
|
||||||
|
'ads_listing_duration_days' => 'nullable|required_if:ads_listing_duration_type,custom|integer|min:1',
|
||||||
|
'featured_item_limit_type' => 'required_if:type,advertisement|in:limited,unlimited',
|
||||||
|
'featured_item_limit' => 'required_if:featured_item_limit_type,limited',
|
||||||
|
'featured_ads_duration_type' => 'required_if:type,advertisement|in:standard,package,custom',
|
||||||
|
'featured_ads_duration_days' => 'nullable|required_if:featured_ads_duration_type,custom|integer|min:1',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["name.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get package type - needed before validation for is_global enforcement
|
||||||
|
$packageType = $resolvedPackageType;
|
||||||
|
|
||||||
|
// Set is_global to 1 for advertisement packages before validation
|
||||||
|
if ($packageType === 'advertisement' && !$request->has('is_global')) {
|
||||||
|
$request->merge(['is_global' => 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
// Prepare key points for default language
|
||||||
|
$defaultKeyPoints = $request->input("key_points.$defaultLangId", []);
|
||||||
|
$defaultKeyPoints = array_filter($defaultKeyPoints); // Remove empty values
|
||||||
|
|
||||||
|
// If old description exists, add it as first key point
|
||||||
|
// $oldDescription = $request->input("description.$defaultLangId");
|
||||||
|
// if (!empty($oldDescription) && !in_array($oldDescription, $defaultKeyPoints)) {
|
||||||
|
// array_unshift($defaultKeyPoints, $oldDescription);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Auto-calculate final price if not provided or if price/discount changed
|
||||||
|
$finalPrice = $request->final_price;
|
||||||
|
if (empty($finalPrice) || ($request->has('price') && $request->has('discount_in_percentage'))) {
|
||||||
|
$price = (float) $request->price;
|
||||||
|
$discount = (float) $request->discount_in_percentage;
|
||||||
|
if ($price > 0 && $discount >= 0 && $discount <= 100) {
|
||||||
|
$discountAmount = ($price * $discount) / 100;
|
||||||
|
$finalPrice = $price - $discountAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is_global: 1 for advertisement packages by default, otherwise use request value or 0
|
||||||
|
$isGlobal = ($packageType === 'advertisement') ? 1 : ($request->is_global ?? 0);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $request->input("name.$defaultLangId"),
|
||||||
|
'price' => $request->price,
|
||||||
|
'discount_in_percentage' => $request->discount_in_percentage,
|
||||||
|
'final_price' => $finalPrice,
|
||||||
|
'ios_product_id' => $request->ios_product_id,
|
||||||
|
'duration' => ($request->package_duration_type == "limited") ? $request->duration : "unlimited",
|
||||||
|
'type' => $packageType,
|
||||||
|
'is_global' => $isGlobal,
|
||||||
|
'key_points' => !empty($defaultKeyPoints) ? json_encode($defaultKeyPoints, JSON_UNESCAPED_UNICODE) : null,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Handle item limits and duration types based on package type
|
||||||
|
if ($packageType === 'item_listing') {
|
||||||
|
// item_limit can be "unlimited" or a number
|
||||||
|
if ($request->ads_item_limit_type == "limited") {
|
||||||
|
$data['item_limit'] = $request->ads_item_limit;
|
||||||
|
} else {
|
||||||
|
$data['item_limit'] = "unlimited";
|
||||||
|
}
|
||||||
|
$data['listing_duration_type'] = $request->ads_listing_duration_type ?? 'standard';
|
||||||
|
// Set days: 30 for 'standard', null for 'package', custom value for 'custom'
|
||||||
|
if ($request->ads_listing_duration_type == 'standard') {
|
||||||
|
$data['listing_duration_days'] = 30;
|
||||||
|
} elseif ($request->ads_listing_duration_type == 'package') {
|
||||||
|
$data['listing_duration_days'] = $data['duration']; // Uses package duration
|
||||||
|
} elseif ($request->ads_listing_duration_type == 'custom') {
|
||||||
|
$data['listing_duration_days'] = $request->ads_listing_duration_days;
|
||||||
|
} else {
|
||||||
|
$data['listing_duration_days'] = null;
|
||||||
|
}
|
||||||
|
} else if ($packageType === 'advertisement') {
|
||||||
|
// item_limit can be "unlimited" or a number
|
||||||
|
if ($request->featured_item_limit_type == "limited") {
|
||||||
|
$data['item_limit'] = $request->featured_item_limit;
|
||||||
|
} else {
|
||||||
|
$data['item_limit'] = "unlimited";
|
||||||
|
}
|
||||||
|
// Use listing_duration for advertisement packages too
|
||||||
|
$data['listing_duration_type'] = $request->featured_ads_duration_type ?? 'standard';
|
||||||
|
// Set days: 30 for 'standard', null for 'package', custom value for 'custom'
|
||||||
|
if ($request->featured_ads_duration_type == 'standard') {
|
||||||
|
$data['listing_duration_days'] = 30;
|
||||||
|
} elseif ($request->featured_ads_duration_type == 'package') {
|
||||||
|
$data['listing_duration_days'] = $data['duration']; // Uses package duration
|
||||||
|
} elseif ($request->featured_ads_duration_type == 'custom') {
|
||||||
|
$data['listing_duration_days'] = $request->featured_ads_duration_days;
|
||||||
|
} else {
|
||||||
|
$data['listing_duration_days'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('icon')) {
|
||||||
|
$data['icon'] = FileService::compressAndUpload($request->file('icon'), $this->uploadFolder);
|
||||||
|
}
|
||||||
|
$package = Package::create($data);
|
||||||
|
|
||||||
|
// Handle categories
|
||||||
|
if ($request->is_global == 1) {
|
||||||
|
// Global package - no categories needed
|
||||||
|
} else {
|
||||||
|
if (!empty($request->selected_categories)) {
|
||||||
|
$categoryMappings = collect($request->selected_categories)->map(function ($categoryId) use ($package) {
|
||||||
|
return [
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
'package_id' => $package->id,
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
PackageCategory::upsert($categoryMappings, ['package_id', 'category_id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle translations with key points
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedName = $request->input("name.$langId");
|
||||||
|
$translatedKeyPoints = $request->input("key_points.$langId", []);
|
||||||
|
$translatedKeyPoints = array_filter($translatedKeyPoints); // Remove empty values
|
||||||
|
|
||||||
|
// If old description exists for this language, add it as first key point
|
||||||
|
// $oldDescription = $request->input("description.$langId");
|
||||||
|
// if (!empty($oldDescription) && !in_array($oldDescription, $translatedKeyPoints)) {
|
||||||
|
// array_unshift($translatedKeyPoints, $oldDescription);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!empty($translatedName) || !empty($translatedKeyPoints)) {
|
||||||
|
PackageTranslation::create([
|
||||||
|
'package_id' => $package->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'name' => $translatedName ?? '',
|
||||||
|
'key_points' => !empty($translatedKeyPoints) ? json_encode($translatedKeyPoints, JSON_UNESCAPED_UNICODE) : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('Package Successfully Added', $data);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th, "PackageController -> store method");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-listing-package-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = Package::with(['translations', 'categories']);
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
if (! empty($request->filter)) {
|
||||||
|
// Fix escaped JSON if middleware or frontend sent " instead of "
|
||||||
|
$filterString = html_entity_decode($request->filter, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$filterData = json_decode($filterString, false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$sql = $sql->filter($filterData);
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
return response()->json(['error' => 'Invalid JSON format in filter parameter'], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->orderBy($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
// Show "Global" or "Category Based" instead of actual category names
|
||||||
|
$tempRow['category_names'] = $row->is_global == 1 ? 'Global' : 'Category Based';
|
||||||
|
if (Auth::user()->can('advertisement-listing-package-update')) {
|
||||||
|
$tempRow['operate'] = BootstrapTableService::editButton(route('package.edit', $row->id));
|
||||||
|
}
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('advertisement-listing-package-update');
|
||||||
|
$package = Package::with(['package_categories', 'translations'])->findOrFail($id);
|
||||||
|
|
||||||
|
$translations = [];
|
||||||
|
$translations[1] = [
|
||||||
|
'name' => $package->name,
|
||||||
|
'description' => $package->description,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($package->translations as $translation) {
|
||||||
|
$translations[$translation->language_id] = [
|
||||||
|
'name' => $translation->name,
|
||||||
|
'description' => $translation->description,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected_categories = $package->package_categories->pluck('category_id')->toArray();
|
||||||
|
$selected_all_categories = $selected_categories;
|
||||||
|
|
||||||
|
foreach ($selected_categories as $catId) {
|
||||||
|
$categoryId = $catId;
|
||||||
|
while ($categoryId) {
|
||||||
|
$parent = Category::without('translations')->where('id', $categoryId)->value('parent_category_id');
|
||||||
|
if ($parent) {
|
||||||
|
$selected_all_categories[] = $parent;
|
||||||
|
$categoryId = $parent;
|
||||||
|
} else {
|
||||||
|
$categoryId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected_all_categories = array_unique($selected_all_categories);
|
||||||
|
$categories = Category::without('translations')
|
||||||
|
->where('status', 1)
|
||||||
|
->get()
|
||||||
|
->each->setAppends([]);
|
||||||
|
$categories = HelperService::buildNestedChildSubcategoryObject($categories);
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
$currency_symbol = CachingService::getSystemSettings('currency_symbol');
|
||||||
|
|
||||||
|
return view('packages.edit', compact('package', 'categories', 'selected_categories', 'selected_all_categories', 'languages', 'translations', 'currency_symbol'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('advertisement-listing-package-update');
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
$package = Package::with('package_categories')->findOrFail($id);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"name.$defaultLangId" => 'required|string',
|
||||||
|
"description.$defaultLangId" => 'nullable|string',
|
||||||
|
'price' => 'required|numeric',
|
||||||
|
'discount_in_percentage' => 'required|numeric',
|
||||||
|
'final_price' => 'required|numeric',
|
||||||
|
'package_duration_type' => 'required|in:limited,unlimited',
|
||||||
|
'duration' => 'nullable|required_if:package_duration_type,limited|integer|min:1',
|
||||||
|
'icon' => 'nullable|mimes:jpeg,jpg,png|max:7168',
|
||||||
|
'is_global' => 'nullable|in:0,1',
|
||||||
|
'selected_categories' => 'required_unless:is_global,1|array|min:1',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add validation rules based on package type
|
||||||
|
if ($package->type === 'item_listing') {
|
||||||
|
$rules['ads_item_limit_type'] = 'required|in:limited,unlimited';
|
||||||
|
// Allow "unlimited" string or integer for item_limit
|
||||||
|
$rules['ads_item_limit'] = 'required_if:ads_item_limit_type,limited';
|
||||||
|
$rules['ads_listing_duration_type'] = 'required|in:standard,package,custom';
|
||||||
|
$rules['ads_listing_duration_days'] = 'nullable|required_if:ads_listing_duration_type,custom|integer|min:1';
|
||||||
|
} else if ($package->type === 'advertisement') {
|
||||||
|
$rules['featured_item_limit_type'] = 'required|in:limited,unlimited';
|
||||||
|
// Allow "unlimited" string or integer for item_limit
|
||||||
|
$rules['featured_item_limit'] = 'required_if:featured_item_limit_type,limited';
|
||||||
|
$rules['featured_ads_duration_type'] = 'required|in:standard,package,custom';
|
||||||
|
$rules['featured_ads_duration_days'] = 'nullable|required_if:featured_ads_duration_type,custom|integer|min:1';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["name.$langId"] = 'nullable|string';
|
||||||
|
$rules["description.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is_global to 1 for advertisement packages before validation
|
||||||
|
if ($package->type === 'advertisement' && !$request->has('is_global')) {
|
||||||
|
$request->merge(['is_global' => 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
// Handle duration based on package type
|
||||||
|
$durationValue = "unlimited";
|
||||||
|
if ($package->type === 'item_listing') {
|
||||||
|
if ($request->ads_item_limit_type == "limited" && !empty($request->ads_item_limit)) {
|
||||||
|
// For item listing, item limit is handled separately
|
||||||
|
}
|
||||||
|
if (isset($request->package_duration_type)) {
|
||||||
|
$durationValue = ($request->package_duration_type == "limited") ? $request->duration : "unlimited";
|
||||||
|
} else {
|
||||||
|
$durationValue = ($request->duration_type == "limited") ? $request->duration : "unlimited";
|
||||||
|
}
|
||||||
|
} else if ($package->type === 'advertisement') {
|
||||||
|
if (isset($request->package_duration_type)) {
|
||||||
|
$durationValue = ($request->package_duration_type == "limited") ? $request->duration : "unlimited";
|
||||||
|
} else {
|
||||||
|
$durationValue = ($request->duration_type == "limited") ? $request->duration : "unlimited";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle item limit based on package type (can be "unlimited" or number for both types)
|
||||||
|
$itemLimitValue = "unlimited";
|
||||||
|
if ($package->type === 'item_listing') {
|
||||||
|
// Allow "unlimited" string or number
|
||||||
|
if ($request->ads_item_limit_type == "limited") {
|
||||||
|
$itemLimitValue = $request->ads_item_limit;
|
||||||
|
} else {
|
||||||
|
$itemLimitValue = "unlimited";
|
||||||
|
}
|
||||||
|
} else if ($package->type === 'advertisement') {
|
||||||
|
// Allow "unlimited" string or number
|
||||||
|
if ($request->featured_item_limit_type == "limited") {
|
||||||
|
$itemLimitValue = $request->featured_item_limit;
|
||||||
|
} else {
|
||||||
|
$itemLimitValue = "unlimited";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to old field names
|
||||||
|
$itemLimitValue = ($request->item_limit_type == "limited") ? $request->item_limit : "unlimited";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-calculate final price if not provided or if price/discount changed
|
||||||
|
$finalPrice = $request->final_price;
|
||||||
|
if (empty($finalPrice) || ($request->has('price') && $request->has('discount_in_percentage'))) {
|
||||||
|
$price = (float) $request->price;
|
||||||
|
$discount = (float) $request->discount_in_percentage;
|
||||||
|
if ($price > 0 && $discount >= 0 && $discount <= 100) {
|
||||||
|
$discountAmount = ($price * $discount) / 100;
|
||||||
|
$finalPrice = $price - $discountAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is_global: 1 for advertisement packages by default, otherwise use request value or existing value
|
||||||
|
$isGlobal = ($package->type === 'advertisement') ? 1 : ($request->is_global ?? $package->is_global ?? 0);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $request->input("name.$defaultLangId"),
|
||||||
|
'description' => $request->input("description.$defaultLangId"),
|
||||||
|
'price' => $request->price,
|
||||||
|
'discount_in_percentage' => $request->discount_in_percentage,
|
||||||
|
'final_price' => $finalPrice,
|
||||||
|
'ios_product_id' => $request->ios_product_id,
|
||||||
|
'duration' => $durationValue,
|
||||||
|
'item_limit' => $itemLimitValue,
|
||||||
|
'is_global' => $isGlobal,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Handle listing duration types based on package type (use listing_duration for both types)
|
||||||
|
if ($package->type === 'item_listing') {
|
||||||
|
$data['listing_duration_type'] = $request->ads_listing_duration_type ?? 'standard';
|
||||||
|
// Set days: 30 for 'standard', null for 'package', custom value for 'custom'
|
||||||
|
if ($request->ads_listing_duration_type == 'standard') {
|
||||||
|
$data['listing_duration_days'] = 30;
|
||||||
|
} elseif ($request->ads_listing_duration_type == 'package') {
|
||||||
|
$data['listing_duration_days'] = $durationValue; // Uses package duration
|
||||||
|
} elseif ($request->ads_listing_duration_type == 'custom') {
|
||||||
|
$data['listing_duration_days'] = $request->ads_listing_duration_days;
|
||||||
|
} else {
|
||||||
|
$data['listing_duration_days'] = null;
|
||||||
|
}
|
||||||
|
} else if ($package->type === 'advertisement') {
|
||||||
|
// Use listing_duration for advertisement packages too
|
||||||
|
$data['listing_duration_type'] = $request->featured_ads_duration_type ?? 'standard';
|
||||||
|
// Set days: 30 for 'standard', null for 'package', custom value for 'custom'
|
||||||
|
if ($request->featured_ads_duration_type == 'standard') {
|
||||||
|
$data['listing_duration_days'] = 30;
|
||||||
|
} elseif ($request->featured_ads_duration_type == 'package') {
|
||||||
|
$data['listing_duration_days'] = $durationValue; // Uses package duration
|
||||||
|
} elseif ($request->featured_ads_duration_type == 'custom') {
|
||||||
|
$data['listing_duration_days'] = $request->featured_ads_duration_days;
|
||||||
|
} else {
|
||||||
|
$data['listing_duration_days'] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->hasFile('icon')) {
|
||||||
|
$data['icon'] = FileService::compressAndReplace($request->file('icon'), $this->uploadFolder, $package->getRawOriginal('icon'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare key points for default language
|
||||||
|
$defaultKeyPoints = $request->input("key_points.$defaultLangId", []);
|
||||||
|
$defaultKeyPoints = array_filter($defaultKeyPoints); // Remove empty values
|
||||||
|
|
||||||
|
// If old description exists, add it as first key point
|
||||||
|
$oldDescription = $request->input("description.$defaultLangId");
|
||||||
|
if (!empty($oldDescription) && !in_array($oldDescription, $defaultKeyPoints)) {
|
||||||
|
array_unshift($defaultKeyPoints, $oldDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['key_points'] = !empty($defaultKeyPoints) ? json_encode($defaultKeyPoints, JSON_UNESCAPED_UNICODE) : null;
|
||||||
|
|
||||||
|
$package->update($data);
|
||||||
|
|
||||||
|
// Handle categories
|
||||||
|
if ($request->is_global == 1) {
|
||||||
|
// Delete all category associations for global package
|
||||||
|
$package->package_categories()->delete();
|
||||||
|
} else {
|
||||||
|
$old_selected_category = $package->package_categories->pluck('category_id')->toArray();
|
||||||
|
$new_selected_category = $request->selected_categories ?? [];
|
||||||
|
|
||||||
|
// Delete removed categories
|
||||||
|
if ($new_selected_category) {
|
||||||
|
foreach (array_diff($old_selected_category, $new_selected_category) as $category_id) {
|
||||||
|
$package->package_categories->first(function ($data) use ($category_id) {
|
||||||
|
return $data->category_id == $category_id;
|
||||||
|
})->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new categories
|
||||||
|
$newSelectedCategory = [];
|
||||||
|
foreach (array_diff($new_selected_category, $old_selected_category) as $category_id) {
|
||||||
|
$newSelectedCategory[] = [
|
||||||
|
'category_id' => $category_id,
|
||||||
|
'package_id' => $package->id,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($newSelectedCategory) > 0) {
|
||||||
|
PackageCategory::insert($newSelectedCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedName = $request->input("name.$langId");
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
$translatedKeyPoints = $request->input("key_points.$langId", []);
|
||||||
|
$translatedKeyPoints = array_filter($translatedKeyPoints); // Remove empty values
|
||||||
|
|
||||||
|
// If old description exists for this language, add it as first key point
|
||||||
|
if (!empty($translatedDescription) && !in_array($translatedDescription, $translatedKeyPoints)) {
|
||||||
|
array_unshift($translatedKeyPoints, $translatedDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageTranslation::updateOrCreate(
|
||||||
|
[
|
||||||
|
'package_id' => $package->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => $translatedName ?? '',
|
||||||
|
'description' => $translatedDescription ?? '',
|
||||||
|
'key_points' => !empty($translatedKeyPoints) ? json_encode($translatedKeyPoints, JSON_UNESCAPED_UNICODE) : null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse("Package Successfully Updated");
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th, "PackageController -> update");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function userPackagesIndex() {
|
||||||
|
ResponseService::noPermissionThenRedirect('user-package-list');
|
||||||
|
return view('packages.user');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userPackagesShow(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('user-package-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = UserPurchasedPackage::with('user:id,name', 'package:id,name');
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->orderBy($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$rows[] = $row->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentTransactionIndex() {
|
||||||
|
ResponseService::noPermissionThenRedirect('payment-transactions-list');
|
||||||
|
return view('packages.payment-transactions');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentTransactionShow(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('payment-transactions-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = PaymentTransaction::with('user')->orderBy($sort, $order);
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['created_at'] = Carbon::createFromFormat('Y-m-d H:i:s', $row->created_at)->format('d-m-y H:i:s');
|
||||||
|
$tempRow['updated_at'] = Carbon::createFromFormat('Y-m-d H:i:s', $row->updated_at)->format('d-m-y H:i:s');
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bankTransferIndex() {
|
||||||
|
ResponseService::noPermissionThenRedirect('payment-transactions-list');
|
||||||
|
return view('packages.bank-transfer');
|
||||||
|
}
|
||||||
|
public function bankTransferShow(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('payment-transactions-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = PaymentTransaction::with('user')->where('payment_gateway' ,'BankTransfer')->orderBy($sort, $order);
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['created_at'] = Carbon::createFromFormat('Y-m-d H:i:s', $row->created_at)->format('d-m-y H:i:s');
|
||||||
|
$tempRow['updated_at'] = Carbon::createFromFormat('Y-m-d H:i:s', $row->updated_at)->format('d-m-y H:i:s');
|
||||||
|
if (Auth::user()->can('featured-advertisement-package-update')) {
|
||||||
|
$tempRow['operate'] = BootstrapTableService::editButton(route('package.bank-transfer.update-status', $row->id), true, '#editStatusModal', 'edit-status', $row->id);
|
||||||
|
}
|
||||||
|
$tempRow['payment_status'] = $row->payment_status_uper;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
public function updateStatus(Request $request, $id)
|
||||||
|
{
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'payment_status' => 'required|in:succeed,rejected'
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$transaction = PaymentTransaction::findOrFail($id);
|
||||||
|
$transaction->update(['payment_status' => $request->payment_status]);
|
||||||
|
|
||||||
|
$userTokens = UserFcmToken::where('user_id', $transaction->user_id)->pluck('fcm_token')->toArray();
|
||||||
|
|
||||||
|
if ($request->payment_status === 'succeed') {
|
||||||
|
$parts = explode('-', $transaction->order_id);
|
||||||
|
$package_id = $parts[2];
|
||||||
|
$package = Package::find((int) $package_id);
|
||||||
|
|
||||||
|
if ($package) {
|
||||||
|
UserPurchasedPackage::create([
|
||||||
|
'package_id' => $package->id,
|
||||||
|
'user_id' => $transaction->user_id,
|
||||||
|
'start_date' => Carbon::now(),
|
||||||
|
'end_date' => $package->duration == "unlimited" ? null : Carbon::now()->addDays($package->duration),
|
||||||
|
'total_limit' => $package->item_limit == "unlimited" ? null : $package->item_limit,
|
||||||
|
'payment_transactions_id' => $transaction->id,
|
||||||
|
'listing_duration_type' => $package->listing_duration_type,
|
||||||
|
'listing_duration_days' => $package->listing_duration_days
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit(); // Close the DB transaction as soon as database work is don e
|
||||||
|
|
||||||
|
// NOW handle the external notification logic after commit
|
||||||
|
if (!empty($userTokens)) {
|
||||||
|
if ($request->payment_status === 'succeed') {
|
||||||
|
$title = "Package Purchased";
|
||||||
|
$body = 'Amount :- ' . $transaction->amount;
|
||||||
|
NotificationService::sendFcmNotification($userTokens, $title, $body, 'payment');
|
||||||
|
} elseif ($request->payment_status === 'rejected') {
|
||||||
|
$title = "Payment Rejected";
|
||||||
|
$body = "Your payment of " . $transaction->amount . " has been rejected.";
|
||||||
|
NotificationService::sendFcmNotification($userTokens, $title, $body, 'payment');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseService::successResponse('Payment Status Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th, 'PackageController ->updateStatus');
|
||||||
|
return ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
681
app/Http/Controllers/PlaceController.php
Normal file
681
app/Http/Controllers/PlaceController.php
Normal file
@@ -0,0 +1,681 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Area;
|
||||||
|
use App\Models\AreaTranslation;
|
||||||
|
use App\Models\City;
|
||||||
|
use App\Models\CityTranslation;
|
||||||
|
use App\Models\Country;
|
||||||
|
use App\Models\CountryTranslation;
|
||||||
|
use App\Models\State;
|
||||||
|
use App\Models\StateTranslation;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Cerbero\JsonParser\JsonParser;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class PlaceController extends Controller
|
||||||
|
{
|
||||||
|
public function countryIndex()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['country-list', 'country-create', 'country-update', 'country-delete']);
|
||||||
|
$countries = JsonParser::parse(resource_path('countries.json'))->pointers(['/-/name', '/-/id', '/-/emoji'])->toArray();
|
||||||
|
$dbCountries = Country::select('name')->get();
|
||||||
|
foreach ($countries as $key => $country) {
|
||||||
|
$countries[$key]['is_already_exists'] = $dbCountries->contains(static function ($dbCountry) use ($country) {
|
||||||
|
return $country['name'] == $dbCountry->name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('places.country', compact('countries'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countryShow(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('country-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 15);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'DESC');
|
||||||
|
|
||||||
|
$sql = Country::select(['id', 'name', 'emoji']);
|
||||||
|
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql = $sql->orderBy($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
if (auth()->user()->can('country-delete')) {
|
||||||
|
$tempRow['operate'] = BootstrapTableService::deleteButton(route('countries.destroy', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, 'CustomFieldController -> show');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyCountry($id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Country::find($id)->delete();
|
||||||
|
ResponseService::successResponse('Country deleted Successfully');
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, 'PlaceController -> destroyCountry');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stateSearch(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenRedirect('state-list');
|
||||||
|
$states = State::where('country_id', $request->country_id)->select(['id', 'name'])->orderBy('name', 'ASC')->get();
|
||||||
|
ResponseService::successResponse('States Fetched Successfully', $states);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th, 'PlaceController -> stateSearch');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stateIndex()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['state-list', 'state-create', 'state-update', 'state-delete']);
|
||||||
|
$countries = Country::with('nameTranslations')->get();
|
||||||
|
|
||||||
|
return view('places.state', compact('countries'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stateShow(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('state-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 15);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'DESC');
|
||||||
|
|
||||||
|
$sql = State::with('country:id,name,emoji');
|
||||||
|
|
||||||
|
if (! empty($request->filter)) {
|
||||||
|
$sql = $sql->filter(json_decode($request->filter, false, 512, JSON_THROW_ON_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql = $sql->sort($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['country_name'] = $row->country->name;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, 'CustomFieldController -> show');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function citySearch(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenRedirect('city-list');
|
||||||
|
$cities = City::where('state_id', $request->state_id)->select(['id', 'name'])->orderBy('name', 'ASC')->get();
|
||||||
|
ResponseService::successResponse('Cities fetched Successfully', $cities);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th, 'PlaceController -> citySearch');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cityIndex()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['city-list', 'city-create', 'city-update', 'city-delete']);
|
||||||
|
$countries = Country::with('nameTranslations')->get();
|
||||||
|
|
||||||
|
$states = State::get();
|
||||||
|
|
||||||
|
return view('places.city', compact('countries', 'states'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCity(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('city-create');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name.*' => 'required|string',
|
||||||
|
'latitude.*' => 'nullable|numeric',
|
||||||
|
'longitude.*' => 'nullable|numeric',
|
||||||
|
'country_id' => 'required|exists:countries,id',
|
||||||
|
'state_id' => 'required|exists:states,id',
|
||||||
|
], [], [
|
||||||
|
'name.*' => 'City name',
|
||||||
|
'latitude.*' => 'Latitude',
|
||||||
|
'longitude.*' => 'Longitude',
|
||||||
|
'country_id' => 'Country',
|
||||||
|
'state_id' => 'State',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$state = State::findOrFail($request->state_id);
|
||||||
|
$country = Country::findOrFail($request->country_id);
|
||||||
|
|
||||||
|
$cityData = [];
|
||||||
|
|
||||||
|
foreach ($request->name as $index => $name) {
|
||||||
|
// Check if city already exists
|
||||||
|
$exists = City::where('name', $name)
|
||||||
|
->where('state_id', $request->state_id)
|
||||||
|
->where('country_id', $request->country_id)
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
ResponseService::validationError("City '{$name}' already exists in this state and country.");
|
||||||
|
}
|
||||||
|
$cityData[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'state_id' => $request->state_id,
|
||||||
|
'country_id' => $request->country_id,
|
||||||
|
'state_code' => $state->state_code,
|
||||||
|
'country_code' => $country->iso2,
|
||||||
|
'latitude' => $request->latitude[$index] ?? null,
|
||||||
|
'longitude' => $request->longitude[$index] ?? null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
City::insert($cityData);
|
||||||
|
ResponseService::successResponse('Cities added successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'message', 'The city already exists.');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cityShow(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('city-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 15);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'DESC');
|
||||||
|
|
||||||
|
$sql = City::with('state:id,name', 'country:id,name,emoji');
|
||||||
|
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
if (! empty($request->filter)) {
|
||||||
|
$sql = $sql->filter(json_decode($request->filter, false, 512, JSON_THROW_ON_ERROR));
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql = $sql->sort($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('city-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('city.update', $row->id), true, '#editModal', 'cityEvents', $row->id);
|
||||||
|
}
|
||||||
|
if (Auth::user()->can('city-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('city.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['state_name'] = $row->state->name;
|
||||||
|
$tempRow['country_name'] = $row->country->name;
|
||||||
|
$tempRow['state_id'] = $row->state->id;
|
||||||
|
$tempRow['country_id'] = $row->country->id;
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, 'PlaceController -> show');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCity(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('city-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'Required',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$city = City::findOrFail($id);
|
||||||
|
$data = $request->all();
|
||||||
|
$city->update($data);
|
||||||
|
ResponseService::successResponse('city updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Place Controller -> update');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyCity(string $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('city-delete');
|
||||||
|
City::findOrFail($id)->delete();
|
||||||
|
ResponseService::successResponse('city delete successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Place Controller -> destroy');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importCountry(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('country-create');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'countries' => 'required|array',
|
||||||
|
'countries.*' => 'integer',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$country_id = $request->countries;
|
||||||
|
DB::beginTransaction();
|
||||||
|
foreach (JsonParser::parse(resource_path('world.json')) as $country) {
|
||||||
|
if (in_array($country['id'], $country_id, false)) {
|
||||||
|
Country::create([
|
||||||
|
...$country,
|
||||||
|
'timezones' => json_encode($country['timezones'], JSON_THROW_ON_ERROR),
|
||||||
|
'translations' => json_encode($country['translations'], JSON_THROW_ON_ERROR),
|
||||||
|
'region_id' => null,
|
||||||
|
'subregion_id' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($country['states'] as $state) {
|
||||||
|
State::create([
|
||||||
|
...$state,
|
||||||
|
'country_id' => $country['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$cities = [];
|
||||||
|
foreach ($state['cities'] as $city) {
|
||||||
|
$cities[] = [
|
||||||
|
...$city,
|
||||||
|
'state_id' => $state['id'],
|
||||||
|
'state_code' => $state['state_code'],
|
||||||
|
'country_id' => $country['id'],
|
||||||
|
'country_code' => $country['iso2'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
City::upsert($cities, ['name', 'state_id', 'country_id'], ['state_code', 'country_code', 'latitude', 'longitude', 'flag', 'wikiDataId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop the JSON file reading if country_id array is empty */
|
||||||
|
unset($country_id[array_search($country['id'], $country_id, true)]);
|
||||||
|
if (empty($country_id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('Country imported successfully');
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($e, 'CustomFieldController -> show');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createArea()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['area-list', 'area-create', 'area-update', 'area-delete']);
|
||||||
|
$countries = Country::get();
|
||||||
|
$states = State::get();
|
||||||
|
$cities = city::get();
|
||||||
|
|
||||||
|
return view('places.area', compact('countries', 'states', 'cities'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addArea(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('area-create');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name.*' => 'required|string',
|
||||||
|
'country_id' => 'required|exists:countries,id',
|
||||||
|
'state_id' => 'required|exists:states,id',
|
||||||
|
'city_id' => 'required|exists:cities,id',
|
||||||
|
'latitude.*' => 'nullable|numeric',
|
||||||
|
'longitude.*' => 'nullable|numeric',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$state = State::findOrFail($request->state_id);
|
||||||
|
$area = [];
|
||||||
|
foreach ($request->name as $index => $name) {
|
||||||
|
$area[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'city_id' => $request->city_id,
|
||||||
|
'state_id' => $request->state_id,
|
||||||
|
'country_id' => $request->country_id,
|
||||||
|
'state_code' => $state->state_code,
|
||||||
|
'latitude' => $request->latitude[$index] ?? null,
|
||||||
|
'longitude' => $request->longitude[$index] ?? null,
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Area::insert($area);
|
||||||
|
ResponseService::successResponse('Area Added Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'place Controller -> store');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function areaShow(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('area-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
|
||||||
|
$sql = Area::with('city:id,name', 'state:id,name', 'country:id,name')->orderBy($sort, $order);
|
||||||
|
|
||||||
|
if (! empty($_GET['search'])) {
|
||||||
|
$search = $_GET['search'];
|
||||||
|
$sql->where('id', 'LIKE', "%$search%")
|
||||||
|
->orwhere('name', 'LIKE', "%$search%")
|
||||||
|
->orwhere('latitude', 'LIKE', "%$search%")
|
||||||
|
->orwhere('longitude', 'LIKE', "%$search%");
|
||||||
|
}
|
||||||
|
if (! empty($request->filter)) {
|
||||||
|
$sql = $sql->filter(json_decode($request->filter, false, 512, JSON_THROW_ON_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('area-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('area.update', $row->id), true, '#editModal', 'areaEvents', $row->id);
|
||||||
|
}
|
||||||
|
if (Auth::user()->can('area-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('area.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'PlaceController --> show');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(string $id) {}
|
||||||
|
|
||||||
|
public function updateArea(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('area-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'Required|string',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$area = Area::findOrFail($id);
|
||||||
|
$data = $request->all();
|
||||||
|
$area->update($data);
|
||||||
|
ResponseService::successResponse('Area updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Area Controller -> update');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyArea(string $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('area-delete');
|
||||||
|
Area::findOrFail($id)->delete();
|
||||||
|
ResponseService::successResponse('Area delete successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Place Controller -> destroy');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showCountryTranslations(Request $request)
|
||||||
|
{
|
||||||
|
$countries = Country::with('nameTranslations')->get();
|
||||||
|
$languages = CachingService::getLanguages()->where('code', '!=', 'en')->values();
|
||||||
|
|
||||||
|
return view('places.country_translation', compact('countries', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCountriesTranslations(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('country-update');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'translations' => 'required|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($request->translations as $languageId => $translations) {
|
||||||
|
foreach ($translations as $countryId => $translatedName) {
|
||||||
|
if (! empty($translatedName)) {
|
||||||
|
CountryTranslation::updateOrCreate(
|
||||||
|
['country_id' => $countryId, 'language_id' => $languageId],
|
||||||
|
['name' => $translatedName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse('Country translations updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Country Controller -> updateTranslations');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showStatesTranslations(Request $request)
|
||||||
|
{
|
||||||
|
$States = State::with('translations')->get();
|
||||||
|
$countries = Country::get();
|
||||||
|
$languages = CachingService::getLanguages()->where('code', '!=', 'en')->values();
|
||||||
|
|
||||||
|
return view('places.state_translation', compact('countries', 'States', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateStatesTranslations(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('country-update');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'translations' => 'required|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($request->translations as $languageId => $translations) {
|
||||||
|
foreach ($translations as $countryId => $translatedName) {
|
||||||
|
if (! empty($translatedName)) {
|
||||||
|
StateTranslation::updateOrCreate(
|
||||||
|
['state_id' => $countryId, 'language_id' => $languageId],
|
||||||
|
['name' => $translatedName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse('State translations updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Country Controller -> updateTranslations');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showCitiesTranslations()
|
||||||
|
{
|
||||||
|
// $cities = City::with('translations')->get();
|
||||||
|
$countries = Country::with('states')->get();
|
||||||
|
$languages = CachingService::getLanguages()->where('code', '!=', 'en')->values();
|
||||||
|
|
||||||
|
return view('places.city_translation', compact('countries', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadStateCities(Request $request, $stateId)
|
||||||
|
{
|
||||||
|
$perPage = $request->input('per_page', 50);
|
||||||
|
$state = State::findOrFail($stateId);
|
||||||
|
$cities = City::where('state_id', $stateId)
|
||||||
|
->with('translations')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages()->where('code', '!=', 'en')->values();
|
||||||
|
|
||||||
|
return view('places.city_translation_tab', compact('state', 'cities', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCitiesTranslations(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('city-update');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'translations' => 'required|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($request->translations as $languageId => $translations) {
|
||||||
|
foreach ($translations as $cityId => $translatedName) {
|
||||||
|
if (! empty($translatedName)) {
|
||||||
|
CityTranslation::updateOrCreate(
|
||||||
|
['city_id' => $cityId, 'language_id' => $languageId],
|
||||||
|
['name' => $translatedName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse('City translations updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'City Translation -> update');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function areaTranslation()
|
||||||
|
{
|
||||||
|
$countries = Country::get();
|
||||||
|
|
||||||
|
return view('places.area_translation', compact('countries'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadCityAreas(Request $request, $cityId)
|
||||||
|
{
|
||||||
|
$city = City::findOrFail($cityId);
|
||||||
|
$areas = Area::where('city_id', $cityId)
|
||||||
|
->with('translations')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages()->where('code', '!=', 'en')->values();
|
||||||
|
|
||||||
|
return view('places.area_translation_tab', compact('city', 'areas', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateAreasTranslations(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('area-update');
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'translations' => 'required|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($request->translations as $languageId => $translations) {
|
||||||
|
foreach ($translations as $areaId => $translatedName) {
|
||||||
|
if (! empty($translatedName)) {
|
||||||
|
AreaTranslation::updateOrCreate(
|
||||||
|
['area_id' => $areaId, 'language_id' => $languageId],
|
||||||
|
['name' => $translatedName]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse('Area translations updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Area Translation -> update');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
230
app/Http/Controllers/ReportReasonController.php
Normal file
230
app/Http/Controllers/ReportReasonController.php
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\Language;
|
||||||
|
use App\Models\ReportReason;
|
||||||
|
use App\Models\ReportReasonTranslation;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\UserReports;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class ReportReasonController extends Controller {
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['report-reason-list', 'report-reason-create', 'report-reason-update', 'report-reason-delete']);
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('reports.index',compact('languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('report-reason-create');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'reason.1' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
$reasons = $request->input('reason');
|
||||||
|
|
||||||
|
$englishReason = $reasons['1'] ?? null;
|
||||||
|
if (!$englishReason) {
|
||||||
|
ResponseService::validationError('English reason is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$reportReason = ReportReason::create([
|
||||||
|
'reason' => $englishReason
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($reasons as $langId => $reasonText) {
|
||||||
|
|
||||||
|
if ($langId === '1' || empty($reasonText)) continue;
|
||||||
|
|
||||||
|
$language = Language::where('id', $langId)->first();
|
||||||
|
|
||||||
|
if ($language) {
|
||||||
|
ReportReasonTranslation::Create(
|
||||||
|
[
|
||||||
|
'report_reason_id' => $reportReason->id,
|
||||||
|
'language_id' => $language->id,
|
||||||
|
'reason' => $reasonText
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse('Reason Successfully Added');
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "ReportReason Controller -> store");
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('report-reason-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
$sql = ReportReason::with('translations')->orderBy($sort, $order);
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
$no = 1;
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['no'] = $no++;
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('report-reason-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('report-reasons.update', $row->id), true,'#editModal', 'reportReasonEvents', $row->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('report-reason-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('report-reasons.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$tempRow['translations'] = $row->translations->map(function ($t) {
|
||||||
|
return [
|
||||||
|
'language_id' => $t->language_id,
|
||||||
|
'reason' => $t->reason,
|
||||||
|
];
|
||||||
|
}) ?? [];
|
||||||
|
|
||||||
|
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('report-reason-update');
|
||||||
|
|
||||||
|
$reasons = $request->input('reason');
|
||||||
|
|
||||||
|
$englishReason = $reasons['1'] ?? null;
|
||||||
|
if (!$englishReason) {
|
||||||
|
ResponseService::validationError('English reason is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$reportReason = ReportReason::findOrFail($id);
|
||||||
|
$reportReason->update([
|
||||||
|
'reason' => $englishReason
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($reasons as $langId => $reasonText) {
|
||||||
|
if ($langId == '1') continue;
|
||||||
|
|
||||||
|
if (!empty($reasonText)) {
|
||||||
|
ReportReasonTranslation::updateOrCreate(
|
||||||
|
['report_reason_id' => $id, 'language_id' => $langId],
|
||||||
|
['reason' => $reasonText]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::successResponse('Reason Successfully Updated');
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "ReportReason Controller -> update");
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('report-reason-delete');
|
||||||
|
|
||||||
|
$reportReason = ReportReason::findOrFail($id);
|
||||||
|
|
||||||
|
// ✅ Check if any user report uses this reason
|
||||||
|
$isUsed = UserReports::where('report_reason_id', $id)->exists();
|
||||||
|
|
||||||
|
if ($isUsed) {
|
||||||
|
return ResponseService::errorResponse(
|
||||||
|
__('This reason is associated with existing user reports. Please remove those reports before deleting.'),
|
||||||
|
422 // Unprocessable Entity
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ If not used, safe to delete
|
||||||
|
$reportReason->delete();
|
||||||
|
|
||||||
|
return ResponseService::successResponse(__('Reason Deleted Successfully'));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, "ReportReason Controller -> destroy");
|
||||||
|
return ResponseService::errorResponse(__('Something Went Wrong'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function usersReports() {
|
||||||
|
ResponseService::noPermissionThenRedirect('user-reports-list');
|
||||||
|
$users = User::select(["id", "name"])->has('user_reports')->get();
|
||||||
|
$items = Item::select(["id", "name","image"])->approved()->has('user_reports')->get();
|
||||||
|
return view('reports.user_reports', compact('users', 'items'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userReportsShow(Request $request) {
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenRedirect('user-reports-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
$sql = UserReports::with(['user' => fn($q) => $q->select(['id', 'name', 'deleted_at'])->withTrashed(),
|
||||||
|
'report_reason:id,reason',
|
||||||
|
'item' => fn($q) => $q->select(['id', 'name', 'deleted_at','user_id','image'])
|
||||||
|
->withTrashed()
|
||||||
|
->with(['user' => fn($q) => $q->select(['id', 'name', 'deleted_at'])->withTrashed()])])->sort($sort, $order);
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($request->filter)) {
|
||||||
|
$sql = $sql->filter(json_decode($request->filter, false, 512, JSON_THROW_ON_ERROR));
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$res = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($res as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['user_status'] = isset($row->item->user) && empty($row->item->user->deleted_at);
|
||||||
|
$tempRow['item_status'] = empty($row->item->deleted_at);
|
||||||
|
$tempRow['reason'] = empty($row->report_reason_id) ? $row->other_message : $row->report_reason->reason;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, "ReportReason Controller -> show");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
app/Http/Controllers/RoleController.php
Normal file
223
app/Http/Controllers/RoleController.php
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Auth;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class RoleController extends Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array|string[]
|
||||||
|
*/
|
||||||
|
private array $reserveRole;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->middleware('permission:role-list|role-create|role-edit|role-delete', ['only' => ['index', 'store']]);
|
||||||
|
$this->middleware('permission:role-create', ['only' => ['create', 'store']]);
|
||||||
|
$this->middleware('permission:role-edit', ['only' => ['edit', 'update']]);
|
||||||
|
$this->middleware('permission:role-delete', ['only' => ['destroy']]);
|
||||||
|
|
||||||
|
$this->reserveRole = [
|
||||||
|
'Super Admin',
|
||||||
|
'User'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['role-list', 'role-create', 'role-edit', 'role-delete']);
|
||||||
|
$roles = Role::orderBy('id', 'DESC')->get();
|
||||||
|
return view('roles.index', compact('roles'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(Request $request) {
|
||||||
|
ResponseService::noPermissionThenRedirect('role-list');
|
||||||
|
$offset = request('offset', 0);
|
||||||
|
$limit = request('limit', 10);
|
||||||
|
$sort = request('sort', 'id');
|
||||||
|
$order = request('order', 'DESC');
|
||||||
|
|
||||||
|
$sql = Role::where('custom_role', 1);
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$search = $request->search;
|
||||||
|
$sql->where(function ($query) use ($search) {
|
||||||
|
$query->where('id', 'LIKE', "%$search%")->orwhere('name', 'LIKE', "%$search%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $sql->count();
|
||||||
|
|
||||||
|
$sql->orderBy($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$res = $sql->get();
|
||||||
|
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
$no = 1;
|
||||||
|
foreach ($res as $row) {
|
||||||
|
$operate = BootstrapTableService::button('fa fa-eye', route('roles.show', $row->id), ['btn-info'], ['title' => 'View']);
|
||||||
|
if (Auth::user()->can('role-edit') && Auth::user()->can('role-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('roles.edit', $row->id), false);
|
||||||
|
}
|
||||||
|
if ($row->custom_role != 0 && Auth::user()->can('role-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('roles.destroy', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['no'] = $no++;
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function create() {
|
||||||
|
ResponseService::noPermissionThenRedirect('role-create');
|
||||||
|
$permission = Permission::get();
|
||||||
|
$groupedPermissions = [];
|
||||||
|
|
||||||
|
foreach ($permission as $key => $val) {
|
||||||
|
$subArr = substr($val->name, 0, strrpos($val->name, "-"));
|
||||||
|
$groupedPermissions[$subArr][] = (object)array(
|
||||||
|
...$val->toArray(),
|
||||||
|
'short_name' => str_replace($subArr . "-", "", $val->name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$groupedPermissions = (object)$groupedPermissions;
|
||||||
|
return view('roles.create', compact('groupedPermissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request) {
|
||||||
|
ResponseService::noPermissionThenRedirect('role-create');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required|unique:roles,name',
|
||||||
|
'permission' => 'required|array'
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (in_array($request->name, $this->reserveRole, true)) {
|
||||||
|
ResponseService::errorResponse($request->name . " " . trans("is not a valid Role name Because it's Reserved Role"));
|
||||||
|
}
|
||||||
|
DB::beginTransaction();
|
||||||
|
$role = Role::create(['name' => $request->input('name'), 'custom_role' => 1]);
|
||||||
|
$role->syncPermissions($request->input('permission'));
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse(trans('Role created Successfully'));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($e, "Role Controller -> store");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('role-list');
|
||||||
|
|
||||||
|
$role = Role::findOrFail($id);
|
||||||
|
|
||||||
|
$rolePermissions = Permission::join("role_has_permissions", "role_has_permissions.permission_id", "=", "permissions.id")
|
||||||
|
->where("role_has_permissions.role_id", $id)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$formattedPermissions = $rolePermissions->map(function ($permission) {
|
||||||
|
// Split only on the LAST hyphen
|
||||||
|
$lastHyphenPos = strrpos($permission->name, '-');
|
||||||
|
|
||||||
|
if ($lastHyphenPos !== false) {
|
||||||
|
$group = substr($permission->name, 0, $lastHyphenPos); // e.g., "seller-verification-field"
|
||||||
|
$action = substr($permission->name, $lastHyphenPos + 1); // e.g., "edit"
|
||||||
|
} else {
|
||||||
|
$group = $permission->name;
|
||||||
|
$action = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate using full group key (with hyphens)
|
||||||
|
return [
|
||||||
|
'group' => __($group),
|
||||||
|
'action' => __($action),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return view('roles.show', compact('role', 'formattedPermissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function edit($id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('role-edit');
|
||||||
|
$role = Role::findOrFail($id);
|
||||||
|
$permission = Permission::get();
|
||||||
|
$rolePermissions = DB::table("role_has_permissions")->where("role_has_permissions.role_id", $id)->pluck('role_has_permissions.permission_id', 'role_has_permissions.permission_id')->all();
|
||||||
|
$groupedPermissions = [];
|
||||||
|
foreach ($permission as $key => $val) {
|
||||||
|
$subArr = substr($val->name, 0, strrpos($val->name, "-"));
|
||||||
|
$groupedPermissions[$subArr][] = (object)array(
|
||||||
|
...$val->toArray(),
|
||||||
|
'short_name' => str_replace($subArr . "-", "", $val->name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$groupedPermissions = (object)$groupedPermissions;
|
||||||
|
return view('roles.edit', compact('role', 'groupedPermissions', 'rolePermissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('role-edit');
|
||||||
|
$validator = Validator::make($request->all(), ['name' => 'required', 'permission' => 'required']);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
if (in_array($request->name, $this->reserveRole, true)) {
|
||||||
|
ResponseService::errorResponse($request->name . " " . trans("is not a valid Role name Because it's Reserved Role"));
|
||||||
|
}
|
||||||
|
$role = Role::findOrFail($id);
|
||||||
|
$role->name = $request->input('name');
|
||||||
|
$role->save();
|
||||||
|
|
||||||
|
$role->syncPermissions($request->input('permission'));
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('Data Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th, "RoleController -> update");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id) {
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('role-delete');
|
||||||
|
$role = Role::withCount('users')->findOrFail($id);
|
||||||
|
if ($role->users_count) {
|
||||||
|
ResponseService::errorResponse('cannot_delete_because_data_is_associated_with_other_data');
|
||||||
|
} else {
|
||||||
|
Role::findOrFail($id)->delete();
|
||||||
|
ResponseService::successResponse('Data Deleted Successfully');
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($e);
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
app/Http/Controllers/SellerController.php
Normal file
176
app/Http/Controllers/SellerController.php
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\SellerRating;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Throwable;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
|
class SellerController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['seller-review-list', 'seller-review-update', 'seller-review-delete']);
|
||||||
|
return view('seller_review.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['seller-review-list', 'seller-review-update', 'seller-review-delete']);
|
||||||
|
return view('seller_review.report');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-review-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
|
||||||
|
$sql = SellerRating::with(['seller:id,name', 'buyer:id,name', 'item:id,name']);
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
|
||||||
|
$sql = $sql->sort($sort, $order)->skip($offset)->take($limit);
|
||||||
|
|
||||||
|
$result = $sql->get();
|
||||||
|
|
||||||
|
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['seller_name'] = $row->seller->name ?? '';
|
||||||
|
$tempRow['buyer_name'] = $row->buyer->name ?? '';
|
||||||
|
$tempRow['item_name'] = $row->item->name;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "ItemController --> show");
|
||||||
|
return ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showReports(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-review-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
|
||||||
|
$sql = SellerRating::with(['seller:id,name', 'buyer:id,name', 'item:id,name'])->whereNotNull('report_status')->withTrashed();
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($request->filter)) {
|
||||||
|
$sql = $sql->filter(json_decode($request->filter, false, 512, JSON_THROW_ON_ERROR));
|
||||||
|
}
|
||||||
|
$sql->sort($sort, $order);
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
$total = $sql->count();
|
||||||
|
$result = $sql->skip($offset)->take($limit)->get();
|
||||||
|
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['seller_name'] = $row->seller->name;
|
||||||
|
$tempRow['buyer_name'] = $row->buyer->name;
|
||||||
|
$tempRow['item_name'] = $row->item->name;
|
||||||
|
$tempRow['operate'] = BootstrapTableService::editButton(route('seller-review.update', $row->id), true, '#editStatusModal', 'edit-status', $row->id);
|
||||||
|
|
||||||
|
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "SellerController --> showSellersWithRatings");
|
||||||
|
return ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, string $id)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'report_status' => 'required|in:approved,rejected',
|
||||||
|
'report_rejected_reason' => 'required_if:report_status,==,rejected'
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('item-update');
|
||||||
|
$seller_rating = SellerRating::withTrashed()->findOrFail($id);
|
||||||
|
$seller_rating->update([
|
||||||
|
...$request->all(),
|
||||||
|
// 'report_rejected_reason' => ($request->status == "rejected") ? $request->report_rejected_reason : ''
|
||||||
|
]);
|
||||||
|
if ($request->report_status == "approved") {
|
||||||
|
$seller_rating->forceDelete();
|
||||||
|
}
|
||||||
|
ResponseService::successResponse('Report Status Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'SellerController ->update');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
239
app/Http/Controllers/SeoSettingController.php
Normal file
239
app/Http/Controllers/SeoSettingController.php
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
use App\Models\SeoSetting;
|
||||||
|
use App\Models\SeoSettingsTranslation;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class SeoSettingController extends Controller
|
||||||
|
{
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->uploadFolder = "seo-setting";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make(
|
||||||
|
$request->all(),
|
||||||
|
[
|
||||||
|
'page' => 'required|unique:seo_settings,page',
|
||||||
|
'title.1' => 'required|string',
|
||||||
|
'description.1' => 'required|string',
|
||||||
|
'keywords.1' => 'nullable|string',
|
||||||
|
'image' => 'nullable|mimes:jpeg,png,jpg,svg|max:7168',
|
||||||
|
'languages' => 'required|array',
|
||||||
|
'languages.*' => 'exists:languages,id',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'page.unique' => 'This page already has SEO settings.',
|
||||||
|
'title.1.required' => 'The English title field is required.',
|
||||||
|
'description.1.required' => 'The English description field is required.',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = $request->all();
|
||||||
|
|
||||||
|
// Handle image upload
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::upload($request->file('image'), $this->uploadFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store main SEO setting (language_id = 1)
|
||||||
|
$seoSetting = SeoSetting::create([
|
||||||
|
'page' => $data['page'],
|
||||||
|
'title' => $data['title'][1],
|
||||||
|
'description' => $data['description'][1],
|
||||||
|
'keywords' => $data['keywords'][1] ?? null,
|
||||||
|
'image' => $data['image'] ?? null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Store translations for other languages
|
||||||
|
foreach ($data['languages'] as $langId) {
|
||||||
|
if ($langId == 1) continue; // Skip default language
|
||||||
|
|
||||||
|
$title = $data['title'][$langId] ?? null;
|
||||||
|
$description = $data['description'][$langId] ?? null;
|
||||||
|
$keywords = $data['keywords'][$langId] ?? null;
|
||||||
|
|
||||||
|
// Skip empty translations
|
||||||
|
if (empty($title) && empty($description) && empty($keywords)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SeoSettingsTranslation::create([
|
||||||
|
'seo_setting_id' => $seoSetting->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'title' => $title,
|
||||||
|
'description' => $description,
|
||||||
|
'keywords' => $keywords,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseService::successResponse('SEO Setting Successfully Added');
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th, "SeoSetting Controller -> Store");
|
||||||
|
return ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = SeoSetting::with('translations')->orderBy($sort, $order);
|
||||||
|
|
||||||
|
if (!empty($_GET['search'])) {
|
||||||
|
$search = $_GET['search'];
|
||||||
|
$sql->where('id', 'LIKE', "%$search%")->orwhere('code', 'LIKE', "%$search%")->orwhere('name', 'LIKE', "%$search%");
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if ($row->code != "en") {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('seo-setting.update', $row->id), true);
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('seo-setting.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$validator = Validator::make(
|
||||||
|
$request->all(),
|
||||||
|
[
|
||||||
|
|
||||||
|
'title.1' => 'required|string',
|
||||||
|
'description.1' => 'required|string',
|
||||||
|
'image' => 'nullable|mimes:jpeg,png,jpg,svg|max:7168',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
|
||||||
|
'title.1.required' => 'The English title field is required.',
|
||||||
|
'description.1.required' => 'The English description field is required.',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$seo = SeoSetting::findOrFail($id);
|
||||||
|
|
||||||
|
$data = $request->only('page');
|
||||||
|
if ($request->hasFile('image')) {
|
||||||
|
$data['image'] = FileService::upload($request->file('image'), $this->uploadFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save base (main) SEO setting
|
||||||
|
$seo->update($data);
|
||||||
|
|
||||||
|
// Update translation for each language
|
||||||
|
foreach ($request->input('languages', []) as $langId) {
|
||||||
|
$translatedTitle = $request->input("title.$langId");
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
$translatedKeywords = $request->input("keywords.$langId");
|
||||||
|
|
||||||
|
if ($langId == 1) {
|
||||||
|
// English (default)
|
||||||
|
$seo->update([
|
||||||
|
'title' => $translatedTitle,
|
||||||
|
'description' => $translatedDescription,
|
||||||
|
'keywords' => $translatedKeywords,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$seo->translations()->updateOrCreate(
|
||||||
|
['language_id' => $langId],
|
||||||
|
[
|
||||||
|
'title' => $translatedTitle,
|
||||||
|
'description' => $translatedDescription,
|
||||||
|
'keywords' => $translatedKeywords,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseService::successResponse('SEO Setting Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
return ResponseService::logErrorRedirect($th, "SeoSetting Controller -> Update");
|
||||||
|
return ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$seo_setting = SeoSetting::findOrFail($id);
|
||||||
|
$seo_setting->delete();
|
||||||
|
FileService::delete($seo_setting->getRawOriginal('image'));
|
||||||
|
ResponseService::successResponse('Seo Setting Deleted successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorRedirect($th, "Language Controller --> Destroy");
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
886
app/Http/Controllers/SettingController.php
Normal file
886
app/Http/Controllers/SettingController.php
Normal file
@@ -0,0 +1,886 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use File;
|
||||||
|
use Throwable;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Models\Currency;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\HelperService;
|
||||||
|
use App\Jobs\ImportDummyDataJob;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use App\Models\SettingTranslation;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use App\Models\PaymentConfiguration;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class SettingController extends Controller
|
||||||
|
{
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->uploadFolder = 'settings';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('settings-update');
|
||||||
|
|
||||||
|
return view('settings.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function page()
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
$type = last(request()->segments());
|
||||||
|
$settings = CachingService::getSystemSettings()->toArray();
|
||||||
|
if (! empty($settings['place_api_key']) && config('app.demo_mode')) {
|
||||||
|
$settings['place_api_key'] = '**************************';
|
||||||
|
}
|
||||||
|
$stripe_currencies = ['USD', 'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', 'BDT', 'BGN', 'BIF', 'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BWP', 'BYN', 'BZD', 'CAD', 'CDF', 'CHF', 'CLP', 'CNY', 'COP', 'CRC', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EGP', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', 'HKD', 'HNL', 'HTG', 'HUF', 'IDR', 'ILS', 'INR', 'ISK', 'JMD', 'JPY', 'KES', 'KGS', 'KHR', 'KMF', 'KRW', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'MAD', 'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRO', 'MUR', 'MVR', 'MWK', 'MXN', 'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG', 'QAR', 'RON', 'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SEK', 'SGD', 'SHP', 'SLE', 'SOS', 'SRD', 'STD', 'SZL', 'THB', 'TJS', 'TOP', 'TTD', 'TWD', 'TZS', 'UAH', 'UGX', 'UYU', 'UZS', 'VND', 'VUV', 'WST', 'XAF', 'XCD', 'XOF', 'XPF', 'YER', 'ZAR', 'ZMW'];
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$translations = $this->getSettingTranslations();
|
||||||
|
|
||||||
|
$languages_translate = CachingService::getLanguages()->where('code', '!=', 'en')->values();
|
||||||
|
|
||||||
|
$currencies = Currency::select(['id', 'iso_code'])->get();
|
||||||
|
|
||||||
|
// Prepare watermark settings for watermark-settings page
|
||||||
|
$watermarkSettings = [];
|
||||||
|
if ($type === 'watermark-settings') {
|
||||||
|
// Get watermark image URL (Setting model already transforms file paths to URLs)
|
||||||
|
$watermarkImageUrl = $settings['watermark_image'] ?? null;
|
||||||
|
// Extract filename for display if needed
|
||||||
|
$watermarkImageFilename = null;
|
||||||
|
if ($watermarkImageUrl) {
|
||||||
|
// Extract filename from URL or path
|
||||||
|
$watermarkImageFilename = basename(parse_url($watermarkImageUrl, PHP_URL_PATH));
|
||||||
|
}
|
||||||
|
|
||||||
|
$watermarkSettings = [
|
||||||
|
'enabled' => $settings['watermark_enabled'] ?? 0,
|
||||||
|
'watermark_image' => $watermarkImageFilename,
|
||||||
|
'watermark_image_url' => $watermarkImageUrl,
|
||||||
|
'opacity' => $settings['watermark_opacity'] ?? 25,
|
||||||
|
'size' => $settings['watermark_size'] ?? 10,
|
||||||
|
'style' => $settings['watermark_style'] ?? 'tile',
|
||||||
|
'position' => $settings['watermark_position'] ?? 'center',
|
||||||
|
'rotation' => $settings['watermark_rotation'] ?? -30,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('settings.' . $type, compact('settings', 'type', 'languages', 'stripe_currencies', 'languages_translate', 'translations', 'watermarkSettings', 'currencies'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSettingTranslations()
|
||||||
|
{
|
||||||
|
$settings = Setting::with('translations')->get();
|
||||||
|
|
||||||
|
$translations = [];
|
||||||
|
|
||||||
|
foreach ($settings as $setting) {
|
||||||
|
foreach ($setting->translations as $translation) {
|
||||||
|
$translations[$setting->name][$translation->language_id] = $translation->translated_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'company_name' => 'nullable',
|
||||||
|
'company_email' => 'nullable',
|
||||||
|
'company_tel1' => 'nullable',
|
||||||
|
'company_tel2' => 'nullable',
|
||||||
|
'company_address' => 'nullable',
|
||||||
|
'default_language' => 'nullable',
|
||||||
|
'currency_symbol' => 'nullable',
|
||||||
|
'android_version' => 'nullable',
|
||||||
|
'play_store_link' => 'nullable',
|
||||||
|
'ios_version' => 'nullable',
|
||||||
|
'app_store_link' => 'nullable',
|
||||||
|
'maintenance_mode' => 'nullable',
|
||||||
|
'force_update' => 'nullable',
|
||||||
|
'number_with_suffix' => 'nullable',
|
||||||
|
'firebase_project_id' => 'nullable',
|
||||||
|
'service_file' => 'nullable',
|
||||||
|
'favicon_icon' => 'nullable|mimes:jpg,jpeg,png,svg|max:7168',
|
||||||
|
'company_logo' => 'nullable|mimes:jpg,jpeg,png,svg|max:7168',
|
||||||
|
'login_image' => 'nullable|mimes:jpg,jpeg,png,svg|max:7168',
|
||||||
|
// "watermark_image" => 'nullable|mimes:jpg,jpeg,png|max:7168',
|
||||||
|
'web_theme_color' => 'nullable',
|
||||||
|
'place_api_key' => 'nullable',
|
||||||
|
'header_logo' => 'nullable|mimes:jpg,jpeg,png,svg|max:7168',
|
||||||
|
'footer_logo' => 'nullable|mimes:jpg,jpeg,png,svg|max:7168',
|
||||||
|
'placeholder_image' => 'nullable|mimes:jpg,jpeg,png,svg|max:7168',
|
||||||
|
'footer_description' => 'nullable',
|
||||||
|
'google_map_iframe_link' => 'nullable',
|
||||||
|
'default_latitude' => 'nullable',
|
||||||
|
'default_longitude' => 'nullable',
|
||||||
|
'instagram_link' => 'nullable|url',
|
||||||
|
'x_link' => 'nullable|url',
|
||||||
|
'facebook_link' => 'nullable|url',
|
||||||
|
'linkedin_link' => 'nullable|url',
|
||||||
|
'pinterest_link' => 'nullable|url',
|
||||||
|
'deep_link_text_file' => 'nullable',
|
||||||
|
'deep_link_json_file' => 'nullable|mimes:json|max:7168',
|
||||||
|
'mobile_authentication' => 'nullable',
|
||||||
|
'google_authentication' => 'nullable',
|
||||||
|
'email_authentication' => 'nullable',
|
||||||
|
'apple_authenticaion' => 'nullable',
|
||||||
|
// Email settings validation
|
||||||
|
'mail_mailer' => 'nullable',
|
||||||
|
'mail_host' => 'nullable',
|
||||||
|
'mail_port' => 'nullable',
|
||||||
|
'mail_username' => 'nullable',
|
||||||
|
'mail_password' => 'nullable',
|
||||||
|
'mail_encryption' => 'nullable',
|
||||||
|
'mail_from_address' => 'nullable|email',
|
||||||
|
'deep_link_scheme' => 'nullable|string|regex:/^[a-z][a-z0-9]*$/|max:30',
|
||||||
|
'otp_service_provider' => 'nullable|in:firebase,twilio',
|
||||||
|
'twilio_account_sid' => 'nullable',
|
||||||
|
'twilio_auth_token' => 'nullable',
|
||||||
|
'twilio_my_phone_number' => 'nullable',
|
||||||
|
'admin_user_email' => 'nullable|email',
|
||||||
|
'admin_user_password' => 'nullable',
|
||||||
|
'currency_iso_code' => 'nullable|string',
|
||||||
|
'free_ad_unlimited' => 'sometimes|nullable|boolean',
|
||||||
|
'free_ad_duration_days' => 'sometimes|nullable|integer|min:1|required_if:free_ad_unlimited,0',
|
||||||
|
]);
|
||||||
|
if (
|
||||||
|
$request->has('mobile_authentication') && $request->mobile_authentication == 0 &&
|
||||||
|
$request->has('google_authentication') && $request->google_authentication == 0 &&
|
||||||
|
$request->has('email_authentication') && $request->email_authentication == 0 &&
|
||||||
|
$request->has('apple_authentication') && $request->apple_authentication == 0
|
||||||
|
) {
|
||||||
|
ResponseService::validationError('At least one authentication method must be enabled.');
|
||||||
|
}
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$inputs = $request->input();
|
||||||
|
// Validate email & password BEFORE creating ad
|
||||||
|
// Validate admin user email & password
|
||||||
|
if ($request->filled('admin_user_email')) {
|
||||||
|
$user = User::where('email', $request->admin_user_email)->first();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
ResponseService::errorResponse('No account found with this email.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($inputs['_token']);
|
||||||
|
if (config('app.demo_mode')) {
|
||||||
|
unset($inputs['place_api_key']);
|
||||||
|
}
|
||||||
|
$data = [];
|
||||||
|
foreach ($inputs as $key => $input) {
|
||||||
|
if (in_array($key, ['translations', 'about_us', 'languages', 'contact_us', 'privacy_policy', 'refund_policy', 'terms_conditions'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$data[] = [
|
||||||
|
'name' => $key,
|
||||||
|
'value' => $input,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldSettingFiles = Setting::whereIn('name', collect($request->files)->keys())->get();
|
||||||
|
foreach ($request->files as $key => $file) {
|
||||||
|
|
||||||
|
if (in_array($key, ['deep_link_json_file', 'deep_link_text_file'])) {
|
||||||
|
$filenameMap = [
|
||||||
|
'deep_link_json_file' => 'assetlinks.json',
|
||||||
|
'deep_link_text_file' => 'apple-app-site-association',
|
||||||
|
];
|
||||||
|
|
||||||
|
$filename = $filenameMap[$key];
|
||||||
|
$fileContents = File::get($file);
|
||||||
|
$publicWellKnownPath = public_path('.well-known');
|
||||||
|
if (! File::exists($publicWellKnownPath)) {
|
||||||
|
File::makeDirectory($publicWellKnownPath, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$publicPath = public_path('.well-known/' . $filename);
|
||||||
|
File::put($publicPath, $fileContents);
|
||||||
|
|
||||||
|
$rootPath = base_path('.well-known/' . $filename);
|
||||||
|
File::put($rootPath, $fileContents);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$data[] = [
|
||||||
|
'name' => $key,
|
||||||
|
'value' => FileService::compressAndUpload($request->file($key), $this->uploadFolder),
|
||||||
|
// 'value' => $request->file($key)->store($this->uploadFolder, 'public'),
|
||||||
|
'type' => 'file',
|
||||||
|
];
|
||||||
|
$oldFile = $oldSettingFiles->first(function ($old) use ($key) {
|
||||||
|
return $old->name == $key;
|
||||||
|
});
|
||||||
|
if (! empty($oldFile)) {
|
||||||
|
FileService::delete($oldFile->getRawOriginal('value'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (($inputs['free_ad_duration_days'] ?? null) != 'free_ad_duration_days') {
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'free_ad_duration_days',
|
||||||
|
'value' => $inputs['free_ad_duration_days'] ?? 'unlimited',
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Unlimited
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'free_ad_duration_days',
|
||||||
|
'value' => "unlimited",
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
Setting::upsert($data, 'name', ['value']);
|
||||||
|
|
||||||
|
if (! empty($inputs['company_name']) && config('app.name') != $inputs['company_name']) {
|
||||||
|
HelperService::changeEnv([
|
||||||
|
'APP_NAME' => $inputs['company_name'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update .env file for email settings
|
||||||
|
$emailSettings = [
|
||||||
|
'MAIL_MAILER' => $inputs['mail_mailer'] ?? config('mail.mailer'),
|
||||||
|
'MAIL_HOST' => $inputs['mail_host'] ?? config('mail.host'),
|
||||||
|
'MAIL_PORT' => $inputs['mail_port'] ?? config('mail.port'),
|
||||||
|
'MAIL_USERNAME' => $inputs['mail_username'] ?? config('mail.username'),
|
||||||
|
'MAIL_PASSWORD' => $inputs['mail_password'] ?? config('mail.password'),
|
||||||
|
'MAIL_ENCRYPTION' => $inputs['mail_encryption'] ?? config('mail.encryption'),
|
||||||
|
'MAIL_FROM_ADDRESS' => $inputs['mail_from_address'] ?? config('mail.from.address'),
|
||||||
|
];
|
||||||
|
$filteredSettings = array_filter($emailSettings, function ($value) {
|
||||||
|
return ! is_null($value) && $value !== '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only update env if there's something to update
|
||||||
|
if (! empty($filteredSettings)) {
|
||||||
|
HelperService::changeEnv($filteredSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($inputs['otp_service_provider']) && $inputs['otp_service_provider'] === 'twilio') {
|
||||||
|
HelperService::changeEnv([
|
||||||
|
'TWILIO_ACCOUNT_SID' => $inputs['twilio_account_sid'] ?? config('services.twilio.account_sid'),
|
||||||
|
'TWILIO_AUTH_TOKEN' => $inputs['twilio_auth_token'] ?? config('services.twilio.auth_token'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('about_us')) {
|
||||||
|
$aboutUsInputs = $request->input('about_us', []);
|
||||||
|
|
||||||
|
// Save default About Us (first language or fallback)
|
||||||
|
$defaultAboutUs = reset($aboutUsInputs);
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['name' => 'about_us'],
|
||||||
|
['value' => $defaultAboutUs, 'type' => 'string']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save translations
|
||||||
|
foreach ($aboutUsInputs as $languageId => $value) {
|
||||||
|
$setting = Setting::where('name', 'about_us')->first();
|
||||||
|
if ($setting) {
|
||||||
|
SettingTranslation::updateOrCreate(
|
||||||
|
['setting_id' => $setting->id, 'language_id' => $languageId],
|
||||||
|
['translated_value' => $value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($request->has('contact_us')) {
|
||||||
|
$contactUsInputs = $request->input('contact_us', []);
|
||||||
|
|
||||||
|
// Save default Contact Us
|
||||||
|
$defaultContactUs = reset($contactUsInputs);
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['name' => 'contact_us'],
|
||||||
|
['value' => $defaultContactUs, 'type' => 'string']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save translations
|
||||||
|
foreach ($contactUsInputs as $languageId => $value) {
|
||||||
|
$setting = Setting::where('name', 'contact_us')->first();
|
||||||
|
if ($setting) {
|
||||||
|
SettingTranslation::updateOrCreate(
|
||||||
|
['setting_id' => $setting->id, 'language_id' => $languageId],
|
||||||
|
['translated_value' => $value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($request->has('privacy_policy')) {
|
||||||
|
$privacyInputs = $request->input('privacy_policy', []);
|
||||||
|
|
||||||
|
// Save default Privacy Policy
|
||||||
|
$defaultPrivacy = reset($privacyInputs);
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['name' => 'privacy_policy'],
|
||||||
|
['value' => $defaultPrivacy, 'type' => 'string']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save translations
|
||||||
|
foreach ($privacyInputs as $languageId => $value) {
|
||||||
|
$setting = Setting::where('name', 'privacy_policy')->first();
|
||||||
|
if ($setting) {
|
||||||
|
SettingTranslation::updateOrCreate(
|
||||||
|
['setting_id' => $setting->id, 'language_id' => $languageId],
|
||||||
|
['translated_value' => $value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($request->has('refund_policy')) {
|
||||||
|
$refundInputs = $request->input('refund_policy', []);
|
||||||
|
|
||||||
|
// Save default Refund Policy
|
||||||
|
$defaultRefund = reset($refundInputs);
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['name' => 'refund_policy'],
|
||||||
|
['value' => $defaultRefund, 'type' => 'string']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save translations
|
||||||
|
foreach ($refundInputs as $languageId => $value) {
|
||||||
|
$setting = Setting::where('name', 'refund_policy')->first();
|
||||||
|
if ($setting) {
|
||||||
|
SettingTranslation::updateOrCreate(
|
||||||
|
['setting_id' => $setting->id, 'language_id' => $languageId],
|
||||||
|
['translated_value' => $value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has('terms_conditions')) {
|
||||||
|
$termsInputs = $request->input('terms_conditions', []);
|
||||||
|
|
||||||
|
// Save default Terms & Conditions
|
||||||
|
$defaultTerms = reset($termsInputs);
|
||||||
|
Setting::updateOrCreate(
|
||||||
|
['name' => 'terms_conditions'],
|
||||||
|
['value' => $defaultTerms, 'type' => 'string']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save translations
|
||||||
|
foreach ($termsInputs as $languageId => $value) {
|
||||||
|
$setting = Setting::where('name', 'terms_conditions')->first();
|
||||||
|
if ($setting) {
|
||||||
|
SettingTranslation::updateOrCreate(
|
||||||
|
['setting_id' => $setting->id, 'language_id' => $languageId],
|
||||||
|
['translated_value' => $value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($request->has('translations')) {
|
||||||
|
foreach ($request->input('translations') as $languageId => $translationData) {
|
||||||
|
$setting = Setting::where('name', $translationData['name'])->first();
|
||||||
|
|
||||||
|
if ($setting) {
|
||||||
|
SettingTranslation::updateOrCreate(
|
||||||
|
[
|
||||||
|
'setting_id' => $setting->id,
|
||||||
|
'language_id' => $languageId,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'translated_value' => $translationData['value'] ?? null,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CachingService::removeCache(config('constants.CACHE.SETTINGS'));
|
||||||
|
ResponseService::successResponse('Settings Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Setting Controller -> store');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateFirebaseSettings(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'apiKey' => 'required',
|
||||||
|
'authDomain' => 'required',
|
||||||
|
'projectId' => 'required',
|
||||||
|
'storageBucket' => 'required',
|
||||||
|
'messagingSenderId' => 'required',
|
||||||
|
'appId' => 'required',
|
||||||
|
'measurementId' => 'required',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$inputs = $request->input();
|
||||||
|
unset($inputs['_token']);
|
||||||
|
$data = [];
|
||||||
|
foreach ($inputs as $key => $input) {
|
||||||
|
$data[] = [
|
||||||
|
'name' => $key,
|
||||||
|
'value' => $input,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
Setting::upsert($data, 'name', ['value']);
|
||||||
|
// Service worker file will be copied here
|
||||||
|
File::copy(public_path('assets/dummy-firebase-messaging-sw.js'), public_path('firebase-messaging-sw.js'));
|
||||||
|
$serviceWorkerFile = file_get_contents(public_path('firebase-messaging-sw.js'));
|
||||||
|
|
||||||
|
$updateFileStrings = [
|
||||||
|
'apiKeyValue' => '"' . $request->apiKey . '"',
|
||||||
|
'authDomainValue' => '"' . $request->authDomain . '"',
|
||||||
|
'projectIdValue' => '"' . $request->projectId . '"',
|
||||||
|
'storageBucketValue' => '"' . $request->storageBucket . '"',
|
||||||
|
'messagingSenderIdValue' => '"' . $request->measurementId . '"',
|
||||||
|
'appIdValue' => '"' . $request->appId . '"',
|
||||||
|
'measurementIdValue' => '"' . $request->measurementId . '"',
|
||||||
|
];
|
||||||
|
$serviceWorkerFile = str_replace(array_keys($updateFileStrings), $updateFileStrings, $serviceWorkerFile);
|
||||||
|
file_put_contents(public_path('firebase-messaging-sw.js'), $serviceWorkerFile);
|
||||||
|
CachingService::removeCache(config('constants.CACHE.SETTINGS'));
|
||||||
|
ResponseService::successResponse('Settings Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Settings Controller -> updateFirebaseSettings');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentSettingsIndex()
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('settings-update');
|
||||||
|
$paymentConfiguration = PaymentConfiguration::all();
|
||||||
|
$paymentGateway = [];
|
||||||
|
foreach ($paymentConfiguration as $row) {
|
||||||
|
$paymentGateway[$row->payment_method] = $row->toArray();
|
||||||
|
}
|
||||||
|
$settings = CachingService::getSystemSettings()->toArray();
|
||||||
|
|
||||||
|
return view('settings.payment-gateway', compact('paymentGateway', 'settings'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paymentSettingsStore(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'gateway' => 'required|array',
|
||||||
|
'gateway.Stripe' => 'required|array|required_array_keys:api_key,secret_key,webhook_secret_key,status',
|
||||||
|
'gateway.Razorpay' => 'required|array|required_array_keys:api_key,secret_key,webhook_secret_key,status',
|
||||||
|
'gateway.Paystack' => 'required|array|required_array_keys:api_key,secret_key,status',
|
||||||
|
'gateway.PhonePe' => 'required|array|required_array_keys:secret_key,api_key,additional_data_1,username,password,payment_mode,status',
|
||||||
|
'bank' => 'required|array',
|
||||||
|
]);
|
||||||
|
$gatewayStatuses = [
|
||||||
|
$request->input('gateway.Stripe.status', 0),
|
||||||
|
$request->input('gateway.Razorpay.status', 0),
|
||||||
|
$request->input('gateway.Paystack.status', 0),
|
||||||
|
$request->input('gateway.PhonePe.status', 0),
|
||||||
|
$request->input('gateway.flutterwave.status', 0),
|
||||||
|
$request->input('gateway.Paypal.status', 0),
|
||||||
|
$request->input('bank.bank_transfer_status', 0),
|
||||||
|
];
|
||||||
|
if (! in_array('1', $gatewayStatuses, true)) {
|
||||||
|
ResponseService::validationError('At least one payment gateway must be enabled.');
|
||||||
|
}
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
foreach ($request->input('bank') as $key => $value) {
|
||||||
|
Setting::updateOrCreate(['name' => $key], ['value' => $value]);
|
||||||
|
}
|
||||||
|
foreach ($request->gateway as $key => $gateway) {
|
||||||
|
PaymentConfiguration::updateOrCreate(['payment_method' => $key], [
|
||||||
|
'api_key' => $gateway['api_key'] ?? '',
|
||||||
|
'secret_key' => $gateway['secret_key'] ?? '',
|
||||||
|
'webhook_secret_key' => $gateway['webhook_secret_key'] ?? '',
|
||||||
|
'status' => $gateway['status'] ?? '',
|
||||||
|
'currency_code' => $gateway['currency_code'] ?? '',
|
||||||
|
'additional_data_1' => $gateway['additional_data_1'] ?? '',
|
||||||
|
'additional_data_2' => $gateway['additional_data_2'] ?? '',
|
||||||
|
'payment_mode' => $gateway['payment_mode'] ?? '',
|
||||||
|
'username' => $gateway['username'] ?? '',
|
||||||
|
'password' => $gateway['password'] ?? '',
|
||||||
|
|
||||||
|
]);
|
||||||
|
if ($key === 'Paystack') {
|
||||||
|
HelperService::changeEnv([
|
||||||
|
'PAYSTACK_PUBLIC_KEY' => $gateway['api_key'] ?? '',
|
||||||
|
'PAYSTACK_SECRET_KEY' => $gateway['secret_key'] ?? '',
|
||||||
|
'PAYSTACK_PAYMENT_URL' => 'https://api.paystack.co',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResponseService::successResponse('Settings Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Settings Controller -> updateFirebaseSettings');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function syatemStatusIndex() {
|
||||||
|
// return view('settings.system-status');
|
||||||
|
// }
|
||||||
|
public function toggleStorageLink()
|
||||||
|
{
|
||||||
|
$linkPath = public_path('storage');
|
||||||
|
|
||||||
|
if (file_exists($linkPath)) {
|
||||||
|
if (is_link($linkPath)) {
|
||||||
|
if (unlink($linkPath)) {
|
||||||
|
return back()->with('message', 'Storage link unlinked successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('message', 'Failed to unlink the storage link.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('message', 'Storage link is not a symbolic link.');
|
||||||
|
} else {
|
||||||
|
Artisan::call('storage:link');
|
||||||
|
|
||||||
|
if (file_exists($linkPath) && is_link($linkPath)) {
|
||||||
|
return back()->with('message', 'Storage link created successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('message', 'Failed to create the storage link.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function systemStatus()
|
||||||
|
{
|
||||||
|
$linkPath = public_path('storage');
|
||||||
|
$isLinked = file_exists($linkPath) && is_dir($linkPath);
|
||||||
|
|
||||||
|
return view('settings.system-status', compact('isLinked'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fileManagerSettingStore(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'file_manager' => 'required|in:public,s3',
|
||||||
|
'S3_aws_access_key_id' => 'required_if:file_manager,==,s3',
|
||||||
|
's3_aws_secret_access_key' => 'required_if:file_manager,==,s3',
|
||||||
|
's3_aws_default_region' => 'required_if:file_manager,==,s3',
|
||||||
|
's3_aws_bucket' => 'required_if:file_manager,==,s3',
|
||||||
|
's3_aws_url' => 'required_if:file_manager,==,s3',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$inputs = $request->input();
|
||||||
|
$data = [];
|
||||||
|
foreach ($inputs as $key => $input) {
|
||||||
|
$data[] = [
|
||||||
|
'name' => $key,
|
||||||
|
'value' => $input,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
Setting::upsert($data, 'name', ['value']);
|
||||||
|
|
||||||
|
$env = [
|
||||||
|
'FILESYSTEM_DISK' => $inputs['file_manager'],
|
||||||
|
'AWS_ACCESS_KEY_ID' => $inputs['S3_aws_access_key_id'] ?? null,
|
||||||
|
'AWS_SECRET_ACCESS_KEY' => $inputs['s3_aws_secret_access_key'] ?? null,
|
||||||
|
'AWS_DEFAULT_REGION' => $inputs['s3_aws_default_region'] ?? null,
|
||||||
|
'AWS_BUCKET' => $inputs['s3_aws_bucket'] ?? null,
|
||||||
|
'AWS_URL' => $inputs['s3_aws_url'] ?? null,
|
||||||
|
];
|
||||||
|
|
||||||
|
HelperService::changeEnv($env);
|
||||||
|
ResponseService::successResponse('File Manager Settings Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Setting Controller -> fileManagerSettingStore');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paystackPaymentSucesss()
|
||||||
|
{
|
||||||
|
return view('payment.paystack');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phonepePaymentSucesss()
|
||||||
|
{
|
||||||
|
return view('payment.phonepe');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function webPageURL($slug)
|
||||||
|
{
|
||||||
|
$appStoreLink = CachingService::getSystemSettings('app_store_link');
|
||||||
|
$playStoreLink = CachingService::getSystemSettings('play_store_link');
|
||||||
|
$appName = CachingService::getSystemSettings('company_name');
|
||||||
|
$scheme = CachingService::getSystemSettings('deep_link_scheme');
|
||||||
|
|
||||||
|
return view('deep-link.deep_link', compact('appStoreLink', 'playStoreLink', 'appName', 'scheme'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flutterWavePaymentSucesss()
|
||||||
|
{
|
||||||
|
return view('payment.flutterwave');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dummyDataIndex()
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('settings-update');
|
||||||
|
|
||||||
|
return view('settings.dummy-data');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importDummyData(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log::info('🚀 Dummy data import request received. Preparing to start background process.');
|
||||||
|
|
||||||
|
// CRITICAL: Continue execution even if client disconnects
|
||||||
|
// This works on PHP 4+ and is essential for background jobs
|
||||||
|
ignore_user_abort(true);
|
||||||
|
|
||||||
|
// Remove execution time limit (PHP 4+)
|
||||||
|
// 0 means unlimited, but some hosts may override this
|
||||||
|
if (function_exists('set_time_limit')) {
|
||||||
|
@set_time_limit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send response to client
|
||||||
|
response()->json([
|
||||||
|
'error' => false,
|
||||||
|
'message' => trans('⏳ Dummy data import started in background. You can continue using the panel — it will complete automatically.'),
|
||||||
|
'data' => null,
|
||||||
|
'code' => config('constants.RESPONSE_CODE.SUCCESS'),
|
||||||
|
])->send();
|
||||||
|
|
||||||
|
Log::info('📤 Dummy data response sent to client. Background process starting...');
|
||||||
|
|
||||||
|
// Flush ALL output buffers (handles nested buffers)
|
||||||
|
// This is critical - previous code only flushed one level
|
||||||
|
while (ob_get_level() > 0) {
|
||||||
|
ob_end_flush();
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
|
||||||
|
// Handle background execution based on server type
|
||||||
|
if (function_exists('fastcgi_finish_request')) {
|
||||||
|
// FastCGI/PHP-FPM servers (Nginx, Apache with PHP-FPM)
|
||||||
|
// Available since PHP 5.3.3
|
||||||
|
Log::info('⚡ fastcgi_finish_request() is available. Finishing request and executing job immediately.');
|
||||||
|
|
||||||
|
fastcgi_finish_request();
|
||||||
|
|
||||||
|
usleep(10000); // 0.1 second delay
|
||||||
|
|
||||||
|
Log::info('📌 Executing ImportDummyDataJob directly (FastCGI mode).');
|
||||||
|
|
||||||
|
try {
|
||||||
|
(new ImportDummyDataJob)->handle();
|
||||||
|
Log::info('✅ ImportDummyDataJob completed successfully.');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error('❌ ImportDummyDataJob execution failed: ' . $th->getMessage());
|
||||||
|
Log::error('Stack trace: ' . $th->getTraceAsString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback for mod_php, CGI, or other server types
|
||||||
|
// register_shutdown_function() available since PHP 4
|
||||||
|
Log::info('🧵 fastcgi_finish_request() NOT available. Using shutdown function for background execution.');
|
||||||
|
|
||||||
|
// Store job instance - closure will capture it
|
||||||
|
$job = new ImportDummyDataJob;
|
||||||
|
|
||||||
|
register_shutdown_function(function () use ($job) {
|
||||||
|
try {
|
||||||
|
// Double-check fastcgi in case it becomes available
|
||||||
|
if (function_exists('fastcgi_finish_request')) {
|
||||||
|
fastcgi_finish_request();
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('🔄 Shutdown function triggered. Running ImportDummyDataJob.');
|
||||||
|
$job->handle();
|
||||||
|
Log::info('✅ ImportDummyDataJob completed in shutdown function.');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error('❌ Background job failed in shutdown function: ' . $th->getMessage());
|
||||||
|
Log::error('Stack trace: ' . $th->getTraceAsString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('✅ Dummy data import execution path completed. Background process should be running.');
|
||||||
|
|
||||||
|
// Exit to prevent further execution
|
||||||
|
// exit(0) is cleaner than exit() - indicates success
|
||||||
|
exit(0);
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error('❌ Dummy Data Import Controller Error: ' . $th->getMessage());
|
||||||
|
Log::error('Stack trace: ' . $th->getTraceAsString());
|
||||||
|
|
||||||
|
ResponseService::logErrorResponse($th, 'ApiController -> importDummyData');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function watermarkSettingsStore(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('settings-update');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Build validation rules dynamically based on style
|
||||||
|
$rules = [
|
||||||
|
'watermark_enabled' => 'nullable|in:0,1',
|
||||||
|
'watermark_image' => 'nullable|image|mimes:png,jpg,jpeg|max:3000',
|
||||||
|
'opacity' => 'required_if:watermark_enabled,1|numeric|min:0|max:100',
|
||||||
|
'size' => 'required_if:watermark_enabled,1|numeric|min:1|max:100',
|
||||||
|
'style' => 'required_if:watermark_enabled,1|in:tile,single,center',
|
||||||
|
'rotation' => 'nullable|numeric|min:-360|max:360',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Position is only required for 'single' and 'center' styles, not for 'tile'
|
||||||
|
$style = $request->input('style');
|
||||||
|
if ($style == 'single' || $style == 'tile') {
|
||||||
|
$rules['position'] = 'required_if:watermark_enabled,1|in:top-left,top-right,bottom-left,bottom-right,center';
|
||||||
|
} else {
|
||||||
|
// For 'tile' style, position is not required but we'll set a default
|
||||||
|
$rules['position'] = 'nullable|in:top-left,top-right,bottom-left,bottom-right,center';
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make($request->all(), $rules, [
|
||||||
|
'watermark_image.mimes' => trans('Image must be JPG, JPEG or PNG'),
|
||||||
|
'watermark_image.max' => trans('Image size must be less than 3MB'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get existing watermark image to delete if new one is uploaded
|
||||||
|
$oldWatermarkImage = Setting::where('name', 'watermark_image')->first();
|
||||||
|
$oldWatermarkPath = $oldWatermarkImage ? $oldWatermarkImage->getRawOriginal('value') : null;
|
||||||
|
|
||||||
|
// Store watermark settings individually in settings table
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
// Store watermark_enabled
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_enabled',
|
||||||
|
'value' => $request->watermark_enabled ?? 0,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Handle watermark image upload
|
||||||
|
if ($request->hasFile('watermark_image') && $request->file('watermark_image')->isValid()) {
|
||||||
|
// Delete old watermark image if exists
|
||||||
|
if (! empty($oldWatermarkPath)) {
|
||||||
|
FileService::delete($oldWatermarkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload new watermark image
|
||||||
|
$watermarkImagePath = FileService::compressAndUpload($request->file('watermark_image'), $this->uploadFolder);
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_image',
|
||||||
|
'value' => $watermarkImagePath,
|
||||||
|
'type' => 'file',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Keep existing watermark image if not uploading new one
|
||||||
|
if ($oldWatermarkImage) {
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_image',
|
||||||
|
'value' => $oldWatermarkPath,
|
||||||
|
'type' => 'file',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store other watermark settings
|
||||||
|
if ($request->filled('opacity')) {
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_opacity',
|
||||||
|
'value' => $request->opacity,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('size')) {
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_size',
|
||||||
|
'value' => $request->size,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->filled('style')) {
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_style',
|
||||||
|
'value' => $request->style,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle position - set default based on style
|
||||||
|
$style = $request->input('style');
|
||||||
|
$position = $request->input('position') ?? $request->input('position_hidden');
|
||||||
|
|
||||||
|
// If style is 'center', force position to 'center'
|
||||||
|
if ($style === 'center') {
|
||||||
|
$position = 'center';
|
||||||
|
} elseif ($style === 'tile') {
|
||||||
|
// For tile, position doesn't matter but set a default for consistency
|
||||||
|
$position = $position ?? 'center';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always save position (needed for watermark job)
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_position',
|
||||||
|
'value' => $position ?? 'center',
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($request->filled('rotation')) {
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_rotation',
|
||||||
|
'value' => $request->rotation ?? -30,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Set default rotation if not provided
|
||||||
|
$data[] = [
|
||||||
|
'name' => 'watermark_rotation',
|
||||||
|
'value' => -30,
|
||||||
|
'type' => 'string',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upsert all settings
|
||||||
|
Setting::upsert($data, 'name', ['value']);
|
||||||
|
|
||||||
|
// Clear cache
|
||||||
|
CachingService::removeCache(config('constants.CACHE.SETTINGS'));
|
||||||
|
|
||||||
|
ResponseService::successResponse(trans('Watermark Settings Updated Successfully'));
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Setting Controller -> watermarkSettingsStore');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
132
app/Http/Controllers/SliderController.php
Normal file
132
app/Http/Controllers/SliderController.php
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\Country;
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\Slider;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\FileService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class SliderController extends Controller
|
||||||
|
{
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->uploadFolder = 'sliders';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['slider-list', 'slider-create', 'slider-update', 'slider-delete']);
|
||||||
|
$slider = Slider::select(['id', 'image', 'sequence', 'country_id', 'state_id', 'city_id'])->orderBy('sequence', 'ASC')->with('country:id,name', 'state:id,name', 'city:id,name')->get();
|
||||||
|
$items = Item::where('status', 'approved')->get();
|
||||||
|
$categories = Category::where('status', 1)->get();
|
||||||
|
$countries = Country::select(['id', 'name'])->get();
|
||||||
|
|
||||||
|
return view('slider.index', compact('slider', 'items', 'categories', 'countries'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (! $request->filled('category_id') && ! $request->filled('item') && ! $request->filled('link')) {
|
||||||
|
ResponseService::validationError('At least one of the fields (Category, Advertisement, or Third Party Link) is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseService::noPermissionThenRedirect('slider-create');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'image.*' => 'required|image|mimes:jpg,png,jpeg|max:7168',
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
|
||||||
|
$lastSequence = Slider::max('sequence');
|
||||||
|
$nextSequence = $lastSequence + 1;
|
||||||
|
$slider = Slider::create([
|
||||||
|
'image' => $request->hasFile('image') ? FileService::compressAndUpload($request->file('image'), $this->uploadFolder) : '',
|
||||||
|
'third_party_link' => $request->link ?? '',
|
||||||
|
'sequence' => $nextSequence,
|
||||||
|
'country_id' => $request->country_id,
|
||||||
|
'state_id' => $request->state_id,
|
||||||
|
'city_id' => $request->city_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->filled('category_id')) {
|
||||||
|
$category = Category::find($request->category_id);
|
||||||
|
$slider->model()->associate($category)->save();
|
||||||
|
}
|
||||||
|
if ($request->filled('item')) {
|
||||||
|
$item = Item::find($request->item);
|
||||||
|
$slider->model()->associate($item)->save();
|
||||||
|
}
|
||||||
|
ResponseService::successResponse('Slider created successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Slider Controller -> store');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('slider-delete');
|
||||||
|
try {
|
||||||
|
$slider = Slider::find($id);
|
||||||
|
if ($slider) {
|
||||||
|
$url = $slider->image;
|
||||||
|
$relativePath = parse_url($url, PHP_URL_PATH);
|
||||||
|
if (Storage::disk(config('filesystems.default'))->exists($relativePath)) {
|
||||||
|
Storage::disk(config('filesystems.default'))->delete($relativePath);
|
||||||
|
}
|
||||||
|
$slider->delete();
|
||||||
|
ResponseService::successResponse('slider delete successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Slider Controller -> destroy');
|
||||||
|
ResponseService::errorResponse('something is wrong !!!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('slider-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
$sql = Slider::with('model', 'country:id,name', 'state:id,name', 'city:id,name');
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->sort($sort, $order)->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('slider-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('slider.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
}
|
||||||
165
app/Http/Controllers/StaffController.php
Normal file
165
app/Http/Controllers/StaffController.php
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class StaffController extends Controller {
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['staff-list', 'staff-create', 'staff-update', 'staff-delete']);
|
||||||
|
$roles = Role::where('custom_role', 1)->get();
|
||||||
|
return view('staff.index', compact('roles'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create() {
|
||||||
|
ResponseService::noPermissionThenRedirect('staff-create');
|
||||||
|
$roles = Role::where('custom_role', 1)->get();
|
||||||
|
return view('staff.create', compact('roles'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request) {
|
||||||
|
ResponseService::noPermissionThenRedirect('staff-create');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required',
|
||||||
|
'email' => 'required|email|unique:users',
|
||||||
|
'password' => 'required',
|
||||||
|
'role' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
$user = User::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'password' => Hash::make($request->password)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->syncRoles($request->role);
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('User created Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th, "StaffController --> store");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('staff-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name' => 'required',
|
||||||
|
'email' => 'required|email|unique:users,email,' . $id,
|
||||||
|
'role_id' => 'required'
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
$user = User::withTrashed()->findOrFail($id);
|
||||||
|
$user->update([
|
||||||
|
...$request->all()
|
||||||
|
]);
|
||||||
|
|
||||||
|
$oldRole = $user->roles;
|
||||||
|
if ($oldRole[0]->id !== $request->role_id) {
|
||||||
|
$newRole = Role::findById($request->role_id);
|
||||||
|
$user->removeRole($oldRole[0]);
|
||||||
|
$user->assignRole($newRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('User Update Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th, "StaffController --> update");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request) {
|
||||||
|
ResponseService::noPermissionThenRedirect('staff-list');
|
||||||
|
$offset = $request->offset ?? 0;
|
||||||
|
$limit = $request->limit ?? 10;
|
||||||
|
$sort = $request->sort ?? 'id';
|
||||||
|
$order = $request->order ?? 'DESC';
|
||||||
|
|
||||||
|
$sql = User::withTrashed()->with('roles')->orderBy($sort, $order)->whereHas('roles', function ($q) {
|
||||||
|
$q->where('custom_role', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('staff-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('staff.update', $row->id), true);
|
||||||
|
$operate .= BootstrapTableService::editButton(route('staff.change-password', $row->id), true, '#resetPasswordModel', null, $row->id, 'bi bi-key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('staff-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('staff.destroy', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['status'] = empty($row->deleted_at);
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id) {
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('staff-delete');
|
||||||
|
User::withTrashed()->findOrFail($id)->forceDelete();
|
||||||
|
ResponseService::successResponse('User Delete Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "StaffController --> delete");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function changePassword(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('staff-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'new_password' => 'required|min:8',
|
||||||
|
'confirm_password' => 'required|same:new_password'
|
||||||
|
]);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
User::findOrFail($id)->update(['password' => Hash::make($request->confirm_password)]);
|
||||||
|
ResponseService::successResponse('Password Reset Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, "StaffController -> changePassword");
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
194
app/Http/Controllers/SystemUpdateController.php
Normal file
194
app/Http/Controllers/SystemUpdateController.php
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Throwable;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class SystemUpdateController extends Controller {
|
||||||
|
private string $destinationPath;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->destinationPath = base_path() . '/update/tmp/';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
if (!Auth::user()->hasRole('Super Admin')) {
|
||||||
|
$response = array(
|
||||||
|
'message' => trans("You Don't have enough permissions")
|
||||||
|
);
|
||||||
|
return redirect(route('home'))->withErrors($response);
|
||||||
|
}
|
||||||
|
$system_version = Setting::where('name', 'system_version')->first();
|
||||||
|
return view('system-update.index', compact('system_version'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request) {
|
||||||
|
if (!Auth::user()->hasRole('Super Admin')) {
|
||||||
|
$response = array(
|
||||||
|
'error' => true,
|
||||||
|
'message' => trans("You Don't have enough permissions")
|
||||||
|
);
|
||||||
|
return response()->json($response);
|
||||||
|
}
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'purchase_code' => 'required',
|
||||||
|
'file' => 'required|file|mimes:zip',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$app_url = (string)url('/');
|
||||||
|
$app_url = preg_replace('#^https?://#i', '', $app_url);
|
||||||
|
$current_version = Setting::where('name', 'system_version')->first()['value'];
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt_array($curl, array(
|
||||||
|
CURLOPT_URL => 'https://validator.wrteam.in/eclassify_validator?purchase_code=' . $request->input('purchase_code') . '&domain_url=' . $app_url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_MAXREDIRS => 10,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4
|
||||||
|
));
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
$response = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
if ($response['error']) {
|
||||||
|
ResponseService::errorResponse($response["message"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir($this->destinationPath) && !mkdir($concurrentDirectory = $this->destinationPath, 0777, TRUE) && !is_dir($concurrentDirectory)) {
|
||||||
|
// sprintf('Directory "%s" was not created', $concurrentDirectory)
|
||||||
|
ResponseService::errorResponse("Permission Error while crating Temp Directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
// zip upload
|
||||||
|
$zipfile = $request->file('file');
|
||||||
|
$fileName = $zipfile->getClientOriginalName();
|
||||||
|
$zipfile->move($this->destinationPath, $fileName);
|
||||||
|
//This will add public in path
|
||||||
|
//$target_path = getcwd() . DIRECTORY_SEPARATOR;
|
||||||
|
$target_path = base_path() . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$filePath = $this->destinationPath . '/' . $fileName;
|
||||||
|
$zipStatus = $zip->open($filePath);
|
||||||
|
if ($zipStatus !== TRUE) {
|
||||||
|
ResponseService::errorResponse('something_wrong_try_again');
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->extractTo($this->destinationPath);
|
||||||
|
$zip->close();
|
||||||
|
unlink($filePath);
|
||||||
|
|
||||||
|
$ver_file = $this->destinationPath . 'version_info.php';
|
||||||
|
$source_path = $this->destinationPath . 'source_code.zip';
|
||||||
|
if (!file_exists($ver_file) && !file_exists($source_path)) {
|
||||||
|
ResponseService::errorResponse('Zip File is not Uploaded to Correct Path');
|
||||||
|
}
|
||||||
|
$ver_file1 = $target_path . 'version_info.php';
|
||||||
|
$source_path1 = $target_path . 'source_code.zip';
|
||||||
|
// MOVE File
|
||||||
|
if (!rename($ver_file, $ver_file1) || !rename($source_path, $source_path1)) {
|
||||||
|
ResponseService::errorResponse('Error Occurred while moving a Zip File');
|
||||||
|
}
|
||||||
|
|
||||||
|
$version_file = require($ver_file1);
|
||||||
|
|
||||||
|
if ($current_version == $version_file['update_version']) {
|
||||||
|
unlink($ver_file1);
|
||||||
|
unlink($source_path1);
|
||||||
|
ResponseService::errorResponse('System is already upto date');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($current_version != $version_file['current_version']) {
|
||||||
|
unlink($ver_file1);
|
||||||
|
unlink($source_path1);
|
||||||
|
ResponseService::errorResponse($current_version . ' ' . trans('Please update nearest version first'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip1 = new ZipArchive();
|
||||||
|
$zipFile1 = $zip1->open($source_path1);
|
||||||
|
|
||||||
|
if ($zipFile1 !== TRUE) {
|
||||||
|
unlink($ver_file1);
|
||||||
|
unlink($source_path1);
|
||||||
|
ResponseService::errorResponse('Source Code Zip Extraction Failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip1->extractTo($target_path);
|
||||||
|
$zip1->close();
|
||||||
|
|
||||||
|
Artisan::call('migrate');
|
||||||
|
Artisan::call('db:seed --class=SystemUpgradeSeeder');
|
||||||
|
Artisan::call('optimize:clear');
|
||||||
|
|
||||||
|
unlink($source_path1);
|
||||||
|
unlink($ver_file1);
|
||||||
|
Setting::where('name', 'system_version')->update([
|
||||||
|
'value' => $version_file['update_version']
|
||||||
|
]);
|
||||||
|
ResponseService::successResponse('System Updated Successfully');
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e);
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetPurchaseCode(Request $request)
|
||||||
|
{
|
||||||
|
if (!Auth::user()->hasRole('Super Admin')) {
|
||||||
|
ResponseService::errorResponse(trans('no_permission_message'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get domain name from env for app url
|
||||||
|
$domain = env('APP_URL');
|
||||||
|
$domain = preg_replace('#^https?://#i', '', $domain);
|
||||||
|
$purchase_code = env('APPSECRET');
|
||||||
|
|
||||||
|
if (!$purchase_code) {
|
||||||
|
ResponseService::errorResponse(__('Purchase Code Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt_array($curl, array(
|
||||||
|
CURLOPT_URL => 'https://validator.wrteam.in/eclassify_reset_purchase_code?purchase_code=' . $purchase_code . '&domain=' . $domain,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_MAXREDIRS => 10,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||||
|
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||||
|
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
|
if ($httpCode !== 200) {
|
||||||
|
ResponseService::errorResponse(__('Failed to connect to validation server'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
if (isset($response['error']) && $response['error']) {
|
||||||
|
ResponseService::errorResponse($response["message"] ?? __('Error occurred while resetting purchase code'));
|
||||||
|
} else {
|
||||||
|
ResponseService::successResponse(__('Purchase Code Reset Successfully'));
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, 'SystemUpdateController -> resetPurchaseCode');
|
||||||
|
ResponseService::errorResponse(__('Error Occurred'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
188
app/Http/Controllers/TipController.php
Normal file
188
app/Http/Controllers/TipController.php
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Tip;
|
||||||
|
use App\Models\TipTranslation;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class TipController extends Controller {
|
||||||
|
public function index() {
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['tip-list', 'tip-create', 'tip-update', 'tip-delete']);
|
||||||
|
return view('tip.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create() {
|
||||||
|
ResponseService::noPermissionThenRedirect('tip-create');
|
||||||
|
/*Values function is used to rearrange collection keys*/
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('tip.create', compact('languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('tip-create');
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"description.$defaultLangId" => 'required|string',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["description.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate($rules);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$tip = Tip::create([
|
||||||
|
'description' => $request->input("description.$defaultLangId"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
|
||||||
|
if (!empty($translatedDescription)) {
|
||||||
|
TipTranslation::create([
|
||||||
|
'description' => $translatedDescription,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'tip_id' => $tip->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successRedirectResponse("Tip Added Successfully", route('tips.index'));
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorRedirect($th, "TipController->store");
|
||||||
|
ResponseService::errorRedirectResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request) {
|
||||||
|
ResponseService::noPermissionThenSendJson('tip-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'sequence');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
$sql = Tip::withTrashed()->with('translations.language:id,name')->orderBy($sort, $order);
|
||||||
|
if (!empty($request->search)) {
|
||||||
|
$sql = $sql->search($request->search);
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = array();
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = array();
|
||||||
|
$no = 1;
|
||||||
|
foreach ($result as $key => $row) {
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('tip-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('tips.edit', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('tip-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('tips.destroy', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['no'] = $no++;
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$tempRow['status'] = empty($row->deleted_at);
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id) {
|
||||||
|
ResponseService::noPermissionThenRedirect('tip-update');
|
||||||
|
$tip = Tip::with('translations')->findOrFail($id);
|
||||||
|
|
||||||
|
// Initialize translations array with English (default) description
|
||||||
|
$translations = [];
|
||||||
|
$translations[1] = $tip->description;
|
||||||
|
|
||||||
|
// Add other language translations
|
||||||
|
foreach ($tip->translations as $translation) {
|
||||||
|
$translations[$translation->language_id] = $translation->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
return view('tip.edit', compact('tip', 'languages', 'translations'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('tip-update');
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages();
|
||||||
|
$defaultLangId = 1;
|
||||||
|
$otherLanguages = $languages->where('id', '!=', $defaultLangId);
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
"description.$defaultLangId" => 'required|string',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$rules["description.$langId"] = 'nullable|string';
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate($rules);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$tip = Tip::findOrFail($id);
|
||||||
|
$tip->update([
|
||||||
|
'description' => $request->input("description.$defaultLangId"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($otherLanguages as $lang) {
|
||||||
|
$langId = $lang->id;
|
||||||
|
$translatedDescription = $request->input("description.$langId");
|
||||||
|
|
||||||
|
TipTranslation::updateOrCreate(
|
||||||
|
[
|
||||||
|
'tip_id' => $tip->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'description' => $translatedDescription ?? '',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successRedirectResponse("Tip Updated Successfully", route('tips.index'));
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorRedirect($th);
|
||||||
|
ResponseService::errorRedirectResponse('Something Went Wrong ', route('tips.index'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id) {
|
||||||
|
ResponseService::noPermissionThenSendJson('tip-delete');
|
||||||
|
try {
|
||||||
|
Tip::findOrFail($id)->forceDelete();
|
||||||
|
ResponseService::successResponse('Tip delete successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse('Something Went Wrong ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
app/Http/Controllers/UserPurchasedPackageController.php
Normal file
35
app/Http/Controllers/UserPurchasedPackageController.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserPurchasedPackageController extends Controller {
|
||||||
|
|
||||||
|
public function index() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function edit($id) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update(Request $request, $id) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function destroy($id) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
530
app/Http/Controllers/UserVerificationController.php
Normal file
530
app/Http/Controllers/UserVerificationController.php
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Language;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use App\Models\VerificationField;
|
||||||
|
use App\Models\VerificationFieldsTranslation;
|
||||||
|
use App\Models\VerificationFieldValue;
|
||||||
|
use App\Models\VerificationRequest;
|
||||||
|
use App\Services\BootstrapTableService;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Auth;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Throwable;
|
||||||
|
use Validator;
|
||||||
|
|
||||||
|
class UserVerificationController extends Controller
|
||||||
|
{
|
||||||
|
private string $uploadFolder;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->uploadFolder = 'seller_verification';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
ResponseService::noAnyPermissionThenRedirect(['seller-verification-field-list', 'seller-verification-field-create', 'seller-verification-field-update', 'seller-verification-field-delete']);
|
||||||
|
$verificationRequests = VerificationRequest::with('verificationFieldValue', 'user');
|
||||||
|
|
||||||
|
return view('seller-verification.index', compact('verificationRequests'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verificationField()
|
||||||
|
{
|
||||||
|
// $verificationRequests = VerificationRequest::all();
|
||||||
|
return view('seller-verification.verificationfield');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('seller-verification-field-create');
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
|
||||||
|
return view('seller-verification.create', compact('languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-create');
|
||||||
|
$defaultValuesCount = count($request->input('values.1', []));
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name.1' => 'required',
|
||||||
|
'type' => 'required|in:number,textbox,fileinput,radio,dropdown,checkbox',
|
||||||
|
'values.1' => 'required_if:type,radio,dropdown,checkbox|array',
|
||||||
|
'min_length' => 'nullable|numeric',
|
||||||
|
'max_length' => 'nullable|numeric|gt:min_length',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Custom validation for other languages
|
||||||
|
$validator->after(function ($validator) use ($request, $defaultValuesCount) {
|
||||||
|
foreach ($request->input('languages', []) as $langId) {
|
||||||
|
if ($langId != 1) {
|
||||||
|
$values = $request->input("values.$langId", []);
|
||||||
|
|
||||||
|
// Only validate if values are not empty
|
||||||
|
if (! empty($values) && count($values) !== $defaultValuesCount) {
|
||||||
|
$validator->errors()->add(
|
||||||
|
"values.$langId",
|
||||||
|
'The number of values for all languages must be the same as the default language.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $request->input('name')[1],
|
||||||
|
'type' => $request->input('type'),
|
||||||
|
'min_length' => $request->input('min_length'),
|
||||||
|
'max_length' => $request->input('max_length'),
|
||||||
|
'is_required' => $request->input('is_required', false),
|
||||||
|
'status' => $request->input('status', true),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Save values for English (default) if needed
|
||||||
|
if (in_array($data['type'], ['radio', 'dropdown', 'checkbox'])) {
|
||||||
|
$data['values'] = json_encode($request->input('values')[1] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
$field = VerificationField::create($data);
|
||||||
|
|
||||||
|
// Store translations
|
||||||
|
foreach ($request->input('languages', []) as $langId) {
|
||||||
|
if ($langId != 1) {
|
||||||
|
$translatedName = $request->input("name.$langId");
|
||||||
|
$translatedValues = $request->input("values.$langId", []);
|
||||||
|
|
||||||
|
if ($translatedName || ! empty($translatedValues)) {
|
||||||
|
VerificationFieldsTranslation::create([
|
||||||
|
'verification_field_id' => $field->id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'name' => $translatedName,
|
||||||
|
'value' => json_encode($translatedValues, JSON_THROW_ON_ERROR),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('Seller verification field added successfully');
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse('Something went wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-list');
|
||||||
|
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'DESC');
|
||||||
|
|
||||||
|
$query = VerificationRequest::with('user', 'verification_field_values.verification_field')->orderBy('updated_at', 'desc');
|
||||||
|
|
||||||
|
if (! empty($request->filter)) {
|
||||||
|
$filters = json_decode($request->filter, true, 512, JSON_THROW_ON_ERROR); // Decode as an associative array
|
||||||
|
foreach ($filters as $field => $value) {
|
||||||
|
$query->where($field, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$query->where(function ($q) use ($request) {
|
||||||
|
$q->where('status', 'like', '%'.$request->search.'%')
|
||||||
|
->orWhereHas('user', function ($q) use ($request) {
|
||||||
|
$q->where('name', 'like', '%'.$request->search.'%');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $query->count();
|
||||||
|
$sql = $query->orderBy('updated_at', 'DESC')->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$no = 1;
|
||||||
|
|
||||||
|
$bulkData = [
|
||||||
|
'total' => $total,
|
||||||
|
'rows' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
$verificationFieldValues = VerificationFieldValue::whereIn('verification_request_id', $result->pluck('id'))->get();
|
||||||
|
$languages = Language::select('id', 'name')->get();
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$row->verification_fields = collect($row->verification_fields)->map(function ($verification_field) use ($verificationFieldValues, $row) {
|
||||||
|
// Default (no lang_id = base value)
|
||||||
|
$fieldValue = $verificationFieldValues->first(function ($data) use ($row, $verification_field) {
|
||||||
|
return $data->verification_field_id == $verification_field->id
|
||||||
|
&& $data->verification_request_id == $row->id
|
||||||
|
&& empty($data->language_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
$verification_field['default_value'] = $fieldValue ? $fieldValue->value : null;
|
||||||
|
|
||||||
|
// All translations grouped by language
|
||||||
|
$translations = $verificationFieldValues->where('verification_field_id', $verification_field->id)
|
||||||
|
->where('verification_request_id', $row->id)
|
||||||
|
->groupBy('language_id')
|
||||||
|
->map(function ($items) {
|
||||||
|
return $items->first()->value; // take value per lang
|
||||||
|
});
|
||||||
|
|
||||||
|
$verification_field['translations'] = $translations;
|
||||||
|
|
||||||
|
return $verification_field;
|
||||||
|
});
|
||||||
|
$operate = '';
|
||||||
|
|
||||||
|
if (Auth::user()->can('seller-verification-field-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('seller_verification.approval', $row->id), true, '#editStatusModal', 'edit-status', $row->id);
|
||||||
|
$operate .= BootstrapTableService::button('fa fa-eye', '#', ['view-verification-fields', 'btn-light-danger '], ['title' => __('View'), 'data-bs-target' => '#editModal', 'data-bs-toggle' => 'modal']);
|
||||||
|
}
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$tempRow['no'] = $no++;
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$tempRow['user_name'] = $row->user->name ?? '';
|
||||||
|
$tempRow['languages'] = $languages;
|
||||||
|
$bulkData['rows'][] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
ResponseService::logErrorResponse($e, 'Controller -> show');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function showVerificationFields(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-list');
|
||||||
|
$offset = $request->input('offset', 0);
|
||||||
|
$limit = $request->input('limit', 10);
|
||||||
|
$sort = $request->input('sort', 'id');
|
||||||
|
$order = $request->input('order', 'ASC');
|
||||||
|
|
||||||
|
$sql = VerificationField::orderBy($sort, $order)->withTrashed();
|
||||||
|
|
||||||
|
if (! empty($_GET['search'])) {
|
||||||
|
$sql->search($_GET['search']);
|
||||||
|
// $sql->where('id', 'LIKE', "%$search%")->orwhere('question', 'LIKE', "%$search%")->orwhere('answer', 'LIKE', "%$search%");
|
||||||
|
|
||||||
|
}
|
||||||
|
$total = $sql->count();
|
||||||
|
$sql->skip($offset)->take($limit);
|
||||||
|
$result = $sql->get();
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = $total;
|
||||||
|
$rows = [];
|
||||||
|
foreach ($result as $row) {
|
||||||
|
$tempRow = $row->toArray();
|
||||||
|
$operate = '';
|
||||||
|
if (Auth::user()->can('seller-verification-field-update')) {
|
||||||
|
$operate .= BootstrapTableService::editButton(route('seller-verification.verification-field.edit', $row->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->can('seller-verification-field-delete')) {
|
||||||
|
$operate .= BootstrapTableService::deleteButton(route('seller-verification.verification-field.delete', $row->id));
|
||||||
|
}
|
||||||
|
$tempRow['operate'] = $operate;
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'UserVerificationController --> show');
|
||||||
|
ResponseService::errorResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenRedirect('seller-verification-field-update');
|
||||||
|
|
||||||
|
$verification_field = VerificationField::withTrashed()
|
||||||
|
->with('translations')
|
||||||
|
->findOrFail($id);
|
||||||
|
|
||||||
|
$languages = CachingService::getLanguages()->values();
|
||||||
|
|
||||||
|
return view('seller-verification.edit', compact('verification_field', 'languages'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-update');
|
||||||
|
$defaultValuesCount = count($request->input('values.1', []));
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'name.1' => 'required',
|
||||||
|
'type' => 'required|in:number,textbox,fileinput,radio,dropdown,checkbox',
|
||||||
|
'values.1' => 'required_if:type,radio,dropdown,checkbox|array',
|
||||||
|
'min_length' => 'nullable|numeric',
|
||||||
|
'max_length' => 'nullable|numeric|gt:min_length',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$validator->after(function ($validator) use ($request, $defaultValuesCount) {
|
||||||
|
foreach ($request->input('languages', []) as $langId) {
|
||||||
|
if ($langId != 1) {
|
||||||
|
$values = $request->input("values.$langId", []);
|
||||||
|
|
||||||
|
// Only validate if values are not empty
|
||||||
|
if (! empty($values) && count($values) !== $defaultValuesCount) {
|
||||||
|
$validator->errors()->add(
|
||||||
|
"values.$langId",
|
||||||
|
'The number of values for all languages must be the same as the default language.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::validationError($validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$field = VerificationField::withTrashed()->findOrFail($id);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'name' => $request->input('name')[1],
|
||||||
|
'type' => $request->input('type'),
|
||||||
|
'min_length' => $request->input('min_length'),
|
||||||
|
'max_length' => $request->input('max_length'),
|
||||||
|
'is_required' => $request->input('is_required', false),
|
||||||
|
'status' => $request->input('status', true),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($data['status']) {
|
||||||
|
$data['deleted_at'] = null;
|
||||||
|
} else {
|
||||||
|
$data['deleted_at'] = now();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($data['type'], ['radio', 'dropdown', 'checkbox'])) {
|
||||||
|
$data['values'] = json_encode($request->input('values')[1] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
$field->update($data);
|
||||||
|
|
||||||
|
// Save Translations
|
||||||
|
VerificationFieldsTranslation::where('verification_field_id', $id)->delete();
|
||||||
|
|
||||||
|
foreach ($request->input('languages', []) as $langId) {
|
||||||
|
if ($langId != 1) {
|
||||||
|
$translatedName = $request->input("name.$langId");
|
||||||
|
$translatedValues = $request->input("values.$langId", []);
|
||||||
|
|
||||||
|
if ($translatedName || ! empty($translatedValues)) {
|
||||||
|
VerificationFieldsTranslation::create([
|
||||||
|
'verification_field_id' => $id,
|
||||||
|
'language_id' => $langId,
|
||||||
|
'name' => $translatedName,
|
||||||
|
'value' => json_encode($translatedValues, JSON_THROW_ON_ERROR),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
ResponseService::successResponse('Verification Field Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse('Something went wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-delete');
|
||||||
|
VerificationField::withTrashed()->find($id)->forceDelete();
|
||||||
|
ResponseService::successResponse('seller Verification delete successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'Seller Verification Controller -> destroy');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSellerVerificationValues(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-update');
|
||||||
|
$values = VerificationField::where('id', $id)->withTrashed()->first()->values;
|
||||||
|
|
||||||
|
if (! empty($request->search)) {
|
||||||
|
$matchingElements = [];
|
||||||
|
foreach ($values as $element) {
|
||||||
|
$stringElement = (string) $element;
|
||||||
|
|
||||||
|
// Check if the search term is present in the element
|
||||||
|
if (str_contains($stringElement, $request->search)) {
|
||||||
|
// If found, add it to the matching elements array
|
||||||
|
$matchingElements[] = $element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$values = $matchingElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bulkData = [];
|
||||||
|
$bulkData['total'] = count($values);
|
||||||
|
$rows = [];
|
||||||
|
foreach ($values as $key => $row) {
|
||||||
|
$tempRow['id'] = $key;
|
||||||
|
$tempRow['value'] = $row;
|
||||||
|
// if (Auth::user()->can('faq-update')) {
|
||||||
|
// $operate .= BootstrapTableService::editButton(route('faq.update', $row->id), true, '#editModal', 'faqEvents', $row->id);
|
||||||
|
// }
|
||||||
|
$tempRow['operate'] = BootstrapTableService::button('fa fa-edit', route('seller-verification.value.update', $id), ['edit_btn'], ['title' => 'Edit', 'data-bs-target' => '#editModal', 'data-bs-toggle' => 'modal']);
|
||||||
|
$tempRow['operate'] .= BootstrapTableService::deleteButton(route('seller-verification.value.delete', [$id, $row]), true);
|
||||||
|
$rows[] = $tempRow;
|
||||||
|
}
|
||||||
|
$bulkData['rows'] = $rows;
|
||||||
|
|
||||||
|
return response()->json($bulkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addSellerVerificationValue(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-create');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'values' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::errorResponse($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$verification_field = VerificationField::findOrFail($id);
|
||||||
|
$newValues = explode(',', $request->values);
|
||||||
|
$values = [
|
||||||
|
...$verification_field->values,
|
||||||
|
...$newValues,
|
||||||
|
];
|
||||||
|
|
||||||
|
$verification_field->values = json_encode($values, JSON_THROW_ON_ERROR);
|
||||||
|
$verification_field->save();
|
||||||
|
ResponseService::successResponse('Seller Verification Value added Successfully');
|
||||||
|
} catch (Throwable) {
|
||||||
|
ResponseService::errorResponse('Something Went Wrong ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateSellerVerificationValue(Request $request, $id)
|
||||||
|
{
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-update');
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'old_verification_field_value' => 'required',
|
||||||
|
'new_verification_field_value' => 'required',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
ResponseService::errorResponse($validator->errors()->first());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$verification_field = VerificationField::where('id', $id)->withTrashed()->first();
|
||||||
|
$values = $verification_field->values;
|
||||||
|
if (is_array($values)) {
|
||||||
|
$values[array_search($request->old_verification_field_value, $values, true)] = $request->new_verification_field_value;
|
||||||
|
} else {
|
||||||
|
$values = $request->new_verification_field_value;
|
||||||
|
}
|
||||||
|
$verification_field->values = $values;
|
||||||
|
$verification_field->save();
|
||||||
|
ResponseService::successResponse('Verification Field Value Updated Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'UserVerificationController -> updateSellerVerificationValue');
|
||||||
|
ResponseService::errorResponse('Something Went Wrong ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteSellerVerificationValue($id, $deletedValue)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-field-delete');
|
||||||
|
$verification_field = VerificationField::where('id', $id)->withTrashed()->first();
|
||||||
|
$values = $verification_field->values;
|
||||||
|
unset($values[array_search($deletedValue, $values, true)]);
|
||||||
|
$verification_field->values = json_encode($values, JSON_THROW_ON_ERROR);
|
||||||
|
$verification_field->save();
|
||||||
|
ResponseService::successResponse('Seller Verification Value Deleted Successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th);
|
||||||
|
ResponseService::errorResponse('Something Went Wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateSellerApproval(Request $request, $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ResponseService::noPermissionThenSendJson('seller-verification-request-update');
|
||||||
|
$verification_field = VerificationRequest::with('user')->findOrFail($id);
|
||||||
|
$newStatus = $request->input('status');
|
||||||
|
$rejectionReason = $request->input('rejection_reason'); // Get the rejection reason from the request
|
||||||
|
if ($newStatus === 'rejected' && empty($rejectionReason)) {
|
||||||
|
ResponseService::validationError('Rejection reason is required when status is rejected.');
|
||||||
|
}
|
||||||
|
$verification_field->update([
|
||||||
|
'status' => $newStatus,
|
||||||
|
'rejection_reason' => $newStatus === 'rejected' ? $rejectionReason : null, // Set the reason if rejected
|
||||||
|
]);
|
||||||
|
|
||||||
|
$verification_field->user->update([
|
||||||
|
'is_verified' => $newStatus === 'approved' ? 1 : 0,
|
||||||
|
'auto_approve_item' => $newStatus === 'approved' ? 1 : 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user_token = UserFcmToken::where('user_id', $verification_field->user->id)->pluck('fcm_token')->toArray();
|
||||||
|
if (! empty($user_token)) {
|
||||||
|
NotificationService::sendFcmNotification($user_token, 'About ', 'Your Verfication Request is '.ucfirst($request->status), 'verifcation-request-update', ['id' => $id]);
|
||||||
|
}
|
||||||
|
ResponseService::successResponse('Seller status updated successfully');
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
ResponseService::logErrorResponse($th, 'UserVerificationController -> updateSellerApproval');
|
||||||
|
ResponseService::errorResponse('Something went wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOTE : Why this simple code is done using chatgpt ? */
|
||||||
|
public function getVerificationDetails($id)
|
||||||
|
{
|
||||||
|
$verificationFieldValues = VerificationFieldValue::with('verificationField')->where('verification_request_id', $id)->get();
|
||||||
|
if ($verificationFieldValues->isEmpty()) {
|
||||||
|
return response()->json(['error' => 'No details found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldValues = $verificationFieldValues->map(function ($fieldValue) {
|
||||||
|
return [
|
||||||
|
'name' => $fieldValue->verificationField->name ?? 'N/A',
|
||||||
|
'value' => $fieldValue->value ?? 'No value provided',
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'verification_field_values' => $fieldValues,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
744
app/Http/Controllers/WebhookController.php
Normal file
744
app/Http/Controllers/WebhookController.php
Normal file
@@ -0,0 +1,744 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Item;
|
||||||
|
use App\Models\Package;
|
||||||
|
use App\Models\PaymentConfiguration;
|
||||||
|
use App\Models\PaymentTransaction;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use App\Models\UserPurchasedPackage;
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Services\Payment\PaymentService;
|
||||||
|
use App\Services\Payment\PayPalPayment;
|
||||||
|
use App\Services\ResponseService;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Razorpay\Api\Api;
|
||||||
|
use Stripe\Exception\SignatureVerificationException;
|
||||||
|
use Stripe\Exception\UnexpectedValueException;
|
||||||
|
use Stripe\Webhook;
|
||||||
|
use PhonePe\PhonePe;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
|
||||||
|
class WebhookController extends Controller {
|
||||||
|
public function stripe() {
|
||||||
|
$payload = @file_get_contents('php://input');
|
||||||
|
try {
|
||||||
|
// Verify webhook signature and extract the event.
|
||||||
|
// See https://stripe.com/docs/webhooks/signatures for more information.
|
||||||
|
// $data = json_decode($payload, false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
|
||||||
|
|
||||||
|
// You can find your endpoint's secret in your webhook settings
|
||||||
|
$paymentConfiguration = PaymentConfiguration::select('webhook_secret_key')->where('payment_method', 'Stripe')->first();
|
||||||
|
$endpoint_secret = $paymentConfiguration['webhook_secret_key'] ;
|
||||||
|
$event = Webhook::constructEvent(
|
||||||
|
$payload, $sig_header, $endpoint_secret
|
||||||
|
);
|
||||||
|
|
||||||
|
$metadata = $event->data->object->metadata;
|
||||||
|
|
||||||
|
// Use this lines to Remove Signature verification for debugging purpose
|
||||||
|
// $event = json_decode($payload, false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
// $metadata = (array)$event->data->object->metadata;
|
||||||
|
|
||||||
|
Log::info("Stripe Webhook : ", [$event]);
|
||||||
|
// handle the events
|
||||||
|
switch ($event->type) {
|
||||||
|
case 'payment_intent.created':
|
||||||
|
//Do nothing
|
||||||
|
http_response_code(200);
|
||||||
|
break;
|
||||||
|
case 'payment_intent.succeeded':
|
||||||
|
$response = $this->assignPackage($metadata['payment_transaction_id'], $metadata['user_id'], $metadata['package_id']);
|
||||||
|
|
||||||
|
if ($response['error']) {
|
||||||
|
Log::error("Stripe Webhook : ", [$response['message']]);
|
||||||
|
}
|
||||||
|
http_response_code(200);
|
||||||
|
break;
|
||||||
|
case 'payment_intent.payment_failed':
|
||||||
|
$response = $this->failedTransaction($metadata['payment_transaction_id'], $metadata['user_id']);
|
||||||
|
if ($response['error']) {
|
||||||
|
Log::error("Stripe Webhook : ", [$response['message']]);
|
||||||
|
}
|
||||||
|
http_response_code(400);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log::error('Stripe Webhook : Received unknown event type', [$event->type]);
|
||||||
|
}
|
||||||
|
} catch (UnexpectedValueException) {
|
||||||
|
// Invalid payload
|
||||||
|
echo "Stripe Webhook : Payload Mismatch";
|
||||||
|
Log::error("Stripe Webhook : Payload Mismatch");
|
||||||
|
http_response_code(400);
|
||||||
|
exit();
|
||||||
|
} catch (SignatureVerificationException) {
|
||||||
|
// Invalid signature
|
||||||
|
echo "Stripe Webhook : Signature Verification Failed";
|
||||||
|
Log::error("Stripe Webhook : Signature Verification Failed");
|
||||||
|
http_response_code(400);
|
||||||
|
exit();
|
||||||
|
} catch
|
||||||
|
(Throwable $e) {
|
||||||
|
Log::error("Stripe Webhook : Error occurred", [$e->getMessage() . ' --> ' . $e->getFile() . ' At Line : ' . $e->getLine()]);
|
||||||
|
http_response_code(400);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function razorpay() {
|
||||||
|
try {
|
||||||
|
Log::info("Razorpay Webhook called");
|
||||||
|
$paymentConfiguration = PaymentConfiguration::select('webhook_secret_key','api_key')->where('payment_method', 'razorpay')->first();
|
||||||
|
$webhookSecret = $paymentConfiguration['webhook_secret_key'];
|
||||||
|
$webhookPublic = $paymentConfiguration["api_key"];
|
||||||
|
|
||||||
|
// get the json data of payment
|
||||||
|
$webhookBody = file_get_contents('php://input');
|
||||||
|
$data = json_decode($webhookBody, false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
Log::info("Razorpay Webhook : ", [$data]);
|
||||||
|
|
||||||
|
$api = new Api($webhookPublic, $webhookSecret);
|
||||||
|
|
||||||
|
$metadata = $data->payload->payment->entity->notes;
|
||||||
|
|
||||||
|
if (isset($data->event) && $data->event == 'payment.captured') {
|
||||||
|
|
||||||
|
//checks the signature
|
||||||
|
$expectedSignature = hash_hmac("SHA256", $webhookBody, $webhookSecret);
|
||||||
|
|
||||||
|
$api->utility->verifyWebhookSignature($webhookBody, $expectedSignature, $webhookSecret);
|
||||||
|
|
||||||
|
$paymentTransactionData = PaymentTransaction::where('id', $metadata->payment_transaction_id)->first();
|
||||||
|
if ($paymentTransactionData == null) {
|
||||||
|
Log::error("Stripe Webhook : Payment Transaction id not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($paymentTransactionData->status == "succeed") {
|
||||||
|
Log::info("Stripe Webhook : Transaction Already Succeed");
|
||||||
|
}
|
||||||
|
$response = $this->assignPackage($metadata->payment_transaction_id, $metadata->user_id, $metadata->package_id);
|
||||||
|
|
||||||
|
if ($response['error']) {
|
||||||
|
Log::error("Razorpay Webhook : ", [$response['message']]);
|
||||||
|
}
|
||||||
|
http_response_code(200);
|
||||||
|
} elseif (isset($data->event) && $data->event == 'payment.failed') {
|
||||||
|
$response = $this->failedTransaction($metadata->payment_transaction_id, $metadata->user_id);
|
||||||
|
if ($response['error']) {
|
||||||
|
Log::error("Razorpay Webhook : ", [$response['message']]);
|
||||||
|
}
|
||||||
|
http_response_code(400);
|
||||||
|
} elseif (isset($data->event) && $data->event == 'payment.authorized') {
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
|
} else {
|
||||||
|
Log::error('Unknown Event Type', [$data->event]);
|
||||||
|
}
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
Log::error($th);
|
||||||
|
Log::error('Razorpay --> Webhook Error Occurred');
|
||||||
|
http_response_code(400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paystack() {
|
||||||
|
try {
|
||||||
|
// only a post with paystack signature header gets our attention
|
||||||
|
if (!array_key_exists('HTTP_X_PAYSTACK_SIGNATURE', $_SERVER) || (strtoupper($_SERVER['REQUEST_METHOD']) != 'POST')) {
|
||||||
|
echo "Signature not found";
|
||||||
|
http_response_code(400);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the request's body
|
||||||
|
$input = @file_get_contents("php://input");
|
||||||
|
$paymentConfiguration = PaymentConfiguration::select('webhook_secret_key')->where('payment_method', 'paystack')->first();
|
||||||
|
$endpoint_secret = $paymentConfiguration['webhook_secret_key'];
|
||||||
|
|
||||||
|
if (hash_equals($_SERVER['HTTP_X_PAYSTACK_SIGNATURE'], hash_hmac('sha512', $input, $endpoint_secret))) {
|
||||||
|
echo "Signature does not match";
|
||||||
|
http_response_code(400);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
// parse event (which is json string) as object
|
||||||
|
// Do something - that will not take long - with $event
|
||||||
|
$event = json_decode($input, false, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$metadata = $event->data->metadata;
|
||||||
|
Log::info("Paystack Webhook event Called", [$event]);
|
||||||
|
switch ($event->event) {
|
||||||
|
case 'charge.success':
|
||||||
|
$response = $this->assignPackage($metadata->payment_transaction_id, $metadata->user_id, $metadata->package_id);
|
||||||
|
if ($response['error']) {
|
||||||
|
Log::error("Paystack Webhook : ", [$response['message']]);
|
||||||
|
}
|
||||||
|
http_response_code(200);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log::error('Paystack Webhook : Received unknown event type', [$event->event]);
|
||||||
|
}
|
||||||
|
http_response_code(200);
|
||||||
|
exit();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Log::error("Paystack Webhook : Error occurred", [$e->getMessage() . ' --> ' . $e->getFile() . ' At Line : ' . $e->getLine()]);
|
||||||
|
http_response_code(400);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paystackSuccessCallback(){
|
||||||
|
ResponseService::successResponse("Payment done successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phonePe()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Log::info("PhonePe Webhook event called");
|
||||||
|
|
||||||
|
// Must be POST
|
||||||
|
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
|
||||||
|
Log::error("Invalid request method");
|
||||||
|
return response('Invalid request method', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw payload
|
||||||
|
$content = trim(file_get_contents("php://input"));
|
||||||
|
$jsonInput = json_decode($content, true);
|
||||||
|
Log::info("PhonePe Webhook Raw Payload", [$jsonInput]);
|
||||||
|
|
||||||
|
if (!$jsonInput) {
|
||||||
|
Log::error("Invalid JSON payload");
|
||||||
|
return response()->json(['error' => 'Invalid JSON'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 1: Verify Authorization Header ---
|
||||||
|
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? null;
|
||||||
|
if (!$authHeader) {
|
||||||
|
Log::error("Missing Authorization header");
|
||||||
|
return response()->json(['error' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch credentials (from DB or static values)
|
||||||
|
$paymentConfiguration = PaymentConfiguration::where('payment_method', 'PhonePe')->first();
|
||||||
|
$username = $paymentConfiguration->username ?? '';
|
||||||
|
$password = $paymentConfiguration->password ?? '';
|
||||||
|
|
||||||
|
|
||||||
|
$expectedHash = hash('sha256', $username . ':' . $password);
|
||||||
|
|
||||||
|
if (hash_equals($expectedHash, $authHeader)) {
|
||||||
|
Log::error("Authorization header mismatch");
|
||||||
|
return response()->json(['error' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 2: Extract Order Info ---
|
||||||
|
$payload = $jsonInput['payload'] ?? [];
|
||||||
|
$merchantOrderId = $payload['merchantOrderId'] ?? null;
|
||||||
|
$orderId = $payload['orderId'] ?? null;
|
||||||
|
$state = $payload['state'] ?? null;
|
||||||
|
$amount = $payload['amount'] ?? null;
|
||||||
|
$metadata = $payload['metadata'] ?? [];
|
||||||
|
|
||||||
|
// Try from merchantOrderId (for web)
|
||||||
|
$transaction_id = null;
|
||||||
|
$package_id = null;
|
||||||
|
$user_id = null;
|
||||||
|
|
||||||
|
if (!empty($merchantOrderId) && strpos($merchantOrderId, 't-') === 0) {
|
||||||
|
$parts = explode('-', $merchantOrderId);
|
||||||
|
$transaction_id = $parts[1] ?? null;
|
||||||
|
$package_id = $parts[3] ?? null;
|
||||||
|
$user_id = $metadata['user_id'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for App SDK
|
||||||
|
if (empty($transaction_id) || empty($package_id)) {
|
||||||
|
$transaction_id = $metadata['payment_transaction_id'] ?? null;
|
||||||
|
$package_id = $metadata['package_id'] ?? null;
|
||||||
|
$user_id = $metadata['user_id'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info("PhonePe Identified Meta", [
|
||||||
|
'transaction_id' => $transaction_id,
|
||||||
|
'package_id' => $package_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Log::info("PhonePe Payment Event", [
|
||||||
|
'event' => $jsonInput['event'] ?? null,
|
||||||
|
'merchantOrderId' => $merchantOrderId,
|
||||||
|
'orderId' => $orderId,
|
||||||
|
'state' => $state,
|
||||||
|
'amount' => $amount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// --- Step 3: Business Logic ---
|
||||||
|
if ($state === "COMPLETED" || $state === "SUCCESS") {
|
||||||
|
// Parse merchantOrderId (example format: t-151-p-1)
|
||||||
|
$parts = explode('-', $merchantOrderId);
|
||||||
|
$transaction_id = $parts[1] ?? null;
|
||||||
|
$package_id = $parts[3] ?? null;
|
||||||
|
|
||||||
|
$paymentTransaction = PaymentTransaction::find($transaction_id);
|
||||||
|
|
||||||
|
if ($paymentTransaction) {
|
||||||
|
$metadata = [
|
||||||
|
'payment_transaction_id' => $transaction_id,
|
||||||
|
'package_id' => $package_id,
|
||||||
|
'user_id' => $paymentTransaction->user_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->assignPackage(
|
||||||
|
$metadata['payment_transaction_id'],
|
||||||
|
$metadata['user_id'],
|
||||||
|
$metadata['package_id']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($response['error']) {
|
||||||
|
Log::error("PhonePe Webhook assignPackage error", [$response['message']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['status' => 'success'], 200);
|
||||||
|
} else {
|
||||||
|
Log::warning("PhonePe Payment Failed", $jsonInput);
|
||||||
|
return response()->json(['status' => 'failed'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error("PhonePe Webhook Error", [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
]);
|
||||||
|
return response()->json(['error' => 'Webhook processing failed'], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function flutterwave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
|
||||||
|
Log::error("Invalid request method");
|
||||||
|
return response('Invalid request method', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = trim(file_get_contents("php://input"));
|
||||||
|
$payload = json_decode($content, true);
|
||||||
|
if (!$payload || empty($payload)) {
|
||||||
|
Log::error('Invalid webhook payload');
|
||||||
|
return response()->json(['error' => 'Invalid payload'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($payload['txRef']) || !isset($payload['status'])) {
|
||||||
|
Log::error('Missing required fields in webhook payload');
|
||||||
|
return response()->json(['error' => 'Invalid payload structure'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactionRef = $payload['txRef'];
|
||||||
|
$status = $payload['status'];
|
||||||
|
$amount = $payload['amount'];
|
||||||
|
$currency = $payload['currency'];
|
||||||
|
$customer = $payload['customer'];
|
||||||
|
$transactionId = $payload['id'];
|
||||||
|
|
||||||
|
$parts = explode('-', $transactionRef);
|
||||||
|
$transaction_id = $parts[1];
|
||||||
|
$package_id = $parts[3];
|
||||||
|
$paymentTransaction = PaymentTransaction::findOrFail($transaction_id);
|
||||||
|
|
||||||
|
if ($status === 'successful') {
|
||||||
|
Log::info('Flutterwave Payment Successful', [
|
||||||
|
'transactionId' => $transactionId,
|
||||||
|
'transactionRef' => $transactionRef,
|
||||||
|
'amount' => $amount,
|
||||||
|
'currency' => $currency,
|
||||||
|
'customer' => $customer
|
||||||
|
]);
|
||||||
|
|
||||||
|
$metadata = [
|
||||||
|
'payment_transaction_id' => $transaction_id,
|
||||||
|
'package_id' => $package_id,
|
||||||
|
'user_id' => $paymentTransaction->user_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->assignPackage($metadata['payment_transaction_id'], $metadata['user_id'], $metadata['package_id']);
|
||||||
|
if ($response['error']) {
|
||||||
|
Log::error("Flutterwave Webhook : ", [$response['message']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['status' => 'success'], 200);
|
||||||
|
} else {
|
||||||
|
Log::warning('Flutterwave Payment Failed or Incomplete', [$payload]);
|
||||||
|
return response()->json(['status' => 'failure'], 400);
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
Log::error("Flutterwave Webhook Error", [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine()
|
||||||
|
]);
|
||||||
|
return response()->json(['error' => 'Webhook processing failed'], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function phonePeSuccessCallback(){
|
||||||
|
ResponseService::successResponse("Payment done successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paypal()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
\Log::info("PayPal Webhook event called");
|
||||||
|
|
||||||
|
// ✅ Ensure POST
|
||||||
|
if (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {
|
||||||
|
\Log::error("Invalid request method");
|
||||||
|
return response('Invalid request method', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Raw payload
|
||||||
|
$content = trim(file_get_contents("php://input"));
|
||||||
|
$jsonInput = json_decode($content, true);
|
||||||
|
\Log::info("PayPal Webhook Raw Payload", [$jsonInput]);
|
||||||
|
|
||||||
|
if (!$jsonInput || empty($jsonInput)) {
|
||||||
|
return response()->json(['error' => 'Invalid JSON'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventType = $jsonInput['event_type'] ?? null;
|
||||||
|
$resource = $jsonInput['resource'] ?? [];
|
||||||
|
|
||||||
|
\Log::info("PayPal Event", [
|
||||||
|
'event' => $eventType,
|
||||||
|
'orderId' => $resource['id'] ?? null,
|
||||||
|
'status' => $resource['status'] ?? null,
|
||||||
|
]);
|
||||||
|
$paymentConfiguration = PaymentConfiguration::where('payment_method', 'paypal')->first();
|
||||||
|
$webhookId = $paymentConfiguration['webhook_url'] ?? url('/webhook/paypal');
|
||||||
|
|
||||||
|
$paypal = new PayPalPayment($paymentConfiguration->api_key, $paymentConfiguration->secret_key, $paymentConfiguration->currency_code,$paymentConfiguration->payment_mode);
|
||||||
|
|
||||||
|
|
||||||
|
// Get raw body for verification
|
||||||
|
$rawBody = file_get_contents('php://input');
|
||||||
|
|
||||||
|
// Extract headers properly (case-insensitive)
|
||||||
|
$paypalHeaders = [
|
||||||
|
'paypal-auth-algo' => request()->header('paypal-auth-algo') ?: request()->header('PAYPAL-AUTH-ALGO'),
|
||||||
|
'paypal-cert-url' => request()->header('paypal-cert-url') ?: request()->header('PAYPAL-CERT-URL'),
|
||||||
|
'paypal-transmission-id' => request()->header('paypal-transmission-id') ?: request()->header('PAYPAL-TRANSMISSION-ID'),
|
||||||
|
'paypal-transmission-sig' => request()->header('paypal-transmission-sig') ?: request()->header('PAYPAL-TRANSMISSION-SIG'),
|
||||||
|
'paypal-transmission-time' => request()->header('paypal-transmission-time') ?: request()->header('PAYPAL-TRANSMISSION-TIME'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Verify webhook authenticity
|
||||||
|
$isValid = $paypal->verifyWebhookSignature($paypalHeaders, $rawBody, $webhookId);
|
||||||
|
|
||||||
|
if (!$isValid) {
|
||||||
|
Log::warning('Invalid PayPal Webhook Signature');
|
||||||
|
return response()->json(['status' => 'invalid'], 200); // respond 200 to prevent PayPal retries
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Step 1: Handle APPROVED orders (capture payment) ---
|
||||||
|
if ($eventType === "CHECKOUT.ORDER.APPROVED") {
|
||||||
|
$orderId = $resource['id'];
|
||||||
|
|
||||||
|
// $paypal = app(PaypalPayment::class);
|
||||||
|
// $capture = $paypal->capturePayment($orderId);
|
||||||
|
|
||||||
|
Log::info("PayPal Order checkout approved");
|
||||||
|
|
||||||
|
// update your DB here
|
||||||
|
return response()->json(['status' => 'captured'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 2: Handle final success event ---
|
||||||
|
if ($eventType === "PAYMENT.CAPTURE.COMPLETED") {
|
||||||
|
$captureId = $resource['id'];
|
||||||
|
$amount = $resource['amount']['value'] ?? null;
|
||||||
|
$currency = $resource['amount']['currency_code'] ?? null;
|
||||||
|
$customId = $resource['custom_id'] ?? null;
|
||||||
|
|
||||||
|
\Log::info("PayPal Payment Completed", [
|
||||||
|
'captureId' => $captureId,
|
||||||
|
'amount' => $amount,
|
||||||
|
'currency' => $currency,
|
||||||
|
'custom_id' => $customId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($customId) {
|
||||||
|
// Extract transaction_id & package_id
|
||||||
|
$parts = explode('-', $customId);
|
||||||
|
$transaction_id = $parts[1] ?? null;
|
||||||
|
$package_id = $parts[3] ?? null;
|
||||||
|
|
||||||
|
if ($transaction_id && $package_id) {
|
||||||
|
$paymentTransaction = PaymentTransaction::find($transaction_id);
|
||||||
|
|
||||||
|
if ($paymentTransaction) {
|
||||||
|
$metadata = [
|
||||||
|
'payment_transaction_id' => $transaction_id,
|
||||||
|
'package_id' => $package_id,
|
||||||
|
'user_id' => $paymentTransaction->user_id,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = $this->assignPackage(
|
||||||
|
$metadata['payment_transaction_id'],
|
||||||
|
$metadata['user_id'],
|
||||||
|
$metadata['package_id']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($response['error']) {
|
||||||
|
\Log::error("PayPal Webhook assignPackage error", [$response['message']]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
\Log::error("PayPal Webhook: PaymentTransaction not found", [
|
||||||
|
'transaction_id' => $transaction_id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['status' => 'success'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 3: Handle failed/refunded ---
|
||||||
|
if (in_array($eventType, ["PAYMENT.CAPTURE.DENIED", "PAYMENT.CAPTURE.REFUNDED"])) {
|
||||||
|
\Log::warning("PayPal Payment Issue", $jsonInput);
|
||||||
|
return response()->json(['status' => 'failed'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['status' => 'ignored'], 200);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\Log::error("PayPal Webhook Error", [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
]);
|
||||||
|
return response()->json(['error' => 'Webhook processing failed'], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handlePaypalCapture($orderId)
|
||||||
|
{
|
||||||
|
$payment = PaymentConfiguration::where([
|
||||||
|
'payment_method' =>'paypal',
|
||||||
|
'status' => 1
|
||||||
|
])->first();
|
||||||
|
|
||||||
|
if (!$payment) {
|
||||||
|
throw new \Exception("PayPal payment configuration not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$paypal = new PayPalPayment(
|
||||||
|
$payment->api_key,
|
||||||
|
$payment->secret_key,
|
||||||
|
$payment->currency_code,
|
||||||
|
$payment->payment_mode
|
||||||
|
);
|
||||||
|
|
||||||
|
return $paypal->capturePayment($orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paypalPaymentSuccess(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
\Log::info("PayPal Success Raw Payload", [$request->all()]);
|
||||||
|
|
||||||
|
$orderId = $request->query('token');
|
||||||
|
|
||||||
|
if (!$orderId) {
|
||||||
|
return view('payment.paypal', [
|
||||||
|
'trxref' => null,
|
||||||
|
'reference' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$paymentResult = $this->handlePaypalCapture($orderId);
|
||||||
|
|
||||||
|
\Log::info("PayPal Success Redirect Capture", (array) $paymentResult);
|
||||||
|
|
||||||
|
return view('payment.paypal', [
|
||||||
|
'trxref' => $orderId,
|
||||||
|
'reference' => $paymentResult['id'] ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\Log::error("PayPal Success Handler Error", [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return view('payment.paypal', [
|
||||||
|
'trxref' => null,
|
||||||
|
'reference' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paypalSuccessCallback(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
\Log::info("PayPal Success callback for app");
|
||||||
|
\Log::info("PayPal Success Raw Payload for app", [$request->all()]);
|
||||||
|
$orderId = $request->query('token');
|
||||||
|
if (!$orderId) {
|
||||||
|
return ResponseService::errorResponse("Missing PayPal order ID.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$paymentResult = $this->handlePaypalCapture($orderId);
|
||||||
|
\Log::info("PayPal Success Redirect Capture For App", (array) $paymentResult);
|
||||||
|
return ResponseService::successResponse("Payment done successfully.");
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\Log::error("PayPal Success Callback Error", [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return ResponseService::errorResponse("Something went wrong during PayPal payment.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paypalCancelCallback()
|
||||||
|
{
|
||||||
|
return ResponseService::successResponse("Payment Cancelled successfully.");
|
||||||
|
}
|
||||||
|
public function paypalCancelCallbackWeb(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$orderId = $request->query('token');
|
||||||
|
return view('payment.paypal', [
|
||||||
|
'trxref' => $orderId ?? null,
|
||||||
|
'reference' => null
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
\Log::error("PayPal Success Handler Error", [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return view('payment.paypalcancle', [
|
||||||
|
'trxref' => null,
|
||||||
|
'reference' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Success Business Login
|
||||||
|
* @param $payment_transaction_id
|
||||||
|
* @param $user_id
|
||||||
|
* @param $package_id
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function assignPackage($payment_transaction_id, $user_id, $package_id) {
|
||||||
|
try {
|
||||||
|
$paymentTransactionData = PaymentTransaction::where('id', $payment_transaction_id)->first();
|
||||||
|
if ($paymentTransactionData == null) {
|
||||||
|
Log::error("Payment Transaction id not found");
|
||||||
|
return [
|
||||||
|
'error' => true,
|
||||||
|
'message' => 'Payment Transaction id not found'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($paymentTransactionData->status == "succeed") {
|
||||||
|
Log::info("Transaction Already Succeed");
|
||||||
|
return [
|
||||||
|
'error' => true,
|
||||||
|
'message' => 'Transaction Already Succeed'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
$paymentTransactionData->update(['payment_status' => "succeed"]);
|
||||||
|
|
||||||
|
$package = Package::find($package_id);
|
||||||
|
|
||||||
|
if (!empty($package)) {
|
||||||
|
// create purchased package record
|
||||||
|
$userPackage = UserPurchasedPackage::create([
|
||||||
|
'package_id' => $package_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'start_date' => Carbon::now(),
|
||||||
|
'end_date' => $package->duration == "unlimited" ? null : Carbon::now()->addDays($package->duration),
|
||||||
|
'total_limit' => $package->item_limit == "unlimited" ? null : $package->item_limit,
|
||||||
|
'listing_duration_type' => $package->listing_duration_type,
|
||||||
|
'listing_duration_days' => $package->listing_duration_days
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = "Package Purchased";
|
||||||
|
$body = 'Amount :- ' . $paymentTransactionData->amount;
|
||||||
|
$userTokens = UserFcmToken::where('user_id', $user_id)->pluck('fcm_token')->toArray();
|
||||||
|
if (!empty($userTokens)) {
|
||||||
|
NotificationService::sendFcmNotification($userTokens, $title, $body, 'payment');
|
||||||
|
}
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'error' => false,
|
||||||
|
'message' => 'Transaction Verified Successfully'
|
||||||
|
];
|
||||||
|
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
Log::error($th->getMessage() . "WebhookController -> assignPackage");
|
||||||
|
return [
|
||||||
|
'error' => true,
|
||||||
|
'message' => 'Error Occurred'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function flutterWaveSuccessCallback(){
|
||||||
|
ResponseService::successResponse("Payment done successfully.");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Failed Business Logic
|
||||||
|
* @param $payment_transaction_id
|
||||||
|
* @param $user_id
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function failedTransaction($payment_transaction_id, $user_id) {
|
||||||
|
try {
|
||||||
|
$paymentTransactionData = PaymentTransaction::find($payment_transaction_id);
|
||||||
|
if (!$paymentTransactionData) {
|
||||||
|
return [
|
||||||
|
'error' => true,
|
||||||
|
'message' => 'Payment Transaction id not found'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$paymentTransactionData->update(['payment_status' => "failed"]);
|
||||||
|
|
||||||
|
$body = 'Amount :- ' . $paymentTransactionData->amount;
|
||||||
|
$userTokens = UserFcmToken::where('user_id', $user_id)->pluck('fcm_token')->toArray();
|
||||||
|
NotificationService::sendFcmNotification($userTokens, 'Package Payment Failed', $body, 'payment');
|
||||||
|
return [
|
||||||
|
'error' => false,
|
||||||
|
'message' => 'Transaction Verified Successfully'
|
||||||
|
];
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
DB::rollBack();
|
||||||
|
Log::error($th->getMessage() . "WebhookController -> failedTransaction");
|
||||||
|
return [
|
||||||
|
'error' => true,
|
||||||
|
'message' => 'Error Occurred'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
78
app/Http/Kernel.php
Normal file
78
app/Http/Kernel.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use App\Http\Middleware\ApiLocalizationMiddleware;
|
||||||
|
use App\Http\Middleware\DemoMiddleware;
|
||||||
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
use Spatie\Permission\Middleware\PermissionMiddleware;
|
||||||
|
|
||||||
|
class Kernel extends HttpKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The application's global HTTP middleware stack.
|
||||||
|
*
|
||||||
|
* These middleware are run during every request to your application.
|
||||||
|
*
|
||||||
|
* @var array<int, class-string|string>
|
||||||
|
*/
|
||||||
|
protected $middleware = [
|
||||||
|
|
||||||
|
\App\Http\Middleware\TrustProxies::class,
|
||||||
|
\Illuminate\Http\Middleware\HandleCors::class,
|
||||||
|
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||||
|
\App\Http\Middleware\TrimStrings::class,
|
||||||
|
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||||
|
// DemoMiddleware::class,
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware groups.
|
||||||
|
*
|
||||||
|
* @var array<string, array<int, class-string|string>>
|
||||||
|
*/
|
||||||
|
protected $middlewareGroups = [
|
||||||
|
'web' => [
|
||||||
|
\App\Http\Middleware\EncryptCookies::class,
|
||||||
|
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
|
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
\App\Http\Middleware\LanguageManager::class,
|
||||||
|
DemoMiddleware::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
'api' => [
|
||||||
|
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||||
|
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
|
ApiLocalizationMiddleware::class,
|
||||||
|
DemoMiddleware::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware.
|
||||||
|
*
|
||||||
|
* These middleware may be assigned to groups or used individually.
|
||||||
|
*
|
||||||
|
* @var array<string, class-string|string>
|
||||||
|
*/
|
||||||
|
protected $routeMiddleware = [
|
||||||
|
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||||
|
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||||
|
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||||
|
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||||
|
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||||
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
|
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||||
|
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||||
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
'language' => \App\Http\Middleware\LanguageManager::class,
|
||||||
|
'permission' => PermissionMiddleware::class,
|
||||||
|
];
|
||||||
|
}
|
||||||
25
app/Http/Middleware/ApiLocalizationMiddleware.php
Normal file
25
app/Http/Middleware/ApiLocalizationMiddleware.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ApiLocalizationMiddleware {
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Closure(Request): (Response|RedirectResponse) $next
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next) {
|
||||||
|
// $request->headers->set('Accept', 'application/json');
|
||||||
|
// $request->headers->set('Content-Type', 'application/json');
|
||||||
|
$localization = $request->header('Content-Language');
|
||||||
|
app()->setLocale($localization);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
app/Http/Middleware/Authenticate.php
Normal file
20
app/Http/Middleware/Authenticate.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class Authenticate extends Middleware {
|
||||||
|
/**
|
||||||
|
* Get the path the user should be redirected to when they are not authenticated.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function redirectTo($request) {
|
||||||
|
if (!$request->expectsJson()) {
|
||||||
|
return route('login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
app/Http/Middleware/DemoMiddleware.php
Normal file
72
app/Http/Middleware/DemoMiddleware.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class DemoMiddleware {
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Closure(Request): (Response|RedirectResponse) $next
|
||||||
|
* @return RedirectResponse|JsonResponse
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next) {
|
||||||
|
$exclude_uri = array(
|
||||||
|
'/user-signup',
|
||||||
|
'/api/user-signup',
|
||||||
|
'/logout',
|
||||||
|
'/api/logout',
|
||||||
|
'/api/manage-favourite'
|
||||||
|
);
|
||||||
|
|
||||||
|
// This URI will not be accessible in Demo mode , regardless of any user
|
||||||
|
$includeUri = array(
|
||||||
|
// '/settings/store',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!config('app.demo_mode')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (in_array($request->getRequestUri(), $exclude_uri) && !in_array($request->getRequestUri(), $includeUri)) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user() === null) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isMethod('get')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
//In APP Demo User should not be allowed to access but in panel demo off user should be allowed to access
|
||||||
|
if (Auth::user()->mobile != "9876598765" && Auth::user()->hasRole('User')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Auth::user()->email == "demooff@gmail.com" && Auth::user()->hasRole('Super Admin')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ($request->is('api/*') || $request->ajax()) {
|
||||||
|
return response()->json(array(
|
||||||
|
'error' => true,
|
||||||
|
'message' => "This is not allowed in the Demo Version.",
|
||||||
|
'code' => 112
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->withErrors([
|
||||||
|
'message' => "This is not allowed in the Demo Version"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Http/Middleware/EncryptCookies.php
Normal file
17
app/Http/Middleware/EncryptCookies.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||||
|
|
||||||
|
class EncryptCookies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the cookies that should not be encrypted.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
60
app/Http/Middleware/LanguageManager.php
Normal file
60
app/Http/Middleware/LanguageManager.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Models\Language;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Session;
|
||||||
|
|
||||||
|
class LanguageManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
|
* @return Response|RedirectResponse
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
// 1. Set a hardcoded fallback first
|
||||||
|
$locale = config('app.fallback_locale', 'en');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Only attempt DB logic if we aren't running a migration/install command
|
||||||
|
if (!app()->runningInConsole()) {
|
||||||
|
|
||||||
|
// Check for the settings table
|
||||||
|
if (\Illuminate\Support\Facades\Schema::hasTable('settings')) {
|
||||||
|
$globalDefault = Cache::rememberForever('global_default_language', function () {
|
||||||
|
return Setting::where('name', 'default_language')->value('value') ?? 'en';
|
||||||
|
});
|
||||||
|
|
||||||
|
$locale = Session::get('locale', $globalDefault);
|
||||||
|
Session::put('locale', $locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the languages table
|
||||||
|
if (\Illuminate\Support\Facades\Schema::hasTable('languages')) {
|
||||||
|
$language = \App\Models\Language::where('code', $locale)->first();
|
||||||
|
if ($language) {
|
||||||
|
Session::put('language', $language);
|
||||||
|
Session::put('is_rtl', (bool)$language->rtl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// If DB connection fails (Access Denied), we catch it here.
|
||||||
|
// The app will continue using the default $locale defined above.
|
||||||
|
}
|
||||||
|
|
||||||
|
app()->setLocale($locale);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||||
|
|
||||||
|
class PreventRequestsDuringMaintenance extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The URIs that should be reachable while maintenance mode is enabled.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
32
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
32
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Providers\RouteServiceProvider;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class RedirectIfAuthenticated {
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
|
* @param string|null ...$guards
|
||||||
|
* @return Response|RedirectResponse
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next, ...$guards) {
|
||||||
|
$guards = empty($guards) ? [null] : $guards;
|
||||||
|
|
||||||
|
foreach ($guards as $guard) {
|
||||||
|
if (Auth::guard($guard)->check()) {
|
||||||
|
return redirect(RouteServiceProvider::HOME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
app/Http/Middleware/TrimStrings.php
Normal file
19
app/Http/Middleware/TrimStrings.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||||
|
|
||||||
|
class TrimStrings extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the attributes that should not be trimmed.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
'current_password',
|
||||||
|
'password',
|
||||||
|
'password_confirmation',
|
||||||
|
];
|
||||||
|
}
|
||||||
20
app/Http/Middleware/TrustHosts.php
Normal file
20
app/Http/Middleware/TrustHosts.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||||
|
|
||||||
|
class TrustHosts extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the host patterns that should be trusted.
|
||||||
|
*
|
||||||
|
* @return array<int, string|null>
|
||||||
|
*/
|
||||||
|
public function hosts()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$this->allSubdomainsOfApplicationUrl(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Middleware/TrustProxies.php
Normal file
28
app/Http/Middleware/TrustProxies.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TrustProxies extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The trusted proxies for this application.
|
||||||
|
*
|
||||||
|
* @var array<int, string>|string|null
|
||||||
|
*/
|
||||||
|
protected $proxies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The headers that should be used to detect proxies.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $headers =
|
||||||
|
Request::HEADER_X_FORWARDED_FOR |
|
||||||
|
Request::HEADER_X_FORWARDED_HOST |
|
||||||
|
Request::HEADER_X_FORWARDED_PORT |
|
||||||
|
Request::HEADER_X_FORWARDED_PROTO |
|
||||||
|
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||||
|
}
|
||||||
22
app/Http/Middleware/ValidateSignature.php
Normal file
22
app/Http/Middleware/ValidateSignature.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||||
|
|
||||||
|
class ValidateSignature extends Middleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The names of the query string parameters that should be ignored.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
// 'fbclid',
|
||||||
|
// 'utm_campaign',
|
||||||
|
// 'utm_content',
|
||||||
|
// 'utm_medium',
|
||||||
|
// 'utm_source',
|
||||||
|
// 'utm_term',
|
||||||
|
];
|
||||||
|
}
|
||||||
16
app/Http/Middleware/VerifyCsrfToken.php
Normal file
16
app/Http/Middleware/VerifyCsrfToken.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||||
|
|
||||||
|
class VerifyCsrfToken extends Middleware {
|
||||||
|
/**
|
||||||
|
* The URIs that should be excluded from CSRF verification.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $except = [
|
||||||
|
'/webhook/*'
|
||||||
|
];
|
||||||
|
}
|
||||||
303
app/Http/Resources/ItemCollection.php
Normal file
303
app/Http/Resources/ItemCollection.php
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use App\Models\City;
|
||||||
|
use App\Models\Language;
|
||||||
|
use App\Services\CurrencyFormatterService;
|
||||||
|
use Illuminate\Contracts\Support\Arrayable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
use Illuminate\Pagination\AbstractPaginator;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use JsonSerializable;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class ItemCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform the resource into an array.
|
||||||
|
*
|
||||||
|
* @return array|Arrayable|JsonSerializable
|
||||||
|
*
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$formatter = app(CurrencyFormatterService::class);
|
||||||
|
$response = [];
|
||||||
|
|
||||||
|
// Get current language once
|
||||||
|
$contentLangCode = $request->header('Content-Language') ?? app()->getLocale();
|
||||||
|
$currentLanguage = Language::where('code', $contentLangCode)->first();
|
||||||
|
$currentLangId = $currentLanguage->id ?? 1;
|
||||||
|
$defaultLangId = 1;
|
||||||
|
|
||||||
|
foreach ($this->collection as $key => $collection) {
|
||||||
|
|
||||||
|
// Base response
|
||||||
|
$response[$key] = $collection->toArray();
|
||||||
|
|
||||||
|
// return response()->json($collection);
|
||||||
|
|
||||||
|
// Currency & price formatting
|
||||||
|
// $response[$key]['currency_symbol'] = $collection->currency_symbol;
|
||||||
|
// $response[$key]['currency_position'] = $collection->currency_position;
|
||||||
|
// $response[$key]['formatted_price'] = $collection->formatted_price;
|
||||||
|
|
||||||
|
// $response[$key]['formatted_min_salary'] = $collection->formatted_min_salary;
|
||||||
|
// $response[$key]['formatted_max_salary'] = $collection->formatted_max_salary;
|
||||||
|
// $response[$key]['formatted_salary_range'] = $collection->formatted_salary_range ?? null;
|
||||||
|
|
||||||
|
// $response[$key]['price'] = $collection->price;
|
||||||
|
|
||||||
|
// return response()->json($collection->currencyRelation->currency_symbol);
|
||||||
|
|
||||||
|
$response[$key]['formatted_price'] = $formatter->formatPrice(
|
||||||
|
$collection?->price,
|
||||||
|
$collection?->currency
|
||||||
|
);
|
||||||
|
|
||||||
|
$response[$key]['formatted_salary_range'] = $formatter->formatSalaryRange(
|
||||||
|
$collection?->min_salary,
|
||||||
|
$collection?->max_salary,
|
||||||
|
$collection?->currency
|
||||||
|
);
|
||||||
|
|
||||||
|
// Feature status
|
||||||
|
$response[$key]['is_feature'] = $collection->status == 'approved' && $collection->relationLoaded('featured_items')
|
||||||
|
? $collection->featured_items->isNotEmpty()
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Favourites
|
||||||
|
if ($collection->relationLoaded('favourites')) {
|
||||||
|
$response[$key]['total_likes'] = $collection->favourites->count();
|
||||||
|
$response[$key]['is_liked'] = Auth::check()
|
||||||
|
? $collection->favourites->where('item_id', $collection->id)->where('user_id', Auth::id())->count() > 0
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User info
|
||||||
|
if ($collection->relationLoaded('user') && ! is_null($collection->user)) {
|
||||||
|
$response[$key]['user'] = $collection->user;
|
||||||
|
$response[$key]['user']['reviews_count'] = $collection->user->sellerReview()->count();
|
||||||
|
$response[$key]['user']['average_rating'] = $collection->user->sellerReview->avg('ratings');
|
||||||
|
if ($collection->user->show_personal_details == 0) {
|
||||||
|
$response[$key]['user']['mobile'] = '';
|
||||||
|
$response[$key]['user']['country_code'] = '';
|
||||||
|
$response[$key]['user']['email'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load city once
|
||||||
|
$city = City::with(['translations', 'state', 'country'])
|
||||||
|
->where('name', $collection->city)
|
||||||
|
->whereHas('state', fn($q) => $q->where('name', $collection->state))
|
||||||
|
->first();
|
||||||
|
|
||||||
|
// Translated item
|
||||||
|
$translatedItem = [
|
||||||
|
'name' => $collection->name,
|
||||||
|
'description' => $collection->description,
|
||||||
|
'address' => $collection->address,
|
||||||
|
'rejected_reason' => $collection->rejected_reason ?? null,
|
||||||
|
'admin_edit_reason' => $collection->admin_edit_reason ?? null,
|
||||||
|
'city' => $city->translated_name ?? $collection->city,
|
||||||
|
'state' => $city->state->translated_name ?? $collection->state,
|
||||||
|
'country' => $city->country->translated_name ?? $collection->country,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($currentLanguage && $collection->relationLoaded('translations')) {
|
||||||
|
$translation = $collection->translations->firstWhere('language_id', $currentLangId);
|
||||||
|
if ($translation) {
|
||||||
|
$translatedItem = [
|
||||||
|
'name' => $translation->name,
|
||||||
|
'description' => $translation->description,
|
||||||
|
'address' => $translation->address,
|
||||||
|
'rejected_reason' => $translation->rejected_reason,
|
||||||
|
'admin_edit_reason' => $translation->admin_edit_reason,
|
||||||
|
'city' => $city->translated_name ?? $collection->city,
|
||||||
|
'state' => $city->state->translated_name ?? $collection->state,
|
||||||
|
'country' => $city->country->translated_name ?? $collection->country,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$response[$key]['translated_item'] = $translatedItem;
|
||||||
|
$response[$key]['translated_area'] = $collection->area->translated_name ?? '';
|
||||||
|
$response[$key]['translated_city'] = $city?->translated_name ?? $collection->city;
|
||||||
|
$response[$key]['translated_state'] = $city->state->translated_name ?? $collection->state;
|
||||||
|
$response[$key]['translated_country'] = $city->country->translated_name ?? $collection->country;
|
||||||
|
$response[$key]['translated_address'] =
|
||||||
|
(! empty($response[$key]['translated_area']) ? $response[$key]['translated_area'] . ', ' : '') .
|
||||||
|
$response[$key]['translated_city'] . ', ' .
|
||||||
|
$response[$key]['translated_state'] . ', ' .
|
||||||
|
$response[$key]['translated_country'];
|
||||||
|
|
||||||
|
// Custom fields
|
||||||
|
if ($collection->relationLoaded('item_custom_field_values')) {
|
||||||
|
$response[$key]['custom_fields'] = [];
|
||||||
|
$response[$key]['translated_custom_fields'] = [];
|
||||||
|
$response[$key]['all_translated_custom_fields'] = [];
|
||||||
|
|
||||||
|
$grouped = $collection->item_custom_field_values->groupBy('custom_field_id');
|
||||||
|
|
||||||
|
foreach ($grouped as $customFieldId => $fieldValues) {
|
||||||
|
$default = $fieldValues->firstWhere('language_id', $defaultLangId);
|
||||||
|
$translated = $currentLanguage ? $fieldValues->firstWhere('language_id', $currentLangId) : null;
|
||||||
|
|
||||||
|
// Default fields
|
||||||
|
if ($default && $default->relationLoaded('custom_field') && ! empty($default->custom_field)) {
|
||||||
|
$field = $default->custom_field;
|
||||||
|
$tempRow = $field->toArray();
|
||||||
|
$tempRow['value'] = $field->type === 'fileinput'
|
||||||
|
? (! empty($default->value) ? [url(Storage::url($default->value))] : [])
|
||||||
|
: (is_array($default->value) ? $default->value : json_decode($default->value, true));
|
||||||
|
$tempRow['custom_field_value'] = $default->toArray();
|
||||||
|
unset($tempRow['custom_field_value']['custom_field']);
|
||||||
|
$tempRow['translated_selected_values'] = $this->resolveTranslatedSelectedValues($field, $default);
|
||||||
|
$response[$key]['custom_fields'][] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translated fields
|
||||||
|
$activeField = $translated ?? $default;
|
||||||
|
if ($activeField && $activeField->relationLoaded('custom_field') && ! empty($activeField->custom_field)) {
|
||||||
|
$field = $activeField->custom_field;
|
||||||
|
$tempRow = $field->toArray();
|
||||||
|
$tempRow['value'] = $field->type === 'fileinput'
|
||||||
|
? (! empty($activeField->value) ? url(Storage::url($activeField->value)) : '')
|
||||||
|
: (is_array($activeField->value) ? $activeField->value : json_decode($activeField->value, true));
|
||||||
|
$tempRow['custom_field_value'] = $activeField->toArray();
|
||||||
|
unset($tempRow['custom_field_value']['custom_field']);
|
||||||
|
$tempRow['language_id'] = $activeField->language_id;
|
||||||
|
$tempRow['translated_selected_values'] = $this->resolveTranslatedSelectedValues($field, $activeField);
|
||||||
|
$response[$key]['translated_custom_fields'][] = $tempRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All translated custom fields
|
||||||
|
foreach ($fieldValues as $fieldValue) {
|
||||||
|
if ($fieldValue->relationLoaded('custom_field') && ! empty($fieldValue->custom_field)) {
|
||||||
|
$field = $fieldValue->custom_field;
|
||||||
|
$tempRow = $field->toArray();
|
||||||
|
// $tempRow['value'] = $field->type === "fileinput"
|
||||||
|
// ? (!empty($fieldValue->value) ? url(Storage::url($fieldValue->value)) : '')
|
||||||
|
// : (is_array($fieldValue->value) ? $fieldValue->value : json_decode($fieldValue->value, true));
|
||||||
|
|
||||||
|
if ($field->type === 'fileinput') {
|
||||||
|
if (! empty($fieldValue->value)) {
|
||||||
|
// ✅ Only decode if it looks like JSON
|
||||||
|
$rawValue = is_string($fieldValue->value) && (str_starts_with($fieldValue->value, '[') || str_starts_with($fieldValue->value, '{'))
|
||||||
|
? json_decode($fieldValue->value, true)
|
||||||
|
: $fieldValue->value;
|
||||||
|
|
||||||
|
// ✅ Handle both single and multiple files
|
||||||
|
if (is_array($rawValue)) {
|
||||||
|
$value = array_map(fn($file) => url(Storage::url($file)), $fieldValue->value);
|
||||||
|
} else {
|
||||||
|
$value = url(Storage::url($fieldValue->value));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$value = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$value = is_array($fieldValue->value)
|
||||||
|
? $fieldValue->value
|
||||||
|
: json_decode($fieldValue->value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempRow['value'] = $value;
|
||||||
|
|
||||||
|
$tempRow['custom_field_value'] = $tempRow;
|
||||||
|
unset($tempRow['custom_field_value']['custom_field']);
|
||||||
|
$tempRow['language_id'] = $fieldValue->language_id;
|
||||||
|
$tempRow['translated_selected_values'] = $this->resolveTranslatedSelectedValues($field, $fieldValue);
|
||||||
|
$response[$key]['all_translated_custom_fields'][] = $tempRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($response[$key]['item_custom_field_values']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item Offers, Reports, Purchases, Job Applications
|
||||||
|
$response[$key]['is_already_offered'] = $collection->relationLoaded('item_offers') && Auth::check()
|
||||||
|
? $collection->item_offers->where('item_id', $collection->id)->where('buyer_id', Auth::id())->count() > 0
|
||||||
|
: false;
|
||||||
|
|
||||||
|
$response[$key]['is_already_reported'] = $collection->relationLoaded('user_reports') && Auth::check()
|
||||||
|
? $collection->user_reports->where('user_id', Auth::id())->count() > 0
|
||||||
|
: false;
|
||||||
|
|
||||||
|
$response[$key]['is_purchased'] = Auth::check() && $collection->sold_to == Auth::id() ? 1 : 0;
|
||||||
|
|
||||||
|
$response[$key]['is_already_job_applied'] = $collection->relationLoaded('job_applications') && Auth::check()
|
||||||
|
? $collection->job_applications->where('item_id', $collection->id)->where('user_id', Auth::id())->count() > 0
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Featured and normal rows
|
||||||
|
$featuredRows = [];
|
||||||
|
$normalRows = [];
|
||||||
|
foreach ($response as $value) {
|
||||||
|
$value['is_feature'] ? $featuredRows[] = $value : $normalRows[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = array_merge($featuredRows, $normalRows);
|
||||||
|
$totalCount = count($response);
|
||||||
|
|
||||||
|
if ($this->resource instanceof AbstractPaginator) {
|
||||||
|
return [
|
||||||
|
...$this->resource->toArray(),
|
||||||
|
'data' => $response,
|
||||||
|
'total_item_count' => $totalCount,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
} catch (Throwable $th) {
|
||||||
|
throw $th;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resolveTranslatedSelectedValues($field, $fieldValue)
|
||||||
|
{
|
||||||
|
$type = $field->type ?? null;
|
||||||
|
$values = $fieldValue->value ?? null;
|
||||||
|
$allPossibleValues = $field->values ?? [];
|
||||||
|
$selected = [];
|
||||||
|
|
||||||
|
$contentLangCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
$currentLanguage = Language::where('code', $contentLangCode)->first();
|
||||||
|
$currentLangId = $currentLanguage->id ?? 1;
|
||||||
|
|
||||||
|
$translatedValues = [];
|
||||||
|
if (! empty($field->translations)) {
|
||||||
|
$translation = collect($field->translations)->firstWhere('language_id', $currentLangId);
|
||||||
|
$translatedValues = $translation['value'] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($type, ['checkbox', 'radio', 'dropdown'])) {
|
||||||
|
$actualValues = is_array($values) ? $values : json_decode($values, true);
|
||||||
|
if (! is_array($actualValues)) {
|
||||||
|
$actualValues = [$actualValues];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($actualValues as $val) {
|
||||||
|
$index = array_search($val, $allPossibleValues);
|
||||||
|
$translatedVal = (is_array($translatedValues) && $index !== false && isset($translatedValues[$index]))
|
||||||
|
? $translatedValues[$index]
|
||||||
|
: $val;
|
||||||
|
|
||||||
|
$selected[] = $translatedVal;
|
||||||
|
}
|
||||||
|
} elseif (in_array($type, ['textbox', 'number'])) {
|
||||||
|
$actualValue = is_array($values) ? ($values[0] ?? '') : $values;
|
||||||
|
$selected[] = $actualValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
394
app/Jobs/AddWatermarkJob.php
Normal file
394
app/Jobs/AddWatermarkJob.php
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use App\Services\CachingService;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Intervention\Image\Facades\Image;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
|
||||||
|
class AddWatermarkJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public string $imagePath;
|
||||||
|
public string $extension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of times the job may be attempted.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public int $tries = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of seconds the job can run before timing out.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public int $timeout = 300; // 5 minutes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of seconds to wait before retrying the job.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public int $backoff = 60; // Wait 60 seconds before retry
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
*
|
||||||
|
* @param string $imagePath
|
||||||
|
* @param string $extension
|
||||||
|
*/
|
||||||
|
public function __construct(string $imagePath, string $extension)
|
||||||
|
{
|
||||||
|
$this->imagePath = $imagePath;
|
||||||
|
$this->extension = $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Log::info('AddWatermarkJob started', [
|
||||||
|
// 'imagePath' => $this->imagePath,
|
||||||
|
// 'extension' => $this->extension,
|
||||||
|
// 'file_exists' => file_exists($this->imagePath)
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
$startTime = microtime(true);
|
||||||
|
|
||||||
|
// Get all settings from cache
|
||||||
|
$settings = CachingService::getSystemSettings()->toArray();
|
||||||
|
|
||||||
|
// Check if watermark is enabled
|
||||||
|
$watermarkEnabled = isset($settings['watermark_enabled']) && (int)$settings['watermark_enabled'] === 1;
|
||||||
|
if (!$watermarkEnabled) {
|
||||||
|
// Log::info('Watermark is disabled, skipping watermark job');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log::info('Watermark is enabled, processing job');
|
||||||
|
|
||||||
|
// Helper function to extract relative path from URL or return path as is
|
||||||
|
$extractPathFromUrl = function($pathOrUrl) {
|
||||||
|
if (empty($pathOrUrl)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a URL, extract the path after /storage/
|
||||||
|
if (filter_var($pathOrUrl, FILTER_VALIDATE_URL)) {
|
||||||
|
$parsedUrl = parse_url($pathOrUrl);
|
||||||
|
$path = $parsedUrl['path'] ?? '';
|
||||||
|
|
||||||
|
// Extract path after /storage/
|
||||||
|
if (preg_match('#/storage/(.+)$#', $path, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If /storage/ not found, try to extract from path
|
||||||
|
if (preg_match('#/([^/]+/.+)$#', $path, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return as is if it's already a relative path
|
||||||
|
return $pathOrUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get watermark image path
|
||||||
|
$watermarkImage = $settings['watermark_image'] ?? null;
|
||||||
|
$watermarkPath = null;
|
||||||
|
$disk = config('filesystems.default');
|
||||||
|
|
||||||
|
if (!empty($watermarkImage)) {
|
||||||
|
// Extract relative path from URL if needed
|
||||||
|
$watermarkRelativePath = $extractPathFromUrl($watermarkImage);
|
||||||
|
|
||||||
|
|
||||||
|
// Try to get absolute path from storage
|
||||||
|
if ($watermarkRelativePath && Storage::disk($disk)->exists($watermarkRelativePath)) {
|
||||||
|
$watermarkPath = Storage::disk($disk)->path($watermarkRelativePath);
|
||||||
|
// Log::info('Watermark found in storage', ['path' => $watermarkPath]);
|
||||||
|
} else {
|
||||||
|
// Try direct path if it's a file path
|
||||||
|
if (file_exists($watermarkImage)) {
|
||||||
|
$watermarkPath = $watermarkImage;
|
||||||
|
// Log::info('Watermark found as direct path', ['path' => $watermarkPath]);
|
||||||
|
} else {
|
||||||
|
// Fallback to public path
|
||||||
|
$watermarkPath = public_path('assets/images/logo/' . basename($watermarkImage));
|
||||||
|
// Log::info('Trying public path fallback', ['path' => $watermarkPath]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If watermark image not found, try company logo
|
||||||
|
if (empty($watermarkPath) || !file_exists($watermarkPath)) {
|
||||||
|
// Log::info('Watermark image not found, trying company logo');
|
||||||
|
$companyLogo = $settings['company_logo'] ?? null;
|
||||||
|
|
||||||
|
if (!empty($companyLogo)) {
|
||||||
|
// Extract relative path from URL if needed
|
||||||
|
$companyLogoRelativePath = $extractPathFromUrl($companyLogo);
|
||||||
|
|
||||||
|
if ($companyLogoRelativePath && Storage::disk($disk)->exists($companyLogoRelativePath)) {
|
||||||
|
$watermarkPath = Storage::disk($disk)->path($companyLogoRelativePath);
|
||||||
|
// Log::info('Company logo found in storage', ['path' => $watermarkPath]);
|
||||||
|
} elseif (file_exists($companyLogo)) {
|
||||||
|
$watermarkPath = $companyLogo;
|
||||||
|
// Log::info('Company logo found as direct path', ['path' => $watermarkPath]);
|
||||||
|
} else {
|
||||||
|
$watermarkPath = public_path('assets/images/logo/' . basename($companyLogo));
|
||||||
|
// Log::info('Trying company logo public path', ['path' => $watermarkPath]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$watermarkPath = public_path('assets/images/logo/logo.png');
|
||||||
|
// Log::info('Using default logo path', ['path' => $watermarkPath]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($watermarkPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get watermark settings with validation
|
||||||
|
$opacity = isset($settings['watermark_opacity']) ? (int)$settings['watermark_opacity'] : 25;
|
||||||
|
$size = isset($settings['watermark_size']) ? (int)$settings['watermark_size'] : 10;
|
||||||
|
$style = $settings['watermark_style'] ?? 'tile';
|
||||||
|
$position = $settings['watermark_position'] ?? 'center';
|
||||||
|
$rotation = isset($settings['watermark_rotation']) ? (int)$settings['watermark_rotation'] : 0;
|
||||||
|
|
||||||
|
// Validate and clamp values to safe ranges
|
||||||
|
$opacity = max(0, min(100, $opacity)); // Clamp between 0-100
|
||||||
|
$size = max(1, min(100, $size)); // Clamp between 1-100
|
||||||
|
$rotation = max(-360, min(360, $rotation)); // Clamp between -360 to 360
|
||||||
|
|
||||||
|
// Normalize rotation to -180 to 180 range for efficiency
|
||||||
|
while ($rotation > 180) $rotation -= 360;
|
||||||
|
while ($rotation < -180) $rotation += 360;
|
||||||
|
|
||||||
|
// Validate style
|
||||||
|
if (!in_array($style, ['tile', 'single', 'center'])) {
|
||||||
|
$style = 'tile';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate position
|
||||||
|
if (!in_array($position, ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'center'])) {
|
||||||
|
$position = 'center';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If style is 'center', force position to center
|
||||||
|
if ($style === 'center') {
|
||||||
|
$position = 'center';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Intervention/Image rotates in the opposite direction compared to our UI/CSS preview.
|
||||||
|
// To keep backend output consistent with the admin preview, invert the rotation sign.
|
||||||
|
$appliedRotation = -$rotation;
|
||||||
|
|
||||||
|
// Load image
|
||||||
|
if (!file_exists($this->imagePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$image = Image::make($this->imagePath);
|
||||||
|
$originalWidth = $image->width();
|
||||||
|
$originalHeight = $image->height();
|
||||||
|
|
||||||
|
|
||||||
|
// Only resize very large images (over 3000px) to speed up processing
|
||||||
|
// This maintains quality for most images while improving performance
|
||||||
|
$maxDimension = 3000;
|
||||||
|
$needsResize = false;
|
||||||
|
|
||||||
|
if ($originalWidth > $maxDimension || $originalHeight > $maxDimension) {
|
||||||
|
$needsResize = true;
|
||||||
|
if ($originalWidth > $originalHeight) {
|
||||||
|
$image->resize($maxDimension, null, fn($c) => $c->aspectRatio());
|
||||||
|
} else {
|
||||||
|
$image->resize(null, $maxDimension, fn($c) => $c->aspectRatio());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and prepare watermark
|
||||||
|
$watermark = Image::make($watermarkPath);
|
||||||
|
$watermarkOriginalWidth = $watermark->width();
|
||||||
|
$watermarkOriginalHeight = $watermark->height();
|
||||||
|
|
||||||
|
// Validate watermark dimensions
|
||||||
|
if ($watermarkOriginalWidth <= 0 || $watermarkOriginalHeight <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate watermark size based on percentage
|
||||||
|
// Ensure minimum size of 10px to prevent invisible watermarks
|
||||||
|
$watermarkWidth = max(10, $image->width() * ($size / 100));
|
||||||
|
// Also ensure it doesn't exceed image dimensions
|
||||||
|
$watermarkWidth = min($watermarkWidth, $image->width());
|
||||||
|
|
||||||
|
$watermark->resize($watermarkWidth, null, fn($c) => $c->aspectRatio());
|
||||||
|
|
||||||
|
// Store dimensions before rotation for tiling calculations
|
||||||
|
$watermarkWidthBeforeRotation = $watermark->width();
|
||||||
|
$watermarkHeightBeforeRotation = $watermark->height();
|
||||||
|
|
||||||
|
// Validate dimensions after resize
|
||||||
|
if ($watermarkWidthBeforeRotation <= 0 || $watermarkHeightBeforeRotation <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply opacity (only if > 0, otherwise skip processing)
|
||||||
|
if ($opacity > 0) {
|
||||||
|
$watermark->opacity($opacity);
|
||||||
|
} else {
|
||||||
|
// Log::info('Watermark opacity is 0, watermark will be invisible');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate the watermark if needed
|
||||||
|
if ($appliedRotation != 0) {
|
||||||
|
$watermark->rotate($appliedRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log::info('Watermark prepared', [
|
||||||
|
// 'original_size' => "{$watermarkOriginalWidth}x{$watermarkOriginalHeight}",
|
||||||
|
// 'resized_size' => "{$watermarkWidthBeforeRotation}x{$watermarkHeightBeforeRotation}",
|
||||||
|
// 'after_rotation_size' => "{$watermark->width()}x{$watermark->height()}",
|
||||||
|
// 'rotation' => $appliedRotation
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🧩 Apply watermark based on style
|
||||||
|
*/
|
||||||
|
if ($style === 'tile') {
|
||||||
|
// Use dimensions before rotation for spacing calculation to ensure proper coverage
|
||||||
|
$baseSpacing = 1.5;
|
||||||
|
$xStep = (int)($watermarkWidthBeforeRotation * $baseSpacing);
|
||||||
|
$yStep = (int)($watermarkHeightBeforeRotation * $baseSpacing);
|
||||||
|
|
||||||
|
// Ensure minimum step size to prevent infinite loops
|
||||||
|
$xStep = max(1, $xStep);
|
||||||
|
$yStep = max(1, $yStep);
|
||||||
|
|
||||||
|
// Calculate number of tiles and optimize spacing if too many
|
||||||
|
$tilesX = (int)ceil($image->width() / max(1, $xStep));
|
||||||
|
$tilesY = (int)ceil($image->height() / max(1, $yStep));
|
||||||
|
$totalTiles = $tilesX * $tilesY;
|
||||||
|
|
||||||
|
// Limit to max 150 tiles for performance (adjust spacing if needed)
|
||||||
|
$maxTiles = 150;
|
||||||
|
if ($totalTiles > $maxTiles) {
|
||||||
|
$factor = sqrt($totalTiles / $maxTiles);
|
||||||
|
$xStep = max(1, (int)($xStep * $factor));
|
||||||
|
$yStep = max(1, (int)($yStep * $factor));
|
||||||
|
// Recalculate after adjustment
|
||||||
|
$tilesX = (int)ceil($image->width() / max(1, $xStep));
|
||||||
|
$tilesY = (int)ceil($image->height() / max(1, $yStep));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ::info('Tiling calculation', [
|
||||||
|
// 'tiles_x' => $tilesX,
|
||||||
|
// 'tiles_y' => $tilesY,
|
||||||
|
// 'total_tiles' => $tilesX * $tilesY,
|
||||||
|
// 'x_step' => $xStep,
|
||||||
|
// Log'y_step' => $yStep
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// Apply tiles - Intervention Image can reuse the same watermark object
|
||||||
|
$tileCount = 0;
|
||||||
|
try {
|
||||||
|
for ($y = 0; $y < $image->height(); $y += $yStep) {
|
||||||
|
for ($x = 0; $x < $image->width(); $x += $xStep) {
|
||||||
|
$image->insert($watermark, 'top-left', $x, $y);
|
||||||
|
$tileCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Log::info('Tiles applied successfully', ['count' => $tileCount]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Log::error('Error applying tiles', [
|
||||||
|
// 'error' => $e->getMessage(),
|
||||||
|
// 'tiles_applied' => $tileCount,
|
||||||
|
// 'trace' => $e->getTraceAsString()
|
||||||
|
// ]);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single watermark or center style
|
||||||
|
$padding = 10;
|
||||||
|
// Single watermark at specified position
|
||||||
|
switch ($position) {
|
||||||
|
case 'top-left':
|
||||||
|
$image->insert($watermark, 'top-left', $padding, $padding);
|
||||||
|
break;
|
||||||
|
case 'top-right':
|
||||||
|
$image->insert($watermark, 'top-right', $padding, $padding);
|
||||||
|
break;
|
||||||
|
case 'bottom-left':
|
||||||
|
$image->insert($watermark, 'bottom-left', $padding, $padding);
|
||||||
|
break;
|
||||||
|
case 'bottom-right':
|
||||||
|
$image->insert($watermark, 'bottom-right', $padding, $padding);
|
||||||
|
break;
|
||||||
|
case 'center':
|
||||||
|
default:
|
||||||
|
$image->insert($watermark, 'center');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ::info('Single watermark applied', [
|
||||||
|
// 'position' => $position,
|
||||||
|
// Log'style' => $style
|
||||||
|
// ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 💾 Save image (replace original)
|
||||||
|
*/
|
||||||
|
// Normalize extension for encoding (jpg -> jpeg)
|
||||||
|
$encodeFormat = strtolower($this->extension);
|
||||||
|
if ($encodeFormat === 'jpg') {
|
||||||
|
$encodeFormat = 'jpeg';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log::info('Saving watermarked image', [
|
||||||
|
// 'path' => $this->imagePath,
|
||||||
|
// 'format' => $encodeFormat,
|
||||||
|
// 'quality' => 85
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$image->encode($encodeFormat, 85)->save($this->imagePath);
|
||||||
|
|
||||||
|
// Verify the file was saved
|
||||||
|
if (!file_exists($this->imagePath)) {
|
||||||
|
throw new \Exception('Image file was not saved successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileSize = filesize($this->imagePath);
|
||||||
|
// Log::info('Watermark job completed successfully', [
|
||||||
|
// 'image_path' => $this->imagePath,
|
||||||
|
// 'file_size' => $fileSize,
|
||||||
|
// 'processing_time' => round(microtime(true) - $startTime, 2) . 's'
|
||||||
|
// ]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error saving watermarked image', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'path' => $this->imagePath,
|
||||||
|
'format' => $encodeFormat,
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('Error in AddWatermarkJob: ' . $e->getMessage(), [
|
||||||
|
'imagePath' => $this->imagePath,
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
app/Jobs/ImportDummyDataJob.php
Normal file
89
app/Jobs/ImportDummyDataJob.php
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class ImportDummyDataJob
|
||||||
|
{
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
Log::info('🚀 Dummy data import started.');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TRUNCATE operations auto-commit in MySQL, so we don't wrap them in a transaction
|
||||||
|
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
||||||
|
DB::table('custom_field_categories')->truncate();
|
||||||
|
DB::table('item_custom_field_values')->truncate();
|
||||||
|
DB::table('custom_fields_translations')->truncate();
|
||||||
|
DB::table('custom_fields')->truncate();
|
||||||
|
DB::table('categories')->truncate();
|
||||||
|
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||||
|
|
||||||
|
// Delete storage directories
|
||||||
|
Storage::deleteDirectory('category');
|
||||||
|
Storage::deleteDirectory('custom-fields');
|
||||||
|
|
||||||
|
// Validate required files
|
||||||
|
$sqlFilePath = public_path('categories_and_sub_custom_field_demo.sql');
|
||||||
|
$zipFilePath = public_path('dummy_data.zip');
|
||||||
|
|
||||||
|
if (!file_exists($sqlFilePath)) {
|
||||||
|
throw new \Exception("SQL file not found at: {$sqlFilePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($zipFilePath)) {
|
||||||
|
throw new \Exception("Images ZIP file not found at: {$zipFilePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute SQL file statements
|
||||||
|
$sqlContent = file_get_contents($sqlFilePath);
|
||||||
|
$sqlContent = preg_replace('/--.*$/m', '', $sqlContent);
|
||||||
|
$sqlContent = preg_replace('/\/\*.*?\*\//s', '', $sqlContent);
|
||||||
|
|
||||||
|
$statements = array_filter(array_map('trim', explode(';', $sqlContent)));
|
||||||
|
foreach ($statements as $statement) {
|
||||||
|
if (!empty($statement)) {
|
||||||
|
try {
|
||||||
|
DB::statement($statement);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('SQL statement failed: ' . $e->getMessage());
|
||||||
|
// Continue with next statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract ZIP file
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ($zip->open($zipFilePath) === TRUE) {
|
||||||
|
$extractPath = storage_path('app/public');
|
||||||
|
if (!File::exists($extractPath)) {
|
||||||
|
File::makeDirectory($extractPath, 0755, true);
|
||||||
|
}
|
||||||
|
$zip->extractTo($extractPath);
|
||||||
|
$zip->close();
|
||||||
|
Log::info('ZIP file extracted successfully.');
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Failed to extract ZIP file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('✅ Dummy data import completed successfully.');
|
||||||
|
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Log::error('❌ Dummy data import failed: ' . $th->getMessage());
|
||||||
|
Log::error('Stack trace: ' . $th->getTraceAsString());
|
||||||
|
|
||||||
|
// Re-enable foreign key checks in case of error
|
||||||
|
try {
|
||||||
|
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Failed to re-enable foreign key checks: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
app/Jobs/SendFcmBatchJob.php
Normal file
85
app/Jobs/SendFcmBatchJob.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Services\NotificationService;
|
||||||
|
use App\Models\UserFcmToken;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class SendFcmBatchJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
protected $title;
|
||||||
|
protected $message;
|
||||||
|
protected $type;
|
||||||
|
protected $customBodyFields;
|
||||||
|
protected $sendToAll;
|
||||||
|
protected $userIds;
|
||||||
|
|
||||||
|
public function __construct($title, $message, $type = 'default', $customBodyFields = [], $sendToAll = false, $userIds = [])
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
$this->message = $message;
|
||||||
|
$this->type = $type;
|
||||||
|
$this->customBodyFields = $customBodyFields;
|
||||||
|
$this->sendToAll = $sendToAll;
|
||||||
|
$this->userIds = $userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
Log::info("🔔 SendFcmBatchJob started");
|
||||||
|
|
||||||
|
// ✅ If sendToAll = true
|
||||||
|
if ($this->sendToAll) {
|
||||||
|
// Fetch tokens with user preference
|
||||||
|
$tokens = UserFcmToken::with('user')
|
||||||
|
->whereHas('user', fn($q) => $q->where('notification', 1))
|
||||||
|
->get(['fcm_token', 'platform_type']);
|
||||||
|
|
||||||
|
// Split tokens by platform
|
||||||
|
$androidIosTokens = $tokens->whereIn('platform_type', ['Android', 'iOS'])->pluck('fcm_token')->toArray();
|
||||||
|
$otherTokens = $tokens->whereNotIn('platform_type', ['Android', 'iOS'])->pluck('fcm_token')->toArray();
|
||||||
|
|
||||||
|
// ✅ Send Android/iOS via Topic
|
||||||
|
if (!empty($androidIosTokens)) {
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
[], $this->title, $this->message, $this->type, $this->customBodyFields, true
|
||||||
|
);
|
||||||
|
Log::info("📱 Topic-based notification sent to Android/iOS users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Send Others via Chunk (if any)
|
||||||
|
if (!empty($otherTokens)) {
|
||||||
|
collect($otherTokens)->chunk(500)->each(function ($chunk) {
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$chunk->toArray(), $this->title, $this->message, $this->type, $this->customBodyFields, false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Log::info("💻 Chunk-based notification sent to other platform users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// ✅ Send to specific selected users
|
||||||
|
UserFcmToken::with('user')
|
||||||
|
->whereIn('user_id', $this->userIds)
|
||||||
|
->whereHas('user', fn($q) => $q->where('notification', 1))
|
||||||
|
->chunk(500, function ($tokens) {
|
||||||
|
$fcmTokens = $tokens->pluck('fcm_token')->toArray();
|
||||||
|
NotificationService::sendFcmNotification(
|
||||||
|
$fcmTokens, $this->title, $this->message, $this->type, $this->customBodyFields, false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Log::info("👥 Notifications sent to selected users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info("✅ SendFcmBatchJob finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
94
app/Models/Area.php
Normal file
94
app/Models/Area.php
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Area extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'country_id',
|
||||||
|
'state_id',
|
||||||
|
'city_id',
|
||||||
|
'state_code',
|
||||||
|
'latitude',
|
||||||
|
'longitude'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $appends = ['translated_name'];
|
||||||
|
protected $with = ['translations'];
|
||||||
|
public function city() {
|
||||||
|
return $this->belongsTo(City::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function state() {
|
||||||
|
return $this->belongsTo(State::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function country() {
|
||||||
|
return $this->belongsTo(Country::class);
|
||||||
|
}
|
||||||
|
public function translations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(AreaTranslation::class);
|
||||||
|
}
|
||||||
|
public function scopeFilter($query, $filterObject) {
|
||||||
|
if (!empty($filterObject)) {
|
||||||
|
foreach ($filterObject as $column => $value) {
|
||||||
|
if ($column == "city.name") {
|
||||||
|
$query->whereHas('city', function ($query) use ($value) {
|
||||||
|
$query->where('city_id', $value);
|
||||||
|
});
|
||||||
|
} elseif ($column == "state.name") {
|
||||||
|
$query->whereHas('state', function ($query) use ($value) {
|
||||||
|
$query->where('state_id', $value);
|
||||||
|
});
|
||||||
|
} elseif ($column == "country.name") {
|
||||||
|
$query->whereHas('country', function ($query) use ($value) {
|
||||||
|
$query->where('country_id', $value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$query->where((string)$column, (string)$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $query;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search) {
|
||||||
|
$search = "%" . $search . "%";
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('id', 'LIKE', $search)
|
||||||
|
->orWhere('name', 'LIKE', $search)
|
||||||
|
->orWhere('city_id', 'LIKE', $search)
|
||||||
|
->orWhere('state_id', 'LIKE', $search)
|
||||||
|
->orWhere('state_code', 'LIKE', $search)
|
||||||
|
->orWhere('country_id', 'LIKE', $search)
|
||||||
|
->orWhereHas('country', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search);
|
||||||
|
})->orWhereHas('state', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search);
|
||||||
|
})->orWhereHas('city', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
public function getTranslatedNameAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
$language = Language::where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
if ($language) {
|
||||||
|
$translations = $this->relationLoaded('translations') ? $this->translations : $this->translations()->get();
|
||||||
|
$translation = $translations->firstWhere('language_id', $language->id);
|
||||||
|
return $translation?->name ?? $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Models/AreaTranslation.php
Normal file
22
app/Models/AreaTranslation.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class AreaTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $fillable = ['area_id', 'language_id', 'name'];
|
||||||
|
|
||||||
|
public function area()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Area::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
app/Models/BlockUser.php
Normal file
15
app/Models/BlockUser.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class BlockUser extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'blocked_user_id'
|
||||||
|
];
|
||||||
|
}
|
||||||
137
app/Models/Blog.php
Normal file
137
app/Models/Blog.php
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class Blog extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $dates = ['created_at', 'updated_at'];
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'slug',
|
||||||
|
'description',
|
||||||
|
'image',
|
||||||
|
'tags'
|
||||||
|
];
|
||||||
|
protected $appends = ['translated_title', 'translated_description', 'translated_tags'];
|
||||||
|
|
||||||
|
public function category() {
|
||||||
|
return $this->belongsTo(Category::class, 'category_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageAttribute($image) {
|
||||||
|
if (!empty($image)) {
|
||||||
|
return url(Storage::url($image));
|
||||||
|
}
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTagsAttribute($value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
return explode(',', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setTagsAttribute($value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$cleaned = array_map(fn($tag) => trim($tag, " \t\n\r\0\x0B\"'"), $value);
|
||||||
|
$this->attributes['tags'] = implode(',', $cleaned);
|
||||||
|
} elseif (is_string($value)) {
|
||||||
|
$this->attributes['tags'] = trim($value, " \t\n\r\0\x0B\"'");
|
||||||
|
} else {
|
||||||
|
$this->attributes['tags'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function translations() {
|
||||||
|
return $this->hasMany(BlogTranslation::class);
|
||||||
|
}
|
||||||
|
public function scopeSearch($query, $search) {
|
||||||
|
$search = "%" . $search . "%";
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('title', 'LIKE', $search)
|
||||||
|
->orWhere('description', 'LIKE', $search)
|
||||||
|
->orWhere('tags', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSort($query, $column, $order) {
|
||||||
|
if ($column == "category_name") {
|
||||||
|
return $query->leftJoin('categories', 'categories.id', '=', 'blogs.category_id')
|
||||||
|
->orderBy('categories.name', $order)
|
||||||
|
->select('blogs.*');
|
||||||
|
}
|
||||||
|
return $query->orderBy($column, $order);
|
||||||
|
}
|
||||||
|
public function getTranslatedTitleAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->title) ? $translation->title : $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedTagsAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!empty($translation?->tags)) {
|
||||||
|
if (is_array($translation->tags)) {
|
||||||
|
return array_map(fn($tag) => trim($tag, " \t\n\r\0\x0B\"'"), $translation->tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($translation->tags)) {
|
||||||
|
return array_map(fn($tag) => trim($tag, " \t\n\r\0\x0B\"'"), explode(',', $translation->tags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_map(fn($tag) => trim($tag, " \t\n\r\0\x0B\"'"), $this->tags ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedDescriptionAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->description) ? $translation->description : $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
app/Models/BlogTranslation.php
Normal file
46
app/Models/BlogTranslation.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class BlogTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $fillable = ['blog_id', 'language_id', 'title', 'description', 'tags'];
|
||||||
|
|
||||||
|
public function blog()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Blog::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
public function getTagsAttribute($value)
|
||||||
|
{
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($value)) {
|
||||||
|
return explode(',', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
public function setTagsAttribute($value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$cleaned = array_map(fn($tag) => trim($tag, " \t\n\r\0\x0B\"'"), $value);
|
||||||
|
$this->attributes['tags'] = implode(',', $cleaned);
|
||||||
|
} elseif (is_string($value)) {
|
||||||
|
$this->attributes['tags'] = trim($value, " \t\n\r\0\x0B\"'");
|
||||||
|
} else {
|
||||||
|
$this->attributes['tags'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
209
app/Models/Category.php
Normal file
209
app/Models/Category.php
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
|
||||||
|
|
||||||
|
class Category extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, HasRecursiveRelationships;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'parent_category_id',
|
||||||
|
'image',
|
||||||
|
'slug',
|
||||||
|
'status',
|
||||||
|
'description',
|
||||||
|
'is_job_category',
|
||||||
|
'price_optional',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getParentKeyName()
|
||||||
|
{
|
||||||
|
return 'parent_category_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $appends = ['translated_name', 'translated_description'];
|
||||||
|
|
||||||
|
protected $with = ['translations'];
|
||||||
|
|
||||||
|
public function subcategories()
|
||||||
|
{
|
||||||
|
return $this->hasMany(self::class, 'parent_category_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function custom_fields()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CustomFieldCategory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageAttribute($image)
|
||||||
|
{
|
||||||
|
if (! empty($image)) {
|
||||||
|
return url(Storage::url($image));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function items()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Item::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function approved_items()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Item::class)->where('status', 'approved');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllItemsCountAttribute()
|
||||||
|
{
|
||||||
|
// Count items in this category
|
||||||
|
$totalItems = $this->items()->where('status', 'approved')->getNonExpiredItems()->count();
|
||||||
|
|
||||||
|
// Count items from ALL descendants (not just loaded ones) using recursive query
|
||||||
|
$descendantIds = $this->descendants()
|
||||||
|
->where('status', 1)
|
||||||
|
->pluck('id')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
if (! empty($descendantIds)) {
|
||||||
|
$descendantItemsCount = Item::without('translations')
|
||||||
|
->whereIn('category_id', $descendantIds)
|
||||||
|
->where('status', 'approved')
|
||||||
|
->getNonExpiredItems()
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$totalItems += $descendantItemsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $totalItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search)
|
||||||
|
{
|
||||||
|
$search = '%'.$search.'%';
|
||||||
|
|
||||||
|
return $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('name', 'LIKE', $search)
|
||||||
|
->orWhere('description', 'LIKE', $search)
|
||||||
|
->orWhereHas('translations', function ($q) use ($search) {
|
||||||
|
$q->where('description', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function slider(): MorphOne
|
||||||
|
{
|
||||||
|
return $this->morphOne(Slider::class, 'model');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CategoryTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedNameAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
if (! empty($languageCode)) {
|
||||||
|
// NOTE : This code can be done in Cache
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
if (empty($language)) {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$languageId = $language->id;
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($languageId) {
|
||||||
|
return $data->language_id == $languageId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ! empty($translation?->name) ? $translation->name : $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedDescriptionAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
if (! empty($languageCode)) {
|
||||||
|
// NOTE : This code can be done in Cache
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
if (empty($language)) {
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
$languageId = $language->id;
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($languageId) {
|
||||||
|
return $data->language_id == $languageId;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ! empty($translation?->description) ? $translation->description : $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parent()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Category::class, 'parent_category_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFullPathAttribute()
|
||||||
|
{
|
||||||
|
$names = [];
|
||||||
|
$current = $this;
|
||||||
|
$visited = [];
|
||||||
|
|
||||||
|
while ($current) {
|
||||||
|
if (in_array($current->id, $visited, true)) {
|
||||||
|
break; // prevent loop
|
||||||
|
}
|
||||||
|
$visited[] = $current->id;
|
||||||
|
|
||||||
|
$names[] = $current->name;
|
||||||
|
$current = $current->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' > ', array_reverse($names));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getItemsGroupedByStatusAttribute()
|
||||||
|
{
|
||||||
|
$counts = [];
|
||||||
|
|
||||||
|
// Count items in this category
|
||||||
|
$items = $this->items()->get();
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$counts[$item->status] = ($counts[$item->status] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include subcategories recursively
|
||||||
|
foreach ($this->subcategories as $subcategory) {
|
||||||
|
$subCounts = $subcategory->items_grouped_by_status;
|
||||||
|
foreach ($subCounts as $status => $count) {
|
||||||
|
$counts[$status] = ($counts[$status] ?? 0) + $count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOtherItemsCountAttribute()
|
||||||
|
{
|
||||||
|
$totalItems = $this->items()->where('status', '!=', 'approved')->count();
|
||||||
|
foreach ($this->subcategories as $subcategory) {
|
||||||
|
$totalItems += $subcategory->other_items_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $totalItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Models/CategoryTranslation.php
Normal file
22
app/Models/CategoryTranslation.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CategoryTranslation extends Model {
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'language_id',
|
||||||
|
'category_id'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function language() {
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function category() {
|
||||||
|
return $this->belongsTo(Category::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
app/Models/Chat.php
Normal file
56
app/Models/Chat.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
|
||||||
|
class Chat extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'sender_id',
|
||||||
|
'item_offer_id',
|
||||||
|
'message',
|
||||||
|
'file',
|
||||||
|
'audio',
|
||||||
|
'is_read'
|
||||||
|
];
|
||||||
|
protected $appends = ['message_type'];
|
||||||
|
|
||||||
|
public function sender() {
|
||||||
|
return $this->belongsTo(User::class, 'sender_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileAttribute($file) {
|
||||||
|
if (!empty($file)) {
|
||||||
|
return url(Storage::url($file));
|
||||||
|
}
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAudioAttribute($value) {
|
||||||
|
if (!empty($value)) {
|
||||||
|
return url(Storage::url($value));
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMessageTypeAttribute() {
|
||||||
|
if (!empty($this->audio)) {
|
||||||
|
return "audio";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->file) && $this->message == "") {
|
||||||
|
return "file";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->file) && $this->message != "") {
|
||||||
|
return "file_and_text";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
}
|
||||||
111
app/Models/City.php
Normal file
111
app/Models/City.php
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
class City extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"state_code",
|
||||||
|
"country_id",
|
||||||
|
"country_code",
|
||||||
|
"latitude",
|
||||||
|
"longitude",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"flag",
|
||||||
|
"wikiDataId",
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $appends = ['translated_name'];
|
||||||
|
protected $with = ['translations'];
|
||||||
|
public function state() {
|
||||||
|
return $this->belongsTo(State::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function country() {
|
||||||
|
return $this->belongsTo(Country::class);
|
||||||
|
}
|
||||||
|
public function translations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CityTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search) {
|
||||||
|
$search = "%" . $search . "%";
|
||||||
|
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('cities.id', 'LIKE', $search)
|
||||||
|
->orWhere('cities.name', 'LIKE', $search)
|
||||||
|
->orWhere('cities.state_id', 'LIKE', $search)
|
||||||
|
->orWhere('cities.state_code', 'LIKE', $search)
|
||||||
|
->orWhere('cities.country_id', 'LIKE', $search)
|
||||||
|
->orWhere('cities.country_code', 'LIKE', $search)
|
||||||
|
->orWhereHas('state', function ($q) use ($search) {
|
||||||
|
$q->where('states.name', 'LIKE', $search);
|
||||||
|
})
|
||||||
|
->orWhereHas('country', function ($q) use ($search) {
|
||||||
|
$q->where('countries.name', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSort($query, $column, $order) {
|
||||||
|
if ($column == "country_name") {
|
||||||
|
$query = $query->leftJoin('countries', 'countries.id', '=', 'cities.country_id')
|
||||||
|
->orderBy('countries.name', $order);
|
||||||
|
} elseif ($column == "state_name") {
|
||||||
|
$query = $query->leftJoin('states', 'states.id', '=', 'cities.state_id')
|
||||||
|
->orderBy('states.name', $order);
|
||||||
|
} else {
|
||||||
|
$query = $query->orderBy("cities.$column", $order);
|
||||||
|
}
|
||||||
|
return $query->select('cities.*');
|
||||||
|
}
|
||||||
|
public function scopeFilter($query, $filterObject) {
|
||||||
|
if (!empty($filterObject)) {
|
||||||
|
foreach ($filterObject as $column => $value) {
|
||||||
|
if($column == "state_name") {
|
||||||
|
$query->whereHas('state', function ($query) use ($value) {
|
||||||
|
$query->where('state_id', $value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
elseif($column == "country_name") {
|
||||||
|
$query->whereHas('country', function ($query) use ($value) {
|
||||||
|
$query->where('country_id', $value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$query->where((string)$column, (string)$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $query;
|
||||||
|
|
||||||
|
}
|
||||||
|
public function areas(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Area::class);
|
||||||
|
}
|
||||||
|
public function getTranslatedNameAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
$language = Language::where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
if ($language) {
|
||||||
|
$translations = $this->relationLoaded('translations') ? $this->translations : $this->translations()->get();
|
||||||
|
$translation = $translations->firstWhere('language_id', $language->id);
|
||||||
|
return $translation?->name ?? $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Models/CityTranslation.php
Normal file
23
app/Models/CityTranslation.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CityTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = ['city_id', 'language_id', 'name'];
|
||||||
|
|
||||||
|
public function city()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(City::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Models/ContactUs.php
Normal file
25
app/Models/ContactUs.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ContactUs extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'contact_us';
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'subject',
|
||||||
|
'message'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function scopeSort($query, $column, $order) {
|
||||||
|
$query = $query->orderBy($column, $order);
|
||||||
|
return $query->select('contact_us.*');
|
||||||
|
}
|
||||||
|
}
|
||||||
102
app/Models/Country.php
Normal file
102
app/Models/Country.php
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Country extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'iso3',
|
||||||
|
'numeric_code',
|
||||||
|
'iso2',
|
||||||
|
'phonecode',
|
||||||
|
'capital',
|
||||||
|
'currency',
|
||||||
|
'currency_name',
|
||||||
|
'currency_symbol',
|
||||||
|
'tld',
|
||||||
|
'native',
|
||||||
|
'region',
|
||||||
|
'region_id',
|
||||||
|
'subregion',
|
||||||
|
'subregion_id',
|
||||||
|
'nationality',
|
||||||
|
'timezones',
|
||||||
|
'translations',
|
||||||
|
'latitude',
|
||||||
|
'longitude',
|
||||||
|
'emoji',
|
||||||
|
'emojiU',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'flag',
|
||||||
|
'wikiDataId',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $appends = ['translated_name'];
|
||||||
|
|
||||||
|
protected $with = ['nameTranslations'];
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search)
|
||||||
|
{
|
||||||
|
$search = '%'.$search.'%';
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('id', 'LIKE', $search)
|
||||||
|
->orWhere('name', 'LIKE', $search)
|
||||||
|
->orWhere('numeric_code', 'LIKE', $search)
|
||||||
|
->orWhere('phonecode', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function states()
|
||||||
|
{
|
||||||
|
return $this->hasMany(State::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nameTranslations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(CountryTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedNameAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (empty($languageCode)) {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
if (! $language) {
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
$nameTranslations = $this->relationLoaded('nameTranslations')
|
||||||
|
? $this->nameTranslations
|
||||||
|
: $this->nameTranslations()->get();
|
||||||
|
|
||||||
|
$translation = $nameTranslations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ! empty($translation?->name) ? $translation->name : $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function translations()
|
||||||
|
// {
|
||||||
|
// return $this->hasMany(CountryTranslation::class);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public function currency()
|
||||||
|
{
|
||||||
|
return $this->hasOne(Currency::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Models/CountryTranslation.php
Normal file
22
app/Models/CountryTranslation.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class CountryTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $fillable = ['country_id', 'language_id', 'name'];
|
||||||
|
public function country()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Country::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
79
app/Models/Currency.php
Normal file
79
app/Models/Currency.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Currency extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'iso_code',
|
||||||
|
'name',
|
||||||
|
'symbol',
|
||||||
|
'symbol_position',
|
||||||
|
'decimal_places',
|
||||||
|
'thousand_separator',
|
||||||
|
'decimal_separator',
|
||||||
|
'country_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function country()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Country::class, 'country_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSort($query, $sortBy, $order)
|
||||||
|
{
|
||||||
|
if ($sortBy === 'country_name' || $sortBy === 'country.name') {
|
||||||
|
return $query
|
||||||
|
->leftJoin('countries', 'currencies.country_id', '=', 'countries.id')
|
||||||
|
->orderBy('countries.name', $order)
|
||||||
|
->select('currencies.*');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->orderBy($sortBy, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// protected $appends = ['translated_name'];
|
||||||
|
|
||||||
|
// public function translations()
|
||||||
|
// {
|
||||||
|
// return $this->hasMany(CurrencyTranslation::class);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search)
|
||||||
|
{
|
||||||
|
$search = '%' . $search . '%';
|
||||||
|
|
||||||
|
return $query->where(function ($q) use ($search) {
|
||||||
|
$q->where('currencies.id', 'LIKE', $search)
|
||||||
|
->orWhere('currencies.iso_code', 'LIKE', $search)
|
||||||
|
->orWhere('currencies.name', 'LIKE', $search)
|
||||||
|
->orWhere('currencies.symbol', 'LIKE', $search)
|
||||||
|
->orWhereHas('country', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public function getTranslatedNameAttribute()
|
||||||
|
// {
|
||||||
|
// $languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
// if ($languageCode) {
|
||||||
|
// $translations = $this->relationLoaded('translations') ? $this->translations : $this->translations()->get();
|
||||||
|
// $translation = $translations->firstWhere('language_id', $languageCode);
|
||||||
|
|
||||||
|
// return $translation?->name ?? $this->name;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return $this->name;
|
||||||
|
// }
|
||||||
|
}
|
||||||
123
app/Models/CustomField.php
Normal file
123
app/Models/CustomField.php
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class CustomField extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'type',
|
||||||
|
'image',
|
||||||
|
'required',
|
||||||
|
'status',
|
||||||
|
'values',
|
||||||
|
'min_length',
|
||||||
|
'max_length',
|
||||||
|
];
|
||||||
|
protected $hidden = ['created_at', 'updated_at'];
|
||||||
|
|
||||||
|
protected $appends = ['translated_name', 'translated_value'];
|
||||||
|
|
||||||
|
public function custom_field_category() {
|
||||||
|
return $this->hasMany(CustomFieldCategory::class, 'custom_field_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translations() {
|
||||||
|
return $this->hasMany(CustomFieldsTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function item_custom_field_values() {
|
||||||
|
return $this->hasMany(ItemCustomFieldValue::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function categories() {
|
||||||
|
return $this->belongsToMany(Category::class, CustomFieldCategory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValuesAttribute($value) {
|
||||||
|
try {
|
||||||
|
return array_values(json_decode($value, true, 512, JSON_THROW_ON_ERROR));
|
||||||
|
} catch (Throwable) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageAttribute($image) {
|
||||||
|
if (!empty($image)) {
|
||||||
|
return url(Storage::url($image));
|
||||||
|
}
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search) {
|
||||||
|
$search = "%" . $search . "%";
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('name', 'LIKE', $search)
|
||||||
|
->orWhere('type', 'LIKE', $search)
|
||||||
|
->orWhere('values', 'LIKE', $search)
|
||||||
|
->orWhere('status', 'LIKE', $search)
|
||||||
|
->orWhereHas('categories', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeFilter($query, $filterObject) {
|
||||||
|
if (!empty($filterObject)) {
|
||||||
|
foreach ($filterObject as $column => $value) {
|
||||||
|
if ($column == "category_names") {
|
||||||
|
$query->whereHas('custom_field_category', function ($query) use ($value) {
|
||||||
|
$query->where('category_id', $value);
|
||||||
|
});
|
||||||
|
} elseif ($column == "type") {
|
||||||
|
$query->where('type', $value);
|
||||||
|
} else {
|
||||||
|
$query->where((string)$column, (string)$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $query;
|
||||||
|
|
||||||
|
}
|
||||||
|
public function getTranslatedNameAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
if ($this->relationLoaded('translations')) {
|
||||||
|
$language = Language::where('code', $languageCode)->first();
|
||||||
|
if ($language) {
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
return $translation->name ?? $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getTranslatedValueAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
if ($this->relationLoaded('translations')) {
|
||||||
|
$language = Language::where('code', $languageCode)->first();
|
||||||
|
if ($language) {
|
||||||
|
$translation = $this->translations->first(fn($t) => $t->language_id == $language->id);
|
||||||
|
try {
|
||||||
|
return $translation && $translation->value
|
||||||
|
? $translation->value
|
||||||
|
: $this->values;
|
||||||
|
} catch (\Throwable) {
|
||||||
|
return $this->values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
27
app/Models/CustomFieldCategory.php
Normal file
27
app/Models/CustomFieldCategory.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static upsert(null[] $array, array $customFieldCategory)
|
||||||
|
*/
|
||||||
|
class CustomFieldCategory extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $hidden = ['created_at', 'updated_at'];
|
||||||
|
protected $fillable = [
|
||||||
|
'category_id',
|
||||||
|
'custom_field_id'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function custom_fields() {
|
||||||
|
return $this->hasOne(CustomField::class, 'id', 'custom_field_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function category() {
|
||||||
|
return $this->hasOne(Category::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Models/CustomFieldsTranslation.php
Normal file
42
app/Models/CustomFieldsTranslation.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class CustomFieldsTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $table = 'custom_fields_translations';
|
||||||
|
protected $fillable = [
|
||||||
|
'custom_field_id',
|
||||||
|
'language_id',
|
||||||
|
'name',
|
||||||
|
'value',
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* Get the custom field that owns this translation.
|
||||||
|
*/
|
||||||
|
public function customField()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CustomField::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the language for this translation.
|
||||||
|
*/
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValueAttribute($value) {
|
||||||
|
try {
|
||||||
|
return array_values(json_decode($value, true, 512, JSON_THROW_ON_ERROR));
|
||||||
|
} catch (Throwable) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
app/Models/Faq.php
Normal file
74
app/Models/Faq.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Faq extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable=[
|
||||||
|
'question',
|
||||||
|
'answer'
|
||||||
|
|
||||||
|
];
|
||||||
|
protected $appends = ['translated_question','translated_answer'];
|
||||||
|
public function translations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(FaqTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslation($languageId)
|
||||||
|
{
|
||||||
|
return $this->translations->where('language_id', $languageId)->first();
|
||||||
|
}
|
||||||
|
public function getTranslatedQuestionAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (empty($languageCode)) {
|
||||||
|
return $this->question;
|
||||||
|
}
|
||||||
|
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
if (!$language) {
|
||||||
|
return $this->question;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations = $this->relationLoaded('translations')
|
||||||
|
? $this->translations
|
||||||
|
: $this->translations()->get();
|
||||||
|
|
||||||
|
$translation = $translations->first(function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->question) ? $translation->question : $this->question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedAnswerAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (empty($languageCode)) {
|
||||||
|
return $this->answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
if (!$language) {
|
||||||
|
return $this->answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations = $this->relationLoaded('translations')
|
||||||
|
? $this->translations
|
||||||
|
: $this->translations()->get();
|
||||||
|
|
||||||
|
$translation = $translations->first(function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->answer) ? $translation->answer : $this->answer;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Models/FaqTranslation.php
Normal file
21
app/Models/FaqTranslation.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class FaqTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $fillable = ['faq_id', 'language_id', 'question', 'answer'];
|
||||||
|
|
||||||
|
public function faq()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Faq::class);
|
||||||
|
}
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/Models/Favourite.php
Normal file
11
app/Models/Favourite.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Favourite extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
}
|
||||||
87
app/Models/FeatureSection.php
Normal file
87
app/Models/FeatureSection.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class FeatureSection extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'slug',
|
||||||
|
'sequence',
|
||||||
|
'filter',
|
||||||
|
'value',
|
||||||
|
'style',
|
||||||
|
'min_price',
|
||||||
|
'max_price',
|
||||||
|
'description'
|
||||||
|
];
|
||||||
|
protected $appends = ['translated_name', 'translated_description'];
|
||||||
|
public function category() {
|
||||||
|
return $this->belongsTo(Category::class, 'category_id', 'id');
|
||||||
|
}
|
||||||
|
public function translations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(FeatureSectionTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search) {
|
||||||
|
$search = "%" . $search . "%";
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('title', 'LIKE', $search)
|
||||||
|
->orWhere('sequence', 'LIKE', $search)
|
||||||
|
->orWhere('filter', 'LIKE', $search)
|
||||||
|
->orWhere('value', 'LIKE', $search)
|
||||||
|
->orWhere('style', 'LIKE', $search)
|
||||||
|
->orWhere('min_price', 'LIKE', $search)
|
||||||
|
->orWhere('max_price', 'LIKE', $search)
|
||||||
|
->orWhere('created_at', 'LIKE', $search)
|
||||||
|
->orWhere('updated_at', 'LIKE', $search)
|
||||||
|
->orWhere('description', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
public function getTranslatedNameAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
// NOTE : This code can be done in Cache
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
|
||||||
|
if (!$language) {
|
||||||
|
$defaultLanguageCode = Setting::where('name', "default_language")->value('value');
|
||||||
|
$language = Language::where('code', $defaultLanguageCode)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->name) ? $translation->name : $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
public function getTranslatedDescriptionAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
if (!$language) {
|
||||||
|
$defaultLanguageCode = Setting::where('name', "default_language")->value('value');
|
||||||
|
$language = Language::where('code', $defaultLanguageCode)->first();
|
||||||
|
}
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->description) ? $translation->description : $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Models/FeatureSectionTranslation.php
Normal file
22
app/Models/FeatureSectionTranslation.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class FeatureSectionTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $fillable = ['feature_section_id', 'language_id', 'name', 'description'];
|
||||||
|
|
||||||
|
public function featureSection()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(FeatureSection::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Models/FeaturedItems.php
Normal file
42
app/Models/FeaturedItems.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
|
||||||
|
class FeaturedItems extends Model {
|
||||||
|
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'start_date',
|
||||||
|
'end_date',
|
||||||
|
'item_id',
|
||||||
|
'package_id',
|
||||||
|
'user_purchased_package_id',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user() {
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeOnlyActive($query) {
|
||||||
|
return $query->whereDate('start_date', '<=', date('Y-m-d'))->where(function ($q) {
|
||||||
|
$q->whereDate('end_date', '>=', date('Y-m-d'))->orWhereNull('end_date');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageAttribute($image) {
|
||||||
|
if (!empty($image)) {
|
||||||
|
return url(Storage::url($image));
|
||||||
|
}
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
public function item()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Item::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
370
app/Models/Item.php
Normal file
370
app/Models/Item.php
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class Item extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'category_id',
|
||||||
|
'currency_id',
|
||||||
|
'name',
|
||||||
|
'price',
|
||||||
|
'description',
|
||||||
|
'latitude',
|
||||||
|
'longitude',
|
||||||
|
'address',
|
||||||
|
'contact',
|
||||||
|
'show_only_to_premium',
|
||||||
|
'video_link',
|
||||||
|
'status',
|
||||||
|
'rejected_reason',
|
||||||
|
'user_id',
|
||||||
|
'image',
|
||||||
|
'country',
|
||||||
|
'state',
|
||||||
|
'city',
|
||||||
|
'area_id',
|
||||||
|
'all_category_ids',
|
||||||
|
'slug',
|
||||||
|
'sold_to',
|
||||||
|
'expiry_date',
|
||||||
|
'min_salary',
|
||||||
|
'max_salary',
|
||||||
|
'is_edited_by_admin',
|
||||||
|
'admin_edit_reason',
|
||||||
|
'package_id',
|
||||||
|
'region_code',
|
||||||
|
'created_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $appends = ['translated_name', 'translated_description'];
|
||||||
|
|
||||||
|
protected $with = ['translations'];
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countryRelation()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Country::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function currency()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Currency::class, 'currency_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function category()
|
||||||
|
{
|
||||||
|
return $this->hasOne(Category::class, 'id', 'category_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function gallery_images()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ItemImages::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function custom_fields()
|
||||||
|
{
|
||||||
|
return $this->hasManyThrough(
|
||||||
|
CustomField::class, CustomFieldCategory::class,
|
||||||
|
'category_id', 'id', 'category_id', 'custom_field_id'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function item_custom_field_values()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ItemCustomFieldValue::class, 'item_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function featured_items()
|
||||||
|
{
|
||||||
|
return $this->hasMany(FeaturedItems::class)->onlyActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function favourites()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Favourite::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function item_offers()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ItemOffer::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user_reports()
|
||||||
|
{
|
||||||
|
return $this->hasMany(UserReports::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sliders(): MorphMany
|
||||||
|
{
|
||||||
|
return $this->morphMany(Slider::class, 'model');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function area()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Area::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function review()
|
||||||
|
{
|
||||||
|
return $this->hasMany(SellerRating::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function job_applications()
|
||||||
|
{
|
||||||
|
return $this->hasMany(JobApplication::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
public function getImageAttribute($image)
|
||||||
|
{
|
||||||
|
return ! empty($image) ? url(Storage::url($image)) : $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatusAttribute($value)
|
||||||
|
{
|
||||||
|
if ($this->deleted_at) {
|
||||||
|
return 'inactive';
|
||||||
|
}
|
||||||
|
if ($this->expiry_date && $this->expiry_date < Carbon::now()) {
|
||||||
|
return 'expired';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ItemTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scopes
|
||||||
|
public function scopeSearch($query, $search)
|
||||||
|
{
|
||||||
|
$search = '%'.$search.'%';
|
||||||
|
|
||||||
|
return $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('name', 'LIKE', $search)
|
||||||
|
->orWhere('description', 'LIKE', $search)
|
||||||
|
->orWhere('price', 'LIKE', $search)
|
||||||
|
->orWhere('image', 'LIKE', $search)
|
||||||
|
->orWhere('latitude', 'LIKE', $search)
|
||||||
|
->orWhere('longitude', 'LIKE', $search)
|
||||||
|
->orWhere('address', 'LIKE', $search)
|
||||||
|
->orWhere('contact', 'LIKE', $search)
|
||||||
|
->orWhere('show_only_to_premium', 'LIKE', $search)
|
||||||
|
->orWhere('status', 'LIKE', $search)
|
||||||
|
->orWhere('video_link', 'LIKE', $search)
|
||||||
|
->orWhere('clicks', 'LIKE', $search)
|
||||||
|
->orWhere('user_id', 'LIKE', $search)
|
||||||
|
->orWhere('country', 'LIKE', $search)
|
||||||
|
->orWhere('state', 'LIKE', $search)
|
||||||
|
->orWhere('city', 'LIKE', $search)
|
||||||
|
->orWhere('category_id', 'LIKE', $search)
|
||||||
|
->orWhereHas('category', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search);
|
||||||
|
})->orWhereHas('user', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search);
|
||||||
|
})->orWhereHas('translations', function ($q) use ($search) {
|
||||||
|
$q->where('name', 'LIKE', $search)
|
||||||
|
->orWhere('description', 'LIKE', $search)
|
||||||
|
->orWhere('address', 'LIKE', $search)
|
||||||
|
->orWhere('city', 'LIKE', $search)
|
||||||
|
->orWhere('state', 'LIKE', $search)
|
||||||
|
->orWhere('country', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeOwner($query)
|
||||||
|
{
|
||||||
|
if (Auth::user()->hasRole('User')) {
|
||||||
|
return $query->where('user_id', Auth::user()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeApproved($query)
|
||||||
|
{
|
||||||
|
return $query->where('status', 'approved');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeNotOwner($query)
|
||||||
|
{
|
||||||
|
return $query->where('user_id', '!=', Auth::user()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSort($query, $column, $order)
|
||||||
|
{
|
||||||
|
if ($column == 'user_name') {
|
||||||
|
return $query->leftJoin('users', 'users.id', '=', 'items.user_id')
|
||||||
|
->orderBy('users.name', $order)
|
||||||
|
->select('items.*');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->orderBy($column, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeFilter($query, $filterObject)
|
||||||
|
{
|
||||||
|
if (empty($filterObject)) {
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($filterObject as $column => $value) {
|
||||||
|
|
||||||
|
if ($column === 'category_id') {
|
||||||
|
|
||||||
|
$categoryId = (int) $value;
|
||||||
|
|
||||||
|
$isParentCategory = Category::where('id', $categoryId)
|
||||||
|
->whereNull('parent_category_id')
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($isParentCategory) {
|
||||||
|
|
||||||
|
$childCategoryIds = Category::where('parent_category_id', $categoryId)
|
||||||
|
->pluck('id')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$allCategoryIds = array_merge([$categoryId], $childCategoryIds);
|
||||||
|
|
||||||
|
$query->where(function ($q) use ($allCategoryIds) {
|
||||||
|
foreach ($allCategoryIds as $catId) {
|
||||||
|
$q->orWhereRaw('FIND_IN_SET(?, all_category_ids)', [$catId]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$query->where('category_id', $categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue; // Skip to next filter
|
||||||
|
|
||||||
|
}
|
||||||
|
if ($column == 'status') {
|
||||||
|
|
||||||
|
if ($value == 'inactive') {
|
||||||
|
$query->whereNotNull('deleted_at')
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereNull('expiry_date')
|
||||||
|
->orWhere('expiry_date', '>=', Carbon::now());
|
||||||
|
});
|
||||||
|
|
||||||
|
} elseif ($value == 'expired') {
|
||||||
|
$query->whereNotNull('expiry_date')
|
||||||
|
->where('expiry_date', '<', Carbon::now())
|
||||||
|
->whereNull('deleted_at');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (in_array($value, [
|
||||||
|
'review', 'approved', 'rejected',
|
||||||
|
'sold out', 'soft rejected',
|
||||||
|
'permanent rejected', 'resubmitted',
|
||||||
|
])) {
|
||||||
|
|
||||||
|
$query->whereNull('deleted_at')
|
||||||
|
->where(function ($q) {
|
||||||
|
$q->whereNull('expiry_date')
|
||||||
|
->orWhere('expiry_date', '>=', Carbon::now());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->where($column, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} elseif ($column == 'featured_status') {
|
||||||
|
|
||||||
|
if ($value == 'featured') {
|
||||||
|
$query->whereHas('featured_items');
|
||||||
|
} elseif ($value == 'premium') {
|
||||||
|
$query->whereDoesntHave('featured_items');
|
||||||
|
}
|
||||||
|
|
||||||
|
} elseif (in_array($column, ['country', 'state', 'city'])) {
|
||||||
|
|
||||||
|
$query->where($column, 'LIKE', '%'.$value.'%');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$query->where((string) $column, (string) $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeOnlyNonBlockedUsers($query)
|
||||||
|
{
|
||||||
|
$blocked_user_ids = BlockUser::where('user_id', Auth::user()->id)
|
||||||
|
->pluck('blocked_user_id');
|
||||||
|
|
||||||
|
return $query->whereNotIn('user_id', $blocked_user_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeGetNonExpiredItems($query)
|
||||||
|
{
|
||||||
|
return $query->where(function ($query) {
|
||||||
|
$query->where('expiry_date', '>', Carbon::now())->orWhereNull('expiry_date');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeIsJobCategory($query, $isJob = 1)
|
||||||
|
{
|
||||||
|
return $query->whereHas('category', function ($q) use ($isJob) {
|
||||||
|
$q->where('is_job_category', $isJob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopePriceOptional($query, $isJob = 1)
|
||||||
|
{
|
||||||
|
return $query->whereHas('category', function ($q) use ($isJob) {
|
||||||
|
$q->where('price_optional', $isJob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedNameAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
$language = Language::where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
if ($language) {
|
||||||
|
$translations = $this->relationLoaded('translations') ? $this->translations : $this->translations()->get();
|
||||||
|
$translation = $translations->firstWhere('language_id', $language->id);
|
||||||
|
|
||||||
|
return $translation?->name ?? $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedDescriptionAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
$language = Language::where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
if ($language) {
|
||||||
|
$translations = $this->relationLoaded('translations') ? $this->translations : $this->translations()->get();
|
||||||
|
$translation = $translations->firstWhere('language_id', $language->id);
|
||||||
|
|
||||||
|
return $translation?->description ?? $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/Models/ItemCustomFieldValue.php
Normal file
39
app/Models/ItemCustomFieldValue.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use JsonException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method static create(array $itemCustomFieldValues)
|
||||||
|
* @method static insert(array $itemCustomFieldValues)
|
||||||
|
*/
|
||||||
|
class ItemCustomFieldValue extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'item_id',
|
||||||
|
'custom_field_id',
|
||||||
|
'value',
|
||||||
|
'language_id'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function custom_field() {
|
||||||
|
return $this->belongsTo(CustomField::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function item()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Item::class, 'item_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValueAttribute($value) {
|
||||||
|
try {
|
||||||
|
return array_values(json_decode($value, true, 512, JSON_THROW_ON_ERROR));
|
||||||
|
} catch (JsonException) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/Models/ItemImages.php
Normal file
23
app/Models/ItemImages.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ItemImages extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'item_id',
|
||||||
|
'image',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getImageAttribute($image) {
|
||||||
|
if (!empty($image)) {
|
||||||
|
return url(Storage::url($image));
|
||||||
|
}
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
||||||
159
app/Models/ItemOffer.php
Normal file
159
app/Models/ItemOffer.php
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class ItemOffer extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'item_id',
|
||||||
|
'seller_id',
|
||||||
|
'buyer_id',
|
||||||
|
'amount',
|
||||||
|
];
|
||||||
|
|
||||||
|
// protected $appends = [
|
||||||
|
// 'formatted_amount',
|
||||||
|
// 'formatted_price',
|
||||||
|
// 'currency_symbol',
|
||||||
|
// 'currency_position',
|
||||||
|
// 'formatted_min_salary',
|
||||||
|
// 'formatted_max_salary',
|
||||||
|
// 'formatted_salary_range',
|
||||||
|
// ];
|
||||||
|
|
||||||
|
public function item()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Item::class)->withTrashed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function seller()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buyer()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function chat()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Chat::class, 'item_offer_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function scopeOwner($query)
|
||||||
|
// {
|
||||||
|
// return $query->where('seller_id', Auth::user()->id)->orWhere('buyer_id', Auth::user()->id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public function scopeOwner($query)
|
||||||
|
{
|
||||||
|
return $query->where(function ($q) {
|
||||||
|
$q->where('seller_id', Auth::id())
|
||||||
|
->orWhere('buyer_id', Auth::id());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function getFormattedAmountAttribute()
|
||||||
|
// {
|
||||||
|
// if (! $this->amount) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $item = $this->relationLoaded('item')
|
||||||
|
// ? $this->item
|
||||||
|
// : $this->item()->with('countryRelation.currency')->first();
|
||||||
|
|
||||||
|
// $symbol = $item?->currency_symbol ?? '₹';
|
||||||
|
// $position = $item?->currency_position ?? 'left';
|
||||||
|
|
||||||
|
// $formatted = number_format($this->amount);
|
||||||
|
|
||||||
|
// return $position === 'right'
|
||||||
|
// ? "{$formatted} {$symbol}"
|
||||||
|
// : "{$symbol} {$formatted}";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function getFormattedPriceAttribute()
|
||||||
|
// {
|
||||||
|
// if (! $this->price) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $symbol = $this->currency_symbol ?? '₹';
|
||||||
|
// $position = $this->currency_position ?? 'left';
|
||||||
|
|
||||||
|
// $formatted = number_format($this->price);
|
||||||
|
|
||||||
|
// return $position === 'right'
|
||||||
|
// ? "{$formatted} {$symbol}"
|
||||||
|
// : "{$symbol} {$formatted}";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function getCurrencySymbolAttribute()
|
||||||
|
// {
|
||||||
|
// return $this->countryRelation?->currency?->symbol ?? '₹';
|
||||||
|
// dd($this->countryRelation);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function getCurrencyPositionAttribute()
|
||||||
|
// {
|
||||||
|
// return $this->countryRelation?->currency?->symbol_position ?? 'left';
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function getFormattedMinSalaryAttribute()
|
||||||
|
// {
|
||||||
|
// if (! $this->min_salary) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $symbol = $this->currency_symbol;
|
||||||
|
// $position = $this->currency_position;
|
||||||
|
// $formatted = number_format($this->min_salary);
|
||||||
|
|
||||||
|
// return $position === 'right'
|
||||||
|
// ? "{$formatted} {$symbol}"
|
||||||
|
// : "{$symbol} {$formatted}";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function getFormattedMaxSalaryAttribute()
|
||||||
|
// {
|
||||||
|
// if (! $this->max_salary) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $symbol = $this->currency_symbol;
|
||||||
|
// $position = $this->currency_position;
|
||||||
|
// $formatted = number_format($this->max_salary);
|
||||||
|
|
||||||
|
// return $position === 'right'
|
||||||
|
// ? "{$formatted} {$symbol}"
|
||||||
|
// : "{$symbol} {$formatted}";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function getFormattedSalaryRangeAttribute()
|
||||||
|
// {
|
||||||
|
// if (! $this->min_salary && ! $this->max_salary) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if ($this->min_salary && ! $this->max_salary) {
|
||||||
|
// return "From {$this->formatted_min_salary}";
|
||||||
|
// }
|
||||||
|
// if (! $this->min_salary && $this->max_salary) {
|
||||||
|
// return "Upto {$this->formatted_max_salary}";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if ($this->min_salary && $this->max_salary) {
|
||||||
|
// return "{$this->formatted_min_salary} - {$this->formatted_max_salary}";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return $this->formatted_min_salary ?? $this->formatted_max_salary;
|
||||||
|
// }
|
||||||
|
}
|
||||||
33
app/Models/ItemTranslation.php
Normal file
33
app/Models/ItemTranslation.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ItemTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'item_id',
|
||||||
|
'language_id',
|
||||||
|
'name',
|
||||||
|
'slug',
|
||||||
|
'description',
|
||||||
|
'address',
|
||||||
|
'rejected_reason',
|
||||||
|
'admin_edit_reason',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function item()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Item::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Models/JobApplication.php
Normal file
42
app/Models/JobApplication.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class JobApplication extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $fillable = [
|
||||||
|
'item_id',
|
||||||
|
'user_id',
|
||||||
|
'recruiter_id',
|
||||||
|
'full_name',
|
||||||
|
'email',
|
||||||
|
'mobile',
|
||||||
|
'resume',
|
||||||
|
'status',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function item()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Item::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
public function recruiter()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
public function getResumeAttribute($image) {
|
||||||
|
if (!empty($image)) {
|
||||||
|
return url(Storage::url($image));
|
||||||
|
}
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Models/Language.php
Normal file
42
app/Models/Language.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class Language extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'name_in_english',
|
||||||
|
'code',
|
||||||
|
'app_file',
|
||||||
|
'panel_file',
|
||||||
|
'web_file',
|
||||||
|
'rtl',
|
||||||
|
'image',
|
||||||
|
'country_code',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getRtlAttribute($rtl)
|
||||||
|
{
|
||||||
|
return $rtl != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageAttribute($value)
|
||||||
|
{
|
||||||
|
if (! empty($value)) {
|
||||||
|
if ($this->code == 'en') {
|
||||||
|
return asset('/assets/images/'.$value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url(Storage::url($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
52
app/Models/Notifications.php
Normal file
52
app/Models/Notifications.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class Notifications extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'message',
|
||||||
|
'image',
|
||||||
|
'item_id',
|
||||||
|
'user_id',
|
||||||
|
'send_to'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
protected $hidden = [
|
||||||
|
'updated_at',
|
||||||
|
'deleted_at'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getImageAttribute($value) {
|
||||||
|
if (!empty($value)) {
|
||||||
|
return url(Storage::url($value));
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search) {
|
||||||
|
$search = "%" . $search . "%";
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('title', 'LIKE', $search)
|
||||||
|
->orWhere('message', 'LIKE', $search)
|
||||||
|
->orWhere('send_to', 'LIKE', $search)
|
||||||
|
->orWhere('item_id', 'LIKE', $search)
|
||||||
|
->orWhere('user_id', 'LIKE', $search)
|
||||||
|
->orWhere('created_at', 'LIKE', $search)
|
||||||
|
->orWhere('updated_at', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function item()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Item::class, 'item_id', 'id');
|
||||||
|
}
|
||||||
|
}
|
||||||
27
app/Models/NumberOtp.php
Normal file
27
app/Models/NumberOtp.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class NumberOtp extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $table = 'number_otps';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'number',
|
||||||
|
'otp',
|
||||||
|
'expire_at',
|
||||||
|
'attempts'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function setOtpAttribute($value) {
|
||||||
|
$this->attributes['otp'] = base64_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOtpAttribute($value) {
|
||||||
|
return base64_decode($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
191
app/Models/Package.php
Normal file
191
app/Models/Package.php
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class Package extends Model {
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'price',
|
||||||
|
'discount_in_percentage',
|
||||||
|
'final_price',
|
||||||
|
'duration',
|
||||||
|
'item_limit',
|
||||||
|
'type',
|
||||||
|
'icon',
|
||||||
|
'description',
|
||||||
|
'status',
|
||||||
|
'ios_product_id',
|
||||||
|
'is_global',
|
||||||
|
'key_points',
|
||||||
|
'listing_duration_type',
|
||||||
|
'listing_duration_days'
|
||||||
|
];
|
||||||
|
protected $appends = ['translated_name', 'translated_description','translated_key_points'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get listing duration type, fallback to package duration if null
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getListingDurationTypeAttribute($value)
|
||||||
|
{
|
||||||
|
// If listing_duration_type is null, return 'package' to indicate it uses package duration
|
||||||
|
if ($value === null) {
|
||||||
|
return 'package';
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get listing duration days, fallback to package duration if null and type is package
|
||||||
|
*
|
||||||
|
* @return int|string|null
|
||||||
|
*/
|
||||||
|
public function getListingDurationDaysAttribute($value)
|
||||||
|
{
|
||||||
|
// If listing_duration_days is null and listing_duration_type is null or 'package', use package duration
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
$listingDurationType = $this->attributes['listing_duration_type'] ?? null;
|
||||||
|
if ($listingDurationType === null || $listingDurationType === '' || $listingDurationType === 'package') {
|
||||||
|
// Use raw duration attribute to avoid infinite loop
|
||||||
|
return $this->attributes['duration'] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user_purchased_packages() {
|
||||||
|
return $this->hasMany(UserPurchasedPackage::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translations()
|
||||||
|
{
|
||||||
|
return $this->hasMany(PackageTranslation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function categories()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Category::class, 'package_categories', 'package_id', 'category_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function package_categories()
|
||||||
|
{
|
||||||
|
return $this->hasMany(PackageCategory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIconAttribute($icon) {
|
||||||
|
if (!empty($icon)) {
|
||||||
|
return url(Storage::url($icon));
|
||||||
|
}
|
||||||
|
return $icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeSearch($query, $search) {
|
||||||
|
$search = "%" . $search . "%";
|
||||||
|
$query = $query->where(function ($q) use ($search) {
|
||||||
|
$q->orWhere('name', 'LIKE', $search)
|
||||||
|
->orWhere('price', 'LIKE', $search)
|
||||||
|
->orWhere('discount_in_percentage', 'LIKE', $search)
|
||||||
|
->orWhere('final_price', 'LIKE', $search)
|
||||||
|
->orWhere('duration', 'LIKE', $search)
|
||||||
|
->orWhere('item_limit', 'LIKE', $search)
|
||||||
|
->orWhere('type', 'LIKE', $search)
|
||||||
|
->orWhere('description', 'LIKE', $search)
|
||||||
|
->orWhere('status', 'LIKE', $search)
|
||||||
|
->orWhere('created_at', 'LIKE', $search)
|
||||||
|
->orWhere('updated_at', 'LIKE', $search);
|
||||||
|
});
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
public function getTranslatedNameAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
// NOTE : This code can be done in Cache
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->name) ? $translation->name : $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
public function getTranslatedDescriptionAttribute() {
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
$language = Language::select(['id', 'code'])->where('code', $languageCode)->first();
|
||||||
|
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return !empty($translation?->description) ? $translation->description : $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeFilter($query, $filterObject) {
|
||||||
|
if (!empty($filterObject)) {
|
||||||
|
foreach ($filterObject as $column => $value) {
|
||||||
|
if ($column == "type") {
|
||||||
|
$query->where('type', $value);
|
||||||
|
} else {
|
||||||
|
$query->where((string)$column, (string)$value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $query;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslatedKeyPointsAttribute()
|
||||||
|
{
|
||||||
|
$languageCode = request()->header('Content-Language') ?? app()->getLocale();
|
||||||
|
|
||||||
|
// ---------- Default / fallback ----------
|
||||||
|
if (!empty($this->key_points)) {
|
||||||
|
$defaultKeyPoints = is_array($this->key_points)
|
||||||
|
? $this->key_points
|
||||||
|
: (json_decode($this->key_points, true) ?? []);
|
||||||
|
} else {
|
||||||
|
$defaultKeyPoints = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Translation ----------
|
||||||
|
if (!empty($languageCode) && $this->relationLoaded('translations')) {
|
||||||
|
$language = Language::select(['id', 'code'])
|
||||||
|
->where('code', $languageCode)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($language) {
|
||||||
|
$translation = $this->translations->first(static function ($data) use ($language) {
|
||||||
|
return $data->language_id == $language->id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!empty($translation?->key_points)) {
|
||||||
|
$translatedKeyPoints = is_array($translation->key_points)
|
||||||
|
? $translation->key_points
|
||||||
|
: json_decode($translation->key_points, true);
|
||||||
|
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE && is_array($translatedKeyPoints)) {
|
||||||
|
return $translatedKeyPoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return is_array($defaultKeyPoints) ? $defaultKeyPoints : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
31
app/Models/PackageCategory.php
Normal file
31
app/Models/PackageCategory.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PackageCategory extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $hidden = ['created_at', 'updated_at'];
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'category_id',
|
||||||
|
'package_id'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function package()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Package::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function category()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Category::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
29
app/Models/PackageTranslation.php
Normal file
29
app/Models/PackageTranslation.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PackageTranslation extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'package_id',
|
||||||
|
'language_id',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'key_points',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function package()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Package::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function language()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user