278 lines
12 KiB
PHP
278 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Transactions;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Http\Request;
|
|
use App\Models\Postcheck;
|
|
use App\Models\Transaction;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class PostchecksController extends Controller
|
|
{
|
|
public function create(Transaction $transaction)
|
|
{
|
|
$acConditions = Postcheck::getAcConditionOptions();
|
|
$blowerConditions = Postcheck::getBlowerConditionOptions();
|
|
$evaporatorConditions = Postcheck::getEvaporatorConditionOptions();
|
|
$compressorConditions = Postcheck::getCompressorConditionOptions();
|
|
|
|
return view('transaction.postchecks.create', compact(
|
|
'transaction',
|
|
'acConditions',
|
|
'blowerConditions',
|
|
'evaporatorConditions',
|
|
'compressorConditions'
|
|
));
|
|
}
|
|
|
|
public function store(Request $request, Transaction $transaction)
|
|
{
|
|
$request->validate([
|
|
'kilometer' => 'required|numeric|min:0',
|
|
'pressure_high' => 'required|numeric|min:0',
|
|
'pressure_low' => 'nullable|numeric|min:0',
|
|
'cabin_temperature' => 'nullable|numeric',
|
|
'cabin_temperature_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'ac_condition' => 'nullable|in:' . implode(',', Postcheck::getAcConditionOptions()),
|
|
'ac_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'blower_condition' => 'nullable|in:' . implode(',', Postcheck::getBlowerConditionOptions()),
|
|
'blower_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'evaporator_condition' => 'nullable|in:' . implode(',', Postcheck::getEvaporatorConditionOptions()),
|
|
'evaporator_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'compressor_condition' => 'nullable|in:' . implode(',', Postcheck::getCompressorConditionOptions()),
|
|
'postcheck_notes' => 'nullable|string',
|
|
'front_image' => 'required|image|mimes:jpeg,png,jpg|max:20480',
|
|
]);
|
|
|
|
$data = [
|
|
'transaction_id' => $transaction->id,
|
|
'postcheck_by' => auth()->id(),
|
|
'postcheck_at' => now(),
|
|
'police_number' => $transaction->police_number,
|
|
'spk_number' => $transaction->spk,
|
|
'kilometer' => $request->kilometer,
|
|
'pressure_high' => $request->pressure_high,
|
|
'pressure_low' => $request->pressure_low,
|
|
'cabin_temperature' => $request->cabin_temperature,
|
|
'ac_condition' => $request->ac_condition,
|
|
'blower_condition' => $request->blower_condition,
|
|
'evaporator_condition' => $request->evaporator_condition,
|
|
'compressor_condition' => $request->compressor_condition,
|
|
'postcheck_notes' => $request->postcheck_notes,
|
|
];
|
|
// Handle file uploads securely
|
|
$imageFields = [
|
|
'front_image', 'cabin_temperature_image', 'ac_image',
|
|
'blower_image', 'evaporator_image'
|
|
];
|
|
foreach ($imageFields as $field) {
|
|
$storedPath = $this->processImageUpload($request, $field, $transaction);
|
|
if ($storedPath) {
|
|
$data[$field] = $storedPath;
|
|
}
|
|
}
|
|
|
|
try {
|
|
Postcheck::create($data);
|
|
return redirect()->route('transaction')->with('success', 'Postcheck berhasil disimpan');
|
|
} catch (\Exception $e) {
|
|
Log::error('Postcheck creation failed: ' . $e->getMessage());
|
|
return back()->withErrors(['error' => 'Gagal menyimpan data postcheck. Silakan coba lagi.']);
|
|
}
|
|
}
|
|
|
|
public function edit(Transaction $transaction, Postcheck $postcheck)
|
|
{
|
|
$acConditions = Postcheck::getAcConditionOptions();
|
|
$blowerConditions = Postcheck::getBlowerConditionOptions();
|
|
$evaporatorConditions = Postcheck::getEvaporatorConditionOptions();
|
|
$compressorConditions = Postcheck::getCompressorConditionOptions();
|
|
|
|
return view('transaction.postchecks.edit', compact(
|
|
'transaction',
|
|
'postcheck',
|
|
'acConditions',
|
|
'blowerConditions',
|
|
'evaporatorConditions',
|
|
'compressorConditions'
|
|
));
|
|
}
|
|
|
|
public function update(Request $request, Transaction $transaction, Postcheck $postcheck)
|
|
{
|
|
$request->validate([
|
|
'kilometer' => 'required|numeric|min:0',
|
|
'pressure_high' => 'required|numeric|min:0',
|
|
'pressure_low' => 'nullable|numeric|min:0',
|
|
'cabin_temperature' => 'nullable|numeric',
|
|
'cabin_temperature_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'ac_condition' => 'nullable|in:' . implode(',', Postcheck::getAcConditionOptions()),
|
|
'ac_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'blower_condition' => 'nullable|in:' . implode(',', Postcheck::getBlowerConditionOptions()),
|
|
'blower_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'evaporator_condition' => 'nullable|in:' . implode(',', Postcheck::getEvaporatorConditionOptions()),
|
|
'evaporator_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
'compressor_condition' => 'nullable|in:' . implode(',', Postcheck::getCompressorConditionOptions()),
|
|
'postcheck_notes' => 'nullable|string',
|
|
'front_image' => 'nullable|image|mimes:jpeg,png,jpg|max:20480',
|
|
]);
|
|
|
|
$updateData = [
|
|
'kilometer' => $request->kilometer,
|
|
'pressure_high' => $request->pressure_high,
|
|
'pressure_low' => $request->pressure_low,
|
|
'cabin_temperature' => $request->cabin_temperature,
|
|
'ac_condition' => $request->ac_condition,
|
|
'blower_condition' => $request->blower_condition,
|
|
'evaporator_condition' => $request->evaporator_condition,
|
|
'compressor_condition' => $request->compressor_condition,
|
|
'postcheck_notes' => $request->postcheck_notes,
|
|
];
|
|
|
|
$imageFields = [
|
|
'front_image', 'cabin_temperature_image', 'ac_image',
|
|
'blower_image', 'evaporator_image'
|
|
];
|
|
|
|
foreach ($imageFields as $field) {
|
|
$newPath = $this->processImageUpload($request, $field, $transaction);
|
|
if ($newPath) {
|
|
// delete old file if exists
|
|
if ($postcheck->{$field}) {
|
|
$this->deleteIfExists($postcheck->{$field});
|
|
}
|
|
$updateData[$field] = $newPath;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$postcheck->update($updateData);
|
|
return redirect()->route('transaction')->with('success', 'Postcheck berhasil diperbarui');
|
|
} catch (\Exception $e) {
|
|
Log::error('Postcheck update failed: ' . $e->getMessage());
|
|
return back()->withErrors(['error' => 'Gagal memperbarui data postcheck. Silakan coba lagi.']);
|
|
}
|
|
}
|
|
|
|
public function print($transaction_id)
|
|
{
|
|
try {
|
|
$postcheck = Postcheck::where('transaction_id', $transaction_id)->firstOrFail();
|
|
|
|
return view('transaction.postchecks.print', compact('postcheck'));
|
|
} catch (\Exception $e) {
|
|
Log::error('Error printing postcheck: ' . $e->getMessage());
|
|
return back()->with('error', 'Gagal membuka halaman print postcheck.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure the base storage directory exists
|
|
*/
|
|
private function ensureStorageDirectoryExists()
|
|
{
|
|
$storagePath = storage_path('app/public');
|
|
|
|
if (!is_dir($storagePath)) {
|
|
if (!mkdir($storagePath, 0755, true)) {
|
|
Log::error('Failed to create storage directory: ' . $storagePath);
|
|
throw new \Exception('Cannot create storage directory: ' . $storagePath . '. Please run: php fix_permissions.php or manually create the directory.');
|
|
}
|
|
Log::info('Created storage directory: ' . $storagePath);
|
|
}
|
|
|
|
// Check if directory is writable
|
|
if (!is_writable($storagePath)) {
|
|
Log::error('Storage directory is not writable: ' . $storagePath);
|
|
throw new \Exception(
|
|
'Storage directory is not writable: ' . $storagePath . '. ' .
|
|
'Please run one of these commands from your project root: ' .
|
|
'1) php fix_permissions.php ' .
|
|
'2) chmod -R 775 storage/ ' .
|
|
'3) mkdir -p storage/app/public/transactions/{transaction_id}/postcheck'
|
|
);
|
|
}
|
|
|
|
// Check if we can create subdirectories
|
|
$testDir = $storagePath . '/test_' . time();
|
|
if (!mkdir($testDir, 0755, true)) {
|
|
Log::error('Cannot create subdirectories in storage: ' . $storagePath);
|
|
throw new \Exception(
|
|
'Cannot create subdirectories in storage. ' .
|
|
'Please check permissions and run: php fix_permissions.php'
|
|
);
|
|
}
|
|
|
|
// Clean up test directory
|
|
rmdir($testDir);
|
|
Log::info('Storage directory is properly configured: ' . $storagePath);
|
|
}
|
|
|
|
/**
|
|
* Securely process image upload to prevent RCE.
|
|
* - Only allows jpeg and png
|
|
* - Generates safe filename
|
|
* - Validates actual image content using getimagesize
|
|
*/
|
|
private function processImageUpload(Request $request, string $field, Transaction $transaction): ?string
|
|
{
|
|
if (!($request->hasFile($field) && $request->file($field)->isValid())) {
|
|
return null;
|
|
}
|
|
|
|
$file = $request->file($field);
|
|
|
|
// Double-check mime type from PHP, disallow svg/gif
|
|
$allowedMimes = ['image/jpeg' => 'jpg', 'image/png' => 'png'];
|
|
$mime = $file->getMimeType();
|
|
if (!array_key_exists($mime, $allowedMimes)) {
|
|
throw new \RuntimeException('Tipe file tidak diperbolehkan');
|
|
}
|
|
|
|
// Verify it's a real image by reading dimensions
|
|
$imageInfo = @getimagesize($file->getRealPath());
|
|
if ($imageInfo === false) {
|
|
throw new \RuntimeException('File bukan gambar yang valid');
|
|
}
|
|
|
|
// Prepare directory
|
|
$directory = 'transactions/' . $transaction->id . '/postcheck';
|
|
$this->ensureStorageDirectoryExists();
|
|
if (!Storage::disk('public')->exists('transactions')) {
|
|
Storage::disk('public')->makeDirectory('transactions', 0755, true);
|
|
}
|
|
if (!Storage::disk('public')->exists('transactions/' . $transaction->id)) {
|
|
Storage::disk('public')->makeDirectory('transactions/' . $transaction->id, 0755, true);
|
|
}
|
|
if (!Storage::disk('public')->exists($directory)) {
|
|
Storage::disk('public')->makeDirectory($directory, 0755, true);
|
|
}
|
|
|
|
// Safe filename
|
|
$ext = $allowedMimes[$mime];
|
|
$filename = time() . '_' . bin2hex(random_bytes(6)) . '_' . $transaction->id . '_' . $field . '.' . $ext;
|
|
|
|
// Store
|
|
$path = $file->storeAs($directory, $filename, 'public');
|
|
Log::info('Secure image stored', ['field' => $field, 'path' => $path]);
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Delete a file from public storage if it exists
|
|
*/
|
|
private function deleteIfExists(string $path): void
|
|
{
|
|
try {
|
|
if ($path && Storage::disk('public')->exists($path)) {
|
|
Storage::disk('public')->delete($path);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::warning('Failed to delete old image', ['path' => $path, 'error' => $e->getMessage()]);
|
|
}
|
|
}
|
|
}
|