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

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