classify admin

This commit is contained in:
Husanjonazamov
2026-02-24 12:52:01 +05:00
commit e0f1989655
769 changed files with 1263008 additions and 0 deletions

18
.editorconfig Normal file
View 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
View 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
View 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>

View File

@@ -0,0 +1,13 @@
{
"applinks": {
"apps": [],
"details": [
{
"appID": "89C47N4UTZ.com.eclassify.wrteam",
"paths": [
"*","/??","/items-details"
]
}
]
}
}

View 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"
]
}
}
]

View 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;
}
}

View 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;
}
}

View 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.');
}
}

View 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.');
}
}

View 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;
}
}

View 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
View 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');
}
}

View 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) {
//
});
}
}

View 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();
}

File diff suppressed because it is too large Load Diff

View 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');
}
}

View 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;
}

View 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();
}
}
}
}

View 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']),
]);
}
}

View 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;
}

View 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');
}
}

View 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 ');
}
}
}

View 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');
}
}
}

View 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
}
}
}

View 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');
}
}
}

File diff suppressed because it is too large Load Diff

View 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();
}
}
}

View 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');
}
}
}

View 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');
}
}
}

View 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);
}
}

View 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;
}
}

View 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());
}
}
}

View 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);
}
}

View 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();
}
}
}

View 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 &quot; 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');
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View 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)
{
//
}
}

View 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');
}
}
}

View 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');
}
}
}

View 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);
}
}

View 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();
}
}
}

View 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'));
}
}
}

View 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 ');
}
}
}

View 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) {
//
}
}

View 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,
]);
}
}

View 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
View 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,
];
}

View 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);
}
}

View 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');
}
}
}

View 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"
]);
}
}

View 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 = [
//
];
}

View 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);
}
}

View 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 = [
//
];
}

View 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);
}
}

View 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',
];
}

View 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(),
];
}
}

View 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;
}

View 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',
];
}

View 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/*'
];
}

View 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;
}
}

View 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(),
]);
}
}
}

View 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());
}
}
}
}

View 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
View 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;
}
}

View 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
View 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
View 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;
}
}

View 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
View 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;
}
}

View 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
View 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
View 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;
}
}

View 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
View 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
View 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);
}
}

View 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
View 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
View 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;
}
}

View 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);
}
}

View 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
View 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;
}
}

View 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
View 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;
}

View 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;
}
}

View 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);
}
}

View 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
View 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;
}
}

View 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
View 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
View 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;
// }
}

View 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);
}
}

View 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
View 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 '';
}
}

View 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
View 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
View 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 : [];
}
}

View 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);
}
}

View 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