From 676ac18e89a79c68721341e10dc4c29515df331c Mon Sep 17 00:00:00 2001
From: Samuel Szajbely
Date: Wed, 3 Jan 2024 18:24:42 +0100
Subject: [PATCH 01/55] refactor
---
.env.example | 2 +
app/Console/Commands.php | 51 ---
app/Console/Commands/CreateDefaultPrinter.php | 39 +++
app/Enums/PrintJobStatus.php | 10 +
.../Controllers/Dormitory/PrintController.php | 323 ------------------
.../Printing/FreePagesController.php | 66 ++++
.../Printing/PrintAccountController.php | 115 +++++++
.../PrintAccountHistoryController.php | 26 ++
.../Dormitory/Printing/PrintJobController.php | 208 +++++++++++
.../Dormitory/Printing/PrinterController.php | 60 ++++
app/Mail/NoPaper.php | 4 +-
app/Models/FreePages.php | 19 +-
app/Models/PrintAccount.php | 91 +++--
app/Models/PrintAccountHistory.php | 6 +-
app/Models/PrintJob.php | 76 +++--
app/Models/Printer.php | 129 +++++++
app/Policies/PrintAccountPolicy.php | 10 +-
app/Utils/Printer.php | 160 ---------
config/print.php | 6 +-
database/factories/PrintJobFactory.php | 4 +-
...9_10_06_224327_create_print_jobs_table.php | 3 +-
...023_12_25_182310_create_printers_table.php | 34 ++
...3_12_25_184733_update_print_jobs_table.php | 38 +++
resources/views/dormitory/print/app.blade.php | 4 +-
.../views/dormitory/print/free.blade.php | 4 +-
.../views/dormitory/print/history.blade.php | 18 +-
.../print/manage/account_history.blade.php | 2 +-
.../dormitory/print/manage/app.blade.php | 4 +-
.../dormitory/print/manage/free.blade.php | 6 +-
.../dormitory/print/manage/modify.blade.php | 7 +-
.../views/dormitory/print/print.blade.php | 35 +-
.../views/dormitory/print/send.blade.php | 8 +-
resources/views/emails/no_paper.blade.php | 2 +-
resources/views/layouts/navbar.blade.php | 4 +-
resources/views/user/printing.blade.php | 4 +-
routes/web.php | 35 +-
36 files changed, 944 insertions(+), 669 deletions(-)
create mode 100644 app/Console/Commands/CreateDefaultPrinter.php
create mode 100644 app/Enums/PrintJobStatus.php
delete mode 100644 app/Http/Controllers/Dormitory/PrintController.php
create mode 100644 app/Http/Controllers/Dormitory/Printing/FreePagesController.php
create mode 100644 app/Http/Controllers/Dormitory/Printing/PrintAccountController.php
create mode 100644 app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php
create mode 100644 app/Http/Controllers/Dormitory/Printing/PrintJobController.php
create mode 100644 app/Http/Controllers/Dormitory/Printing/PrinterController.php
create mode 100644 app/Models/Printer.php
delete mode 100644 app/Utils/Printer.php
create mode 100644 database/migrations/2023_12_25_182310_create_printers_table.php
create mode 100644 database/migrations/2023_12_25_184733_update_print_jobs_table.php
diff --git a/.env.example b/.env.example
index 89de920f8..442eb81f4 100644
--- a/.env.example
+++ b/.env.example
@@ -68,6 +68,8 @@ PREVENT_ACCESSING_MISSING_ATTRIBUTES=false
PRINT_COST_ONESIDED=8
PRINT_COST_TWOSIDED=12
PRINTER_NAME=NemUjBela
+PRINTER_IP=
+PRINTER_PORT=
NETREG=1000
KKT=2000
diff --git a/app/Console/Commands.php b/app/Console/Commands.php
index 6af3de083..a8ef4700a 100644
--- a/app/Console/Commands.php
+++ b/app/Console/Commands.php
@@ -15,57 +15,6 @@ private static function isDebugMode()
return config('app.debug');
}
- public static function getCompletedPrintingJobs()
- {
- $command = "lpstat -W completed -o " . config('print.printer_name') . " | awk '{print $1}'";
- if (self::isDebugMode()) {
- $result = [0];
- } else {
- $result = [];
- exec($command, $result);
- }
- Log::info([$command, $result]);
- return $result;
- }
-
- public static function print($command)
- {
- if (self::isDebugMode()) {
- $job_id = 0;
- $result = "request id is " . config('print.printer_name') . "-" . $job_id . " (1 file(s))";
- } else {
- $result = exec($command);
- }
- Log::info([$command, $result]);
- return $result;
- }
-
- public static function cancelPrintJob(string $jobID)
- {
- $command = "cancel " . $jobID;
- if (self::isDebugMode()) {
- // cancel(1) exits with status code 0 if it succeeds
- $result = ['output' => '', 'exit_code' => 0];
- } else {
- $output = exec($command, $result, $exit_code);
- $result = ['output' => $output, 'exit_code' => $exit_code];
- }
- Log::info([$command, $result]);
- return $result;
- }
-
- public static function getPages($path)
- {
- $command = "pdfinfo " . $path . " | grep '^Pages' | awk '{print $2}' 2>&1";
- if (self::isDebugMode()) {
- $result = rand(1, 10);
- } else {
- $result = exec($command);
- }
- Log::info([$command, $result]);
- return $result;
- }
-
public static function pingRouter($router)
{
if (self::isDebugMode()) {
diff --git a/app/Console/Commands/CreateDefaultPrinter.php b/app/Console/Commands/CreateDefaultPrinter.php
new file mode 100644
index 000000000..c36d20775
--- /dev/null
+++ b/app/Console/Commands/CreateDefaultPrinter.php
@@ -0,0 +1,39 @@
+ env('PRINTER_NAME'),
+ 'ip' => env('PRINTER_IP'),
+ 'port' => env('PRINTER_PORT'),
+ ]);
+
+ return Command::SUCCESS;
+ }
+}
diff --git a/app/Enums/PrintJobStatus.php b/app/Enums/PrintJobStatus.php
new file mode 100644
index 000000000..e64d38d0b
--- /dev/null
+++ b/app/Enums/PrintJobStatus.php
@@ -0,0 +1,10 @@
+middleware('can:use,App\Models\PrintAccount');
- }
-
- public function index()
- {
- return view('dormitory.print.app', [
- "users" => User::printers(),
- "free_pages" => user()->sumOfActiveFreePages()
- ]);
- }
-
- public function noPaper()
- {
- $reporterName = user()->name;
- $admins = User::withRole(Role::SYS_ADMIN)->get();
- foreach ($admins as $admin) {
- Mail::to($admin)->send(new NoPaper($admin->name, $reporterName));
- }
- Cache::put('print.no-paper', now(), 3600);
- return redirect()->back()->with('message', __('mail.email_sent'));
- }
-
- public function addedPaper()
- {
- $this->authorize('handleAny', PrintAccount::class);
-
- Cache::forget('print.no-paper');
- return redirect()->back()->with('message', __('general.successful_modification'));
- }
-
- public function admin()
- {
- $this->authorize('handleAny', PrintAccount::class);
-
- return view('dormitory.print.manage.app', ["users" => User::printers()]);
- }
-
- public function print(Request $request)
- {
- $validator = Validator::make($request->all(), [
- 'file_to_upload' => 'required|file|mimes:pdf|max:' . config('print.pdf_size_limit'),
- 'number_of_copies' => 'required|integer|min:1'
- ]);
- $validator->validate();
-
- $is_two_sided = $request->has('two_sided');
- $number_of_copies = $request->number_of_copies;
- $use_free_pages = $request->use_free_pages;
- $file = $request->file_to_upload;
- $filename = $file->getClientOriginalName();
- $path = $this->storeFile($file);
-
- $printer = new Printer($filename, $path, $use_free_pages, $is_two_sided, $number_of_copies);
-
- return $printer->print();
- }
-
- public function transferBalance(Request $request)
- {
- $validator = Validator::make($request->all(), [
- 'balance' => 'required|integer|min:1',
- 'user_to_send' => 'required|integer|exists:users,id'
- ]);
- $validator->validate();
-
- $balance = $request->balance;
- $user = User::find($request->user_to_send);
- $from_account = user()->printAccount;
- $to_account = $user->printAccount;
-
- if (!$from_account->hasEnoughMoney($balance)) {
- return $this->handleNoBalance();
- }
- $to_account->update(['last_modified_by' => user()->id]);
- $from_account->update(['last_modified_by' => user()->id]);
-
- $from_account->decrement('balance', $balance);
- $to_account->increment('balance', $balance);
-
- // Send notification mail
- Mail::to($user)->queue(new ChangedPrintBalance($user, $balance, user()->name));
-
- return redirect()->back()->with('message', __('general.successful_transaction'));
- }
-
- public function modifyBalance(Request $request)
- {
- $validator = Validator::make($request->all(), [
- 'user_id_modify' => 'required|integer|exists:users,id',
- 'balance' => 'required|integer',
- ]);
- $validator->validate();
-
- $balance = $request->balance;
- $user = User::find($request->user_id_modify);
- $print_account = $user->printAccount;
-
- $this->authorize('modify', $print_account);
-
- if ($balance < 0 && !$print_account->hasEnoughMoney($balance)) {
- return $this->handleNoBalance();
- }
- $print_account->update(['last_modified_by' => user()->id]);
- $print_account->increment('balance', $balance);
-
- $admin_checkout = Checkout::admin();
- Transaction::create([
- 'checkout_id' => $admin_checkout->id,
- 'receiver_id' => user()->id,
- 'payer_id' => $user->id,
- 'semester_id' => Semester::current()->id,
- 'amount' => $request->balance,
- 'payment_type_id' => PaymentType::print()->id,
- 'comment' => null,
- 'moved_to_checkout' => null,
- ]);
-
- // Send notification mail
- Mail::to($user)->queue(new ChangedPrintBalance($user, $balance, user()->name));
-
- return redirect()->back()->with('message', __('general.successful_modification'));
- }
-
- public function addFreePages(Request $request)
- {
- $validator = Validator::make($request->all(), [
- 'user_id_free' => 'required|integer|exists:users,id',
- 'free_pages' => 'required|integer|min:1',
- 'deadline' => 'required|date|after:now',
- ]);
- $validator->validate();
-
- $this->authorize('create', FreePages::class);
-
- FreePages::create([
- 'user_id' => $request->user_id_free,
- 'amount' => $request->free_pages,
- 'deadline' => $request->deadline,
- 'last_modified_by' => user()->id,
- 'comment' => $request->comment,
- ]);
-
- return redirect()->back()->with('message', __('general.successfully_added'));
- }
-
- public function listAllPrintJobs()
- {
- $this->authorize('viewAny', PrintJob::class);
-
- $this->updateCompletedPrintingJobs();
-
- $columns = ['created_at', 'filename', 'cost', 'state', 'user.name'];
- $printJobs = PrintJob::join('users as user', 'user.id', '=', 'user_id')
- ->select('print_jobs.*')
- ->with('user')
- ->orderby('print_jobs.created_at', 'desc');
-
- return $this->printJobsPaginator($printJobs, $columns);
- }
-
- public function listPrintJobs()
- {
- $this->authorize('viewSelf', PrintJob::class);
-
- $this->updateCompletedPrintingJobs();
-
- $columns = ['created_at', 'filename', 'cost', 'state'];
- $printJobs = user()->printJobs()->orderby('created_at', 'desc');
-
- return $this->printJobsPaginator($printJobs, $columns);
- }
-
- public function listAllFreePages()
- {
- $this->authorize('viewAny', FreePages::class);
-
- $columns = ['amount', 'deadline', 'modifier', 'comment', 'user.name', 'created_at'];
-
- $freePages = FreePages::join('users as user', 'user.id', '=', 'user_id');
-
- return $this->freePagesPaginator($freePages, $columns);
- }
-
- public function listFreePages()
- {
- $this->authorize('viewSelf', FreePages::class);
-
- $columns = ['amount', 'deadline', 'modifier', 'comment'];
- $freePages = user()->freePages();
-
- return $this->freePagesPaginator($freePages, $columns);
- }
-
- public function listPrintAccountHistory()
- {
- $this->authorize('viewAny', PrintJob::class);
-
- $columns = ['user.name', 'balance_change', 'free_page_change', 'deadline_change', 'modifier.name', 'modified_at'];
- $paginator = TabulatorPaginator::from(
- PrintAccountHistory::join('users as user', 'user.id', '=', 'user_id')
- ->join('users as modifier', 'modifier.id', '=', 'modified_by')
- ->select('print_account_history.*')
- ->with('user')
- ->with('modifier')
- )->sortable($columns)
- ->filterable($columns)
- ->paginate();
- return $paginator;
- }
-
- public function cancelPrintJob($id)
- {
- $printJob = PrintJob::findOrFail($id);
-
- $this->authorize('update', $printJob);
-
- if ($printJob->state === PrintJob::QUEUED) {
- $result = Commands::cancelPrintJob($printJob->job_id);
-
- if ($result['exit_code'] == 0) {
- // Command was successful, job cancelled.
- $printJob->state = PrintJob::CANCELLED;
- // Reverting balance change
- // TODO: test what happens when cancelled right before the end
- $printAccount = $printJob->user->printAccount;
- $printAccount->update(['last_modified_by' => user()->id]);
- $printAccount->increment('balance', $printJob->cost);
- } else {
- if (strpos($result['output'], "already canceled") !== false) {
- return redirect()->back()->with('error', __('print.already_cancelled'));
- } elseif (strpos($result['output'], "already completed") !== false) {
- $printJob->state = PrintJob::SUCCESS;
- return redirect()->back()->with('message', __('general.successful_modification'));
- } else {
- Log::warning("cannot cancel print job " . $printJob->job_id ." for unknown reasons: " . var_dump($result));
- return redirect()->back()->with('error', __('general.unknown_error'));
- }
- }
- $printJob->save();
- }
- }
-
- /** Private helper functions */
-
- private function printJobsPaginator($printJobs, $columns)
- {
- $paginator = TabulatorPaginator::from($printJobs)->sortable($columns)->filterable($columns)->paginate();
-
- $paginator->getCollection()->transform(PrintJob::translateStates());
- $paginator->getCollection()->transform(PrintJob::addCurrencyTag());
-
- return $paginator;
- }
-
- private function freePagesPaginator($freePages, $columns)
- {
- $paginator = TabulatorPaginator::from(
- $freePages->join('users as creator', 'creator.id', '=', 'last_modified_by')
- ->select('creator.name as modifier', 'printing_free_pages.*')
- ->with('user')
- )->sortable($columns)->filterable($columns)->paginate();
- return $paginator;
- }
-
- private function updateCompletedPrintingJobs()
- {
- try {
- $result = Commands::getCompletedPrintingJobs();
- PrintJob::whereIn('job_id', $result)->update(['state' => PrintJob::SUCCESS]);
- } catch (\Exception $e) {
- Log::error("Printing error at line: " . __FILE__ . ":" . __LINE__ . " (in function " . __FUNCTION__ . "). " . $e->getMessage());
- }
- }
-
- private function storeFile($file)
- {
- $path = $file->storePubliclyAs(
- '',
- md5(rand(0, 100000) . date('c')) . '.pdf',
- 'printing'
- );
- $path = Storage::disk('printing')->path($path);
-
- return $path;
- }
-
- private function handleNoBalance()
- {
- return back()->withInput()->with('error', __('print.no_balance'));
- }
-}
diff --git a/app/Http/Controllers/Dormitory/Printing/FreePagesController.php b/app/Http/Controllers/Dormitory/Printing/FreePagesController.php
new file mode 100644
index 000000000..173982bee
--- /dev/null
+++ b/app/Http/Controllers/Dormitory/Printing/FreePagesController.php
@@ -0,0 +1,66 @@
+authorize('viewAny', FreePages::class);
+
+ return $this->freePagesPaginator(
+ freePages: FreePages::with('user'),
+ columns: [
+ 'amount',
+ 'deadline',
+ 'modifier',
+ 'comment',
+ 'user.name',
+ 'created_at',
+ ]
+ );
+ }
+
+ $this->authorize('viewSelf', FreePages::class);
+
+ return $this->freePagesPaginator(
+ freePages: user()->freePages(),
+ columns: [
+ 'amount',
+ 'deadline',
+ 'modifier',
+ 'comment',
+ ]
+ );
+ }
+
+ private function freePagesPaginator(Builder $freePages, array $columns) {
+ $paginator = TabulatorPaginator::from(
+ $freePages->with('modifier')
+ )->sortable($columns)->filterable($columns)->paginate();
+ return $paginator;
+ }
+
+ public function store(Request $request) {
+ $data = $request->validate([
+ "user_id" => "required|exists:users,id",
+ "amount" => "required|integer|min:1",
+ "deadline" => "required|date|after:date:now",
+ "comment" => "string",
+ ]);
+
+ $this->authorize('create', FreePages::class);
+
+ FreePages::create($data + [
+ "last_modified_by" => user()->id,
+ ]);
+
+ return redirect()->back()->with('message', __('general.successfully_added'));
+ }
+}
diff --git a/app/Http/Controllers/Dormitory/Printing/PrintAccountController.php b/app/Http/Controllers/Dormitory/Printing/PrintAccountController.php
new file mode 100644
index 000000000..93769c7b1
--- /dev/null
+++ b/app/Http/Controllers/Dormitory/Printing/PrintAccountController.php
@@ -0,0 +1,115 @@
+validate([
+ 'amount' => 'required|integer',
+ 'user' => 'required|exists:users,id', // Normally this would be a path parameter for the PrintAccount, but we can't do that because of the limitations of blade templates
+ 'other_user' => 'nullable|exists:users,id',
+ ]);
+
+ $printAccount = User::find($request->get('user'))->printAccount;
+
+ // If user can not even transfer balance, we can stop here
+ if (user()->cannot('transferBalance', $printAccount)) {
+ abort(403);
+ }
+
+ $otherAccount = $request->other_user ? User::find($request->get('other_user'))->printAccount : null;
+
+ // This is a transfer between accounts
+ if ($otherAccount !== null) {
+ // Cannot transfer to yourself
+ if ($otherAccount->user_id === $printAccount->user_id) {
+ abort(400);
+ }
+
+ // Cannot transfer from other user's account (even if you are admin)
+ if ($printAccount->user_id !== user()->id) {
+ abort(403);
+ }
+
+ $amount = $request->get('amount');
+
+ // This would be effectively stealing printing money from the other account
+ if ($amount < 0) {
+ abort(400);
+ }
+
+ // Cannot transfer if there is not enough balance to be transfered
+ if ($printAccount->balance < $amount) {
+ return $this->returnNoBalance();
+ }
+
+ $printAccount->update([
+ 'balance' => $printAccount->balance - $amount,
+ 'last_modified_by' => user()->id,
+ ]);
+
+ $otherAccount->update([
+ 'balance' => $otherAccount->balance + $amount,
+ 'last_modified_by' => user()->id,
+ ]);
+
+ Mail::to($printAccount->user)->queue(new ChangedPrintBalance($printAccount->user, $request->get('amount'), user()->name));
+
+ return redirect()->back()->with('message', __('general.successful_transaction'));
+ }
+ // This is a modification of the current account
+ else {
+ // Only admins can modify accounts
+ if (user()->cannot('modify', $printAccount)) {
+ abort(403);
+ }
+
+ $amount = $request->get('amount');
+
+ if ($amount < 0 && $printAccount->balance < $amount) {
+ $this->returnNoBalance();
+ }
+
+ $printAccount->update([
+ 'balance' => $printAccount->balance + $amount,
+ 'last_modified_by' => user()->id,
+ ]);
+
+ Mail::to($printAccount->user)->queue(new ChangedPrintBalance($printAccount->user, $request->get('amount'), user()->name));
+
+ $adminCheckout = Checkout::admin();
+ Transaction::create([
+ 'checkout_id' => $adminCheckout->id,
+ 'receiver_id' => user()->id,
+ 'payer_id' => $printAccount->user->id,
+ 'semester_id' => Semester::current()->id,
+ 'amount' => $amount,
+ 'payment_type_id' => PaymentType::print()->id,
+ 'comment' => null,
+ 'moved_to_checkout' => null,
+ ]);
+
+ return redirect()->back()->with('message', __('general.successful_modification'));
+ }
+ }
+
+ private function returnNoBalance()
+ {
+ return back()->withInput()->with('error', __('print.no_balance'));
+ }
+}
diff --git a/app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php b/app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php
new file mode 100644
index 000000000..0c17bb03c
--- /dev/null
+++ b/app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php
@@ -0,0 +1,26 @@
+authorize('viewAny', PrintJob::class);
+
+ $columns = ['user.name', 'balance_change', 'free_page_change', 'deadline_change', 'modifier.name', 'modified_at'];
+ return TabulatorPaginator::from(
+ PrintAccountHistory::join('users as user', 'user.id', '=', 'user_id')
+ ->join('users as modifier', 'modifier.id', '=', 'modified_by')
+ ->select('print_account_history.*')
+ ->with('user') // TODO: check this
+ ->with('modifier')
+ )->sortable($columns)
+ ->filterable($columns)
+ ->paginate();
+ }
+}
diff --git a/app/Http/Controllers/Dormitory/Printing/PrintJobController.php b/app/Http/Controllers/Dormitory/Printing/PrintJobController.php
new file mode 100644
index 000000000..edb9e2daa
--- /dev/null
+++ b/app/Http/Controllers/Dormitory/Printing/PrintJobController.php
@@ -0,0 +1,208 @@
+authorize('viewAny', PrintJob::class);
+
+ PrintJob::checkAndUpdateStatuses();
+
+ return $this->paginatorFrom(
+ printJobs: PrintJob::query()
+ ->with('user')
+ ->orderBy('print_jobs.created_at', 'desc'), // TODO: test if it works without join
+ columns: [
+ 'created_at',
+ 'filename',
+ 'cost',
+ 'state',
+ 'user.name',
+ ]
+ );
+ }
+
+ $this->authorize('viewSelf', PrintJob::class);
+
+ PrintJob::checkAndUpdateStatuses();
+ return $this->paginatorFrom(
+ printJobs: user()->printJobs()->orderBy('created_at', 'desc'),
+ columns: [
+ 'created_at',
+ 'filename',
+ 'cost',
+ 'state',
+ ]
+ );
+ }
+
+ /**
+ * Prints a document, then stores the corresponding `PrintJob`.
+ * @param Request $request
+ * @return RedirectResponse
+ */
+ public function store(Request $request) {
+ $request->validate([
+ 'file' => 'required|file',
+ 'copies' => 'required|integer|min:1',
+ 'two_sided' => 'boolean',
+ 'printer_id' => 'nullable|exists:printers,id',
+ 'use_free_pages' => 'nullable|boolean',
+ ]);
+
+ $useFreePages = $request->boolean('use_free_pages');
+ $copyNumber = $request->get('copies');
+ $twoSided = $request->get('two_sided');
+ $file = $request->file('file');
+
+ /** @var Printer */
+ $printer = $request->printer_id ? Printer::find($request->get("printer_id")) : Printer::firstWhere('name', config('print.printer_name'));
+
+ $path = $file->store('print-documents');
+ $pageNumber = Printer::getDocumentPageNumber($path);
+
+ /** @var PrintAccount */
+ $printAccount = user()->printAccount;
+
+ if (($useFreePages && $printAccount->hasEnoughFreePages($pageNumber, $copyNumber, $twoSided)) ||
+ (!$useFreePages && $printAccount->hasEnoughBalance($pageNumber, $copyNumber, $twoSided))
+ ) {
+ return back()->with('error', __('print.no_balance'));
+ }
+
+ $jobId = null;
+ try {
+ $jobId = $printer->print($twoSided, $copyNumber, $path);
+ } catch (\Exception $e) {
+ return back()->with('error', __('print.error_printing'));
+ } finally {
+ Storage::delete($path);
+ }
+
+ $cost = $useFreePages ?
+ PrintAccount::getFreePagesNeeeded($pageNumber, $copyNumber, $twoSided) :
+ PrintAccount::getBalanceNeeded($pageNumber, $copyNumber, $twoSided);
+
+ user()->printJobs()->create([
+ 'state' => PrintJobStatus::QUEUED,
+ 'job_id' => $jobId,
+ 'cost' => $cost,
+ 'used_free_pages' => $useFreePages,
+ 'filename' => $file->getClientOriginalName(),
+ ]);
+
+ // Update the print account history
+ $printAccount->last_modified_by = user()->id;
+
+ if ($useFreePages) {
+ $freePagesToSubtract = $cost;
+ $freePages = $printAccount->available_free_pages->where('amount', '>', 0);
+
+ /** @var FreePages */
+ foreach ($freePages as $pages) {
+ $subtractablePages = min($freePagesToSubtract, $pages->amount);
+ $pages->update([
+ 'last_modified_by' => user()->id,
+ 'amount' => $pages->amount - $subtractablePages,
+ ]);
+
+ $freePagesToSubtract -= $subtractablePages;
+
+ if ($freePagesToSubtract <= 0) { // < should not be necessary, but better safe than sorry
+ break;
+ }
+ }
+ } else {
+ $printAccount->balance -= $cost;
+ }
+
+ $printAccount->save();
+
+ return back()->with('message', __('print.success'));
+ }
+
+ /**
+ * Cancels a `PrintJob`
+ * @param PrintJob $job
+ * @return RedirectResponse
+ */
+ public function update(PrintJob $job) {
+ Log::info('asd');
+ $this->authorize('update', $job);
+ Log::info($job->state->value);
+
+ if ($job->state === PrintJobStatus::QUEUED ) {
+ $result = ($job->printer ?? Printer::firstWhere('name', config('print.printer_name')))->cancelPrintJob($job);
+ switch ($result) {
+ case PrinterCancelResult::Success:
+ $job->update([
+ 'state' => PrintJobStatus::CANCELLED,
+ ]);
+ $printAccount = $job->printAccount;
+ $printAccount->last_modified_by = user()->id;
+
+ if ($job->used_free_pages) {
+ $pages = $printAccount->available_free_pages->first();
+ $pages->update([
+ 'last_modified_by' => user()->id,
+ 'amount' => $pages->amount + $job->cost,
+ ]);
+ } else {
+ $printAccount->balance += $job->cost;
+ }
+
+ $job->save();
+ return back()->with('message', __('general.successful_modification'));
+ case PrinterCancelResult::AlreadyCompleted:
+ $job->update([
+ 'state' => PrintJobStatus::SUCCESS,
+ ]);
+ break;
+ case PrinterCancelResult::AlreadyCancelled:
+ $job->update([
+ 'state' => PrintJobStatus::CANCELLED,
+ ]);
+ break;
+ }
+
+ return back()->with('error', __("print.$result->value"));
+ }
+ }
+
+
+
+ private function paginatorFrom(Builder $printJobs, array $columns) {
+ $paginator = TabulatorPaginator::from($printJobs)->sortable($columns)->filterable($columns)->paginate();
+
+ // Process the data before showing it in a table.
+ $paginator->getCollection()->transform(function (PrintJob $printJob) {
+ $printJob->translatedState = __("print." . strtoupper($printJob->state->value));
+ $printJob->cost = "$printJob->cost HUF";
+ return $printJob;
+ });
+
+ return $paginator;
+ }
+}
diff --git a/app/Http/Controllers/Dormitory/Printing/PrinterController.php b/app/Http/Controllers/Dormitory/Printing/PrinterController.php
new file mode 100644
index 000000000..4d2f88de9
--- /dev/null
+++ b/app/Http/Controllers/Dormitory/Printing/PrinterController.php
@@ -0,0 +1,60 @@
+authorize('handleAny', PrintAccount::class);
+
+ return view('dormitory.print.manage.app', ["users" => User::all()]);
+ }
+
+ return view('dormitory.print.app', [
+ "users" => User::printers(),
+ "user" => user(),
+ "printer" => Printer::firstWhere('name', config('print.printer_name')),
+ ]);
+ }
+
+ /**
+ * Sets the given printer's out of paper sign.
+ */
+ public function update(Request $request, Printer $printer) {
+ $request->validate([
+ "no_paper" => "boolean",
+ ]);
+
+ if ($request->boolean("no_paper")) {
+ if ($printer->paper_out_at === null || now()->diffInMinutes($printer->paper_out_at) > 5) {
+ Mail::to(User::withRole(Role::SYS_ADMIN)->get())->queue(new NoPaper(user()->name));
+ }
+ $printer->update([
+ "paper_out_at" => now(),
+ ]);
+ return redirect()->back()->with('message', __('mail.email_sent'));
+ } else {
+ $this->authorize('handleAny', PrintAccount::class);
+ $printer->update([
+ "paper_out_at" => null,
+ ]);
+ return redirect()->back()->with('message', __('general.successful_modification'));
+ }
+ }
+}
diff --git a/app/Mail/NoPaper.php b/app/Mail/NoPaper.php
index 8a1431b18..ba6fbde45 100644
--- a/app/Mail/NoPaper.php
+++ b/app/Mail/NoPaper.php
@@ -11,7 +11,6 @@ class NoPaper extends Mailable
use Queueable;
use SerializesModels;
- public string $recipient;
public string $reporter;
/**
@@ -19,9 +18,8 @@ class NoPaper extends Mailable
*
* @param string $userName
*/
- public function __construct(string $recipient, string $reporter)
+ public function __construct(string $reporter)
{
- $this->recipient = $recipient;
$this->reporter = $reporter;
}
diff --git a/app/Models/FreePages.php b/app/Models/FreePages.php
index 807a435eb..a9c8df0ad 100644
--- a/app/Models/FreePages.php
+++ b/app/Models/FreePages.php
@@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* Model to keep track of the users' free pages.
@@ -27,23 +28,27 @@ class FreePages extends Model
'comment',
];
- public function user()
+ protected $casts = [
+ 'deadline' => 'date',
+ ];
+
+ public function user(): BelongsTo
{
- return $this->belongsTo('App\Models\User');
+ return $this->belongsTo(User::class);
}
public function printAccount()
{
- return $this->belongsTo('App\Models\PrintAccount', 'user_id', 'user_id');
+ return $this->belongsTo(PrintAccount::class, 'user_id', 'user_id');
}
- public function available()
+ protected function getAvailableAttribute()
{
- return $this->deadline > date('Y-m-d');
+ return now()->isBefore($this->deadline);
}
- public function lastModifiedBy()
+ public function modifier(): BelongsTo
{
- return User::find($this->last_modified_by);
+ return $this->belongsTo(User::class, 'last_modified_by');
}
}
diff --git a/app/Models/PrintAccount.php b/app/Models/PrintAccount.php
index 2333872b0..2730b17dc 100644
--- a/app/Models/PrintAccount.php
+++ b/app/Models/PrintAccount.php
@@ -2,8 +2,12 @@
namespace App\Models;
+use Illuminate\Contracts\Container\BindingResolutionException;
+use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Psr\Container\NotFoundExceptionInterface;
+use Psr\Container\ContainerExceptionInterface;
/**
* Model to keep track of the users' print balance.
@@ -11,11 +15,9 @@
*
* @property mixed $user_id
*/
-class PrintAccount extends Model
-{
+class PrintAccount extends Model {
use HasFactory;
- protected $table = 'print_accounts';
protected $primaryKey = 'user_id';
public $incrementing = false;
public $timestamps = false;
@@ -36,33 +38,78 @@ class PrintAccount extends Model
'balance' => 0,
];
- public function user()
- {
- return $this->belongsTo('App\Models\User');
+ public function user() {
+ return $this->belongsTo(User::class);
}
- public function freePages()
- {
- return $this->hasMany('App\Models\FreePages', 'user_id', 'user_id');
+ public function freePages() {
+ return $this->hasMany(FreePages::class, 'user_id', 'user_id');
}
- public function hasEnoughMoney($balance)
- {
- return $this->balance >= abs($balance);
+ /**
+ * The free pages which are currently available. Sorts the free pages by their deadline.
+ * @return Collection
+ */
+ public function getAvailableFreePagesAttribute() {
+ return $this->freePages()->where('deadline', '>', now())->orderBy('deadline')->get();
+ }
+
+ /**
+ * Returns wether the user has enough free pages to print a document.
+ * A free page is enough to print either a one sided or a two sided page.
+ * @param int $pages
+ * @param int $copies
+ * @param bool $twoSided
+ * @return bool
+ */
+ public function hasEnoughFreePages(int $pages, int $copies, bool $twoSided) {
+ return $this->getAvailableFreePagesAttribute()->sum('amount') >
+ $this::getFreePagesNeeeded($pages, $copies, $twoSided);
+ }
+
+ /**
+ * Returns wether the user has enough balance to print a document.
+ * @param int $pages
+ * @param int $copies
+ * @param bool $twoSided
+ * @return bool
+ */
+ public function hasEnoughBalance(int $pages, int $copies, bool $twoSided) {
+ return $this->balance >= $this::getBalanceNeeded($pages, $twoSided, $copies);
}
- public static function getCost($pages, $is_two_sided, $number_of_copies)
- {
- if (!$is_two_sided) {
- return $pages * self::$COST['one_sided'] * $number_of_copies;
+ /**
+ * Returns an array with the number of one-sided and two-sided pages needed to print the given number of pages.
+ * @param int $pages
+ * @param bool $twoSided
+ * @return array
+ */
+ public static function getPageTypesNeeded(int $pages, bool $twoSided) {
+ $oneSidedPages = 0;
+ $twoSidedPages = 0;
+ if (!$twoSided) {
+ $oneSidedPages = $pages;
+ } else {
+ $oneSidedPages = $pages % 2;
+ $twoSidedPages = floor($pages / 2);
}
- $orphan_ending = $pages % 2;
- $one_copy_cost = floor($pages / 2) * self::$COST['two_sided']
- + $orphan_ending * self::$COST['one_sided'];
+ return [
+ 'one_sided' => $oneSidedPages,
+ 'two_sided' => $twoSidedPages,
+ ];
+ }
+
+ public static function getFreePagesNeeeded(int $pages, $copies, $twoSided) {
+ $pageTypesNeeded = self::getPageTypesNeeded($pages, $twoSided);
- return $one_copy_cost * $number_of_copies;
+ return ($pageTypesNeeded['one_sided'] + $pageTypesNeeded['two_sided']) * $copies;
}
-}
-PrintAccount::$COST = config('print.cost');
+ public static function getBalanceNeeded(int $pages, int $copies, bool $twoSided) {
+ $pageTypesNeeded = self::getPageTypesNeeded($pages, $twoSided);
+
+ return $pageTypesNeeded['one_sided'] * config('print.one_sided_cost') * $copies +
+ $pageTypesNeeded['two_sided'] * config('print.two_sided_cost') * $copies;
+ }
+}
diff --git a/app/Models/PrintAccountHistory.php b/app/Models/PrintAccountHistory.php
index b8744108d..4634060ef 100644
--- a/app/Models/PrintAccountHistory.php
+++ b/app/Models/PrintAccountHistory.php
@@ -5,7 +5,7 @@
use Illuminate\Database\Eloquent\Model;
// Note: the elements of this class should no be changed manually.
-// Triggers are set up in the database (see migration).
+// Obeservers are set up.
class PrintAccountHistory extends Model
{
protected $table = 'print_account_history';
@@ -23,11 +23,11 @@ class PrintAccountHistory extends Model
public function user()
{
- return $this->belongsTo('App\Models\User', 'user_id');
+ return $this->belongsTo(User::class);
}
public function modifier()
{
- return $this->belongsTo('App\Models\User', 'modified_by');
+ return $this->belongsTo(User::class, 'modified_by');
}
}
diff --git a/app/Models/PrintJob.php b/app/Models/PrintJob.php
index d9c4f108e..ce8e0540b 100644
--- a/app/Models/PrintJob.php
+++ b/app/Models/PrintJob.php
@@ -2,56 +2,66 @@
namespace App\Models;
+use App\Enums\PrintJobStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\HasOneThrough;
+use Log;
/**
* @property mixed $user_id
*/
-class PrintJob extends Model
-{
+class PrintJob extends Model {
use HasFactory;
- protected $table = 'print_jobs';
- protected $primaryKey = 'id';
- public $incrementing = true;
public $timestamps = true;
- public const QUEUED = 'QUEUED';
- public const ERROR = 'ERROR';
- public const CANCELLED = 'CANCELLED';
- public const SUCCESS = 'SUCCESS';
- public const STATES = [
- self::QUEUED,
- self::ERROR,
- self::CANCELLED,
- self::SUCCESS,
+ protected $fillable = [
+ 'user_id',
+ 'state',
+ 'job_id',
+ 'cost',
+ 'printer_id',
+ 'used_free_pages'
];
- protected $fillable = [
- 'filename', 'filepath', 'user_id', 'state', 'job_id', 'cost',
+ protected $casts = [
+ 'state' => PrintJobStatus::class,
+ 'used_free_pages' => 'boolean',
];
- public function user()
- {
- return $this->belongsTo('App\Models\User');
+ public function user() {
+ return $this->belongsTo(User::class);
}
- public static function translateStates(): \Closure
- {
- return function ($data) {
- $data->state = __('print.'.strtoupper($data->state));
-
- return $data;
- };
+ public function printer() {
+ return $this->belongsTo(Printer::class);
+ }
+
+ /**
+ * `PrintAccount` which is related to this `PrintJob` through the `User`.
+ * The `PrintJob` and the `PrintAccount` both belong to the `User`, in this sense this relationship is articifial.
+ * Trying to fix the decision made for the database a few years ago.
+ * @return HasOneThrough
+ */
+ public function printAccount() {
+ return $this->hasOneThrough(
+ PrintAccount::class,
+ User::class,
+ 'id', // Foreign key on users
+ 'user_id', // Foreign key on print_accounts
+ 'user_id', // Local key on print_jobs
+ 'id', // Local key on users
+ );
}
- public static function addCurrencyTag(): \Closure
- {
- return function ($data) {
- $data->cost = "{$data->cost} HUF";
-
- return $data;
- };
+ /**
+ * Gets the printjob-status with every printer, updates the status of the completed printjobs.
+ */
+ public static function checkAndUpdateStatuses() {
+ foreach(PrintJob::query()->where('state', PrintJobStatus::QUEUED)->whereNotNull('printer_id')->pluck('printer_id')->unique() as $printer_id) {
+ $printJobs = Printer::find($printer_id)->getCompletedPrintJobs();
+ PrintJob::whereIn('job_id', $printJobs)->update(['state' => PrintJobStatus::SUCCESS]);
+ }
}
}
diff --git a/app/Models/Printer.php b/app/Models/Printer.php
new file mode 100644
index 000000000..6461b43f7
--- /dev/null
+++ b/app/Models/Printer.php
@@ -0,0 +1,129 @@
+ 'datetime',
+ ];
+
+ public function printJobs() {
+ return $this->hasMany(PrintJob::class);
+ }
+
+ /**
+ * Attemts to cancel the given `PrintJob`. Returns wether it was successful.
+ * @param PrintJob $printJob
+ * @return PrinterCancelResult
+ */
+ public function cancelPrintJob(PrintJob $printJob) {
+ $command = "cancel $printJob->job_id -h $this->ip:$this->port";
+ if (config('app.debug')) {
+ // cancel(1) exits with status code 0 if it succeeds
+ $result = ['output' => '', 'exit_code' => 0];
+ } else {
+ $output = exec($command, $result, $exit_code);
+ $result = ['output' => $output, 'exit_code' => $exit_code];
+ }
+ Log::info([$command, $result]);
+ if ($result['exit_code'] == 0) {
+ return PrinterCancelResult::Success;
+ }
+ if (strpos($result['output'], "already canceled") !== false) {
+ return PrinterCancelResult::AlreadyCancelled;
+ }
+ if (strpos($result['output'], "already completed") !== false) {
+ return PrinterCancelResult::AlreadyCompleted;
+ }
+ return PrinterCancelResult::CannotCancel;
+ }
+
+ /**
+ * Asks the printer to print a document with the given configuration.
+ * @param bool $twoSided
+ * @param int $copies
+ * @param string $path
+ * @return int The `jobId` belonging to the printjob
+ * @throws PrinterException If the printing fails
+ */
+ public function print(bool $twoSided, int $copies, string $path) { // TODO: debug mode
+ $jobId = null;
+ try {
+ $result = exec(
+ "lp -d $this->name"
+ . "-h $this->ip:$this->port "
+ . ($twoSided ? "-o sides=two-sided-long-edge " : " ")
+ . "-n $copies $path 2>&1"
+ );
+ if (!preg_match("/^request id is ([^\s]*) \\([0-9]* file\\(s\\)\\)$/", $result, $matches)) {
+ Log::error("Printing error at line: " . __FILE__ . ":" . __LINE__ . " (in function " . __FUNCTION__ . "). result:"
+ . print_r($result, true));
+ throw new PrinterException($result);
+ }
+ $jobId = intval($matches[1]);
+ } catch (\Exception $e) {
+ Log::error("Printing error at line: " . __FILE__ . ":" . __LINE__ . " (in function " . __FUNCTION__ . "). " . $e->getMessage());
+ throw new PrinterException($e->getMessage(), $e->getCode(), $e->getPrevious());
+ }
+
+ return $jobId;
+ }
+
+ /**
+ * Returns the number of pages in the PDF document at the given path.
+ * @param string $path
+ * @return int
+ */
+ public static function getDocumentPageNumber(string $path): int {
+
+ $command = "pdfinfo " . $path . " | grep '^Pages' | awk '{print $2}' 2>&1";
+ if (config('app.debug')) {
+ $result = rand(1, 10);
+ } else {
+ $result = exec($command);
+ }
+ Log::info([$command, $result]);
+ return $result;
+ }
+
+ public function getCompletedPrintJobs() {
+ try {
+ $command = "lpstat -W completed -o $this->name -h $this->ip:$this->port" . " | awk '{print $1}'";
+ if (config('app.debug')) {
+ $result = [];
+ } else {
+ $result = [];
+ exec($command, $result);
+ }
+ Log::info([$command, $result]);
+ } catch (\Exception $e) {
+ Log::error("Printing error at line: " . __FILE__ . ":" . __LINE__ . " (in function " . __FUNCTION__ . "). " . $e->getMessage());
+ }
+ }
+}
+
+enum PrinterCancelResult: string {
+ case AlreadyCancelled = "already-cancelled";
+ case AlreadyCompleted = "already-completed";
+ case CannotCancel = "cannot-cancel";
+ case Success = "successfully-cancelled";
+}
+
+class PrinterException extends \Exception {
+ //
+}
diff --git a/app/Policies/PrintAccountPolicy.php b/app/Policies/PrintAccountPolicy.php
index 4269655dd..db5f369cf 100644
--- a/app/Policies/PrintAccountPolicy.php
+++ b/app/Policies/PrintAccountPolicy.php
@@ -16,9 +16,6 @@ public function before(User $user, $ability)
if ($user->isAdmin()) {
return true;
}
- if (!$user->hasRole(Role::PRINTER)) {
- return false;
- }
}
/**
@@ -37,10 +34,15 @@ public function handleAny(User $user)
public function view(User $user, PrintAccount $printAccount): bool
{
return $user->id === $printAccount->user_id;
- }
+ }
public function modify(User $user): bool
{
return false;
}
+
+ public function transferBalance(User $user, PrintAccount $printAccount): bool
+ {
+ return $user->id == $printAccount->user_id;
+ }
}
diff --git a/app/Utils/Printer.php b/app/Utils/Printer.php
deleted file mode 100644
index 575f40c75..000000000
--- a/app/Utils/Printer.php
+++ /dev/null
@@ -1,160 +0,0 @@
-filename = $filename;
- $this->path = $path;
- $this->is_two_sided = $is_two_sided;
- $this->number_of_copies = $number_of_copies;
- $this->use_free_pages = $use_free_pages;
- $this->print_account = user()->printAccount;
- }
-
- public function print()
- {
- // Getting the number of pages from the document
- $errors = $this->setPages();
- if ($errors != null) {
- return $errors;
- }
-
- // If using free pages, check the amount that can be used
- if ($this->use_free_pages) {
- $this->calculateFreePagePool();
- }
-
- // Calculate cost
- $this->cost = PrintAccount::getCost($this->pages, $this->is_two_sided, $this->number_of_copies);
-
- // Check balance
- if (!$this->print_account->hasEnoughMoney($this->cost)) {
- return back()->withInput()->with('error', __('print.no_balance'));
- }
-
- // Print document
- return $this->printDocument();
- }
-
- /**
- * Only calculating the values here to see how many pages can be covered free of charge.
- */
- private function calculateFreePagePool()
- {
- $this->free_page_pool = [];
- $available_pages = 0;
- $all_pages = user()->freePages
- ->where('deadline', '>', Carbon::now())
- ->sortBy('deadline');
-
- foreach ($all_pages as $key => $free_page) {
- if ($available_pages + $free_page->amount >= $this->pages) {
- $this->free_page_pool[] = [
- 'page' => $free_page,
- 'new_amount' => $free_page->amount - ($this->pages - $available_pages)
- ];
- $available_pages = $this->pages;
- break;
- }
- $this->free_page_pool[] = [
- 'page' => $free_page,
- 'new_amount' => 0
- ];
- $available_pages += $free_page->amount;
- }
-
- $this->pages -= $available_pages;
- }
-
- private function printDocument()
- {
- // Print file and return on error
- if (!$this->printFile()) {
- return back()->with('error', __('print.error_printing'));
- }
-
- // Update print account history
- $this->print_account->update(['last_modified_by' => user()->id]);
- foreach ($this->free_page_pool as $fp) {
- $fp['page']->update([
- 'amount' => $fp['new_amount'],
- 'last_modified_by' => user()->id
- ]);
- }
-
- // Update print account
- $this->print_account->decrement('balance', $this->cost);
-
- return back()->with('message', __('print.success'));
- }
-
- private function printFile()
- {
- $printer_name = config('print.printer_name');
- $state = PrintJob::QUEUED;
- try {
- $command = "lp -d " . $printer_name
- . ($this->is_two_sided ? " -o sides=two-sided-long-edge " : " ")
- . "-n " . $this->number_of_copies . " "
- . $this->path . " 2>&1";
- $result = Commands::print($command);
- if (!preg_match("/^request id is ([^\s]*) \\([0-9]* file\\(s\\)\\)$/", $result, $job)) {
- Log::error("Printing error at line: " . __FILE__ . ":" . __LINE__ . " (in function " . __FUNCTION__ . "). result:"
- . print_r($result, true));
- $state = PrintJob::ERROR;
- }
- $job_id = $job[1];
- } catch (\Exception $e) {
- Log::error("Printing error at line: " . __FILE__ . ":" . __LINE__ . " (in function " . __FUNCTION__ . "). " . $e->getMessage());
- $state = PrintJob::ERROR;
- $job_id = "";
- $this->path = "";
- }
-
- PrintJob::create([
- 'filename' => $this->filename,
- 'filepath' => $this->path,
- 'user_id' => user()->id,
- 'state' => $state,
- 'job_id' => $job_id,
- 'cost' => $this->cost,
- ]);
- return $state == PrintJob::QUEUED;
- }
-
- private function setPages()
- {
- try {
- $this->pages = Commands::getPages($this->path);
- } catch (\Exception $e) {
- Log::error("File retrieval exception at line: " . __FILE__ . ":" . __LINE__ . " (in function " . __FUNCTION__ . "). " . $e->getMessage());
- $this->pages = "";
- }
-
- if ($this->pages == "" || !is_numeric($this->pages) || $this->pages <= 0) {
- Log::error("Cannot get number of pages for uploaded file!" . print_r($this->pages, true));
- return back()->withInput()->with('error', __('print.invalid_pdf'));
- }
- return null;
- }
-}
diff --git a/config/print.php b/config/print.php
index 3e5c9f1c6..8fa5667d6 100644
--- a/config/print.php
+++ b/config/print.php
@@ -2,10 +2,8 @@
return [
- 'cost' => [
- 'one_sided' => env('PRINT_COST_ONESIDED'),
- 'two_sided' => env('PRINT_COST_TWOSIDED'),
- ],
+ 'one_sided_cost' => env('PRINT_COST_ONESIDED'),
+ 'two_sided_cost' => env('PRINT_COST_TWOSIDED'),
// Maximum accepted PDF size in byte.
'pdf_size_limit' => env('PRINT_MAX_FILE_SIZE', 5000000),
diff --git a/database/factories/PrintJobFactory.php b/database/factories/PrintJobFactory.php
index 801b7ee4c..5eae04289 100644
--- a/database/factories/PrintJobFactory.php
+++ b/database/factories/PrintJobFactory.php
@@ -2,6 +2,7 @@
namespace Database\Factories;
+use App\Enums\PrintJobStatus;
use App\Models\PrintJob;
use Illuminate\Database\Eloquent\Factories\Factory;
@@ -13,8 +14,7 @@ public function definition()
{
return [
'filename' => $this->faker->text,
- 'filepath' => $this->faker->text,
- 'state' => $this->faker->randomElement(PrintJob::STATES),
+ 'state' => $this->faker->randomElement(PrintJobStatus::cases()),
'job_id' => $this->faker->randomNumber,
'cost' => $this->faker->numberBetween(8, 1000),
];
diff --git a/database/migrations/2019_10_06_224327_create_print_jobs_table.php b/database/migrations/2019_10_06_224327_create_print_jobs_table.php
index 0f3817600..f20bacf52 100644
--- a/database/migrations/2019_10_06_224327_create_print_jobs_table.php
+++ b/database/migrations/2019_10_06_224327_create_print_jobs_table.php
@@ -1,5 +1,6 @@
text('filename');
$table->text('filepath');
$table->unsignedBigInteger('user_id')->nullable();
- $table->set('state', \App\Models\PrintJob::STATES);
+ $table->set('state', array_map(fn ($state) => $state->value, PrintJobStatus::cases()));
$table->unsignedBigInteger('job_id');
$table->unsignedInteger('cost');
$table->timestamps();
diff --git a/database/migrations/2023_12_25_182310_create_printers_table.php b/database/migrations/2023_12_25_182310_create_printers_table.php
new file mode 100644
index 000000000..10c513057
--- /dev/null
+++ b/database/migrations/2023_12_25_182310_create_printers_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->string('name')->unique();
+ $table->string('ip')->nullable();
+ $table->string('port')->nullable();
+ $table->timestamp('paper_out_at')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('printers');
+ }
+};
diff --git a/database/migrations/2023_12_25_184733_update_print_jobs_table.php b/database/migrations/2023_12_25_184733_update_print_jobs_table.php
new file mode 100644
index 000000000..dc882b62c
--- /dev/null
+++ b/database/migrations/2023_12_25_184733_update_print_jobs_table.php
@@ -0,0 +1,38 @@
+foreignIdFor(Printer::class)->nullable()->after('user_id')->constrained()->nullOnDelete();
+ $table->boolean('used_free_pages')->default(false)->after('cost');
+ $table->dropColumn('filepath');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('print_jobs', function (Blueprint $table) {
+ $table->dropForeign(['printer_id']);
+ $table->dropColumn('printer_id');
+ $table->dropColumn('used_free_pages');
+ $table->string('filepath')->after('user_id');
+ });
+ }
+};
diff --git a/resources/views/dormitory/print/app.blade.php b/resources/views/dormitory/print/app.blade.php
index f3c818d0b..e4c843f57 100644
--- a/resources/views/dormitory/print/app.blade.php
+++ b/resources/views/dormitory/print/app.blade.php
@@ -9,10 +9,10 @@
@include("dormitory.print.print")
- @include("dormitory.print.history", ['route' => route('print.print_jobs.list')])
+ @include("dormitory.print.history", ['route' => route('print-job.index'), 'admin' => false])
- @include("dormitory.print.free", ['route' => route('print.free_pages.list')])
+ @include("dormitory.print.free", ['route' => route('free-pages.index'), 'admin' => false])
@include("dormitory.print.send")
diff --git a/resources/views/dormitory/print/free.blade.php b/resources/views/dormitory/print/free.blade.php
index 15aced843..ecfc6492e 100644
--- a/resources/views/dormitory/print/free.blade.php
+++ b/resources/views/dormitory/print/free.blade.php
@@ -27,6 +27,7 @@
ajaxURL: "{{ $route }}", //set url for ajax request
placeholder: "@lang('print.no_free_pages')",
columns: [
+ @if($admin)
@can('viewAny', App\Models\FreePages::class)
{
title: "@lang('internet.created_at')",
@@ -41,6 +42,7 @@
sorter: "string",
headerFilter: 'input'
},
+ @endif
@endcan
{
title: "@lang('print.free')",
@@ -56,7 +58,7 @@
},
{
title: "@lang('print.last_modified_by')",
- field: "modifier",
+ field: "modifier.name",
sorter: "string",
@can('viewAny', App\Models\FreePages::class) headerFilter: 'input' @endif
},
diff --git a/resources/views/dormitory/print/history.blade.php b/resources/views/dormitory/print/history.blade.php
index 8645ad6a5..661b16a61 100644
--- a/resources/views/dormitory/print/history.blade.php
+++ b/resources/views/dormitory/print/history.blade.php
@@ -7,13 +7,13 @@
$(document).ready(function() {
var deleteButton = function(cell, formatterParams, onRendered) {
var data = cell.getRow().getData();
- if(data.state == '@lang('print.'.App\Models\PrintJob::QUEUED)'){
+ if(data.state == "QUEUED"){
return $(
""
).click(function() {
$.ajax({
- type: "POST",
- url: "{{ route('print.print_jobs.cancel', [':id']) }}".replace(':id', data.id),
+ type: "PUT",
+ url: "{{ route('print-job.update', [':job']) }}".replace(':job', data.id),
success: function() {
cell.getTable().setPage(cell.getTable().getPage());
},
@@ -49,22 +49,24 @@
placeholder: "@lang('internet.nothing_to_show')",
headerSort: false,
columnMinWidth:200,
- columns: [{
+ columns: [
+ {
title: "@lang('internet.created_at')",
field: "created_at",
sorter: "datetime",
formatter:dateFormatter,
@can('viewAny', App\Models\PrintJob::class) headerFilter: 'input' @endcan
},
+ @if ($admin)
@can('viewAny', App\Models\PrintJob::class)
{
- //TODO empty when not in admin view
title: "@lang('print.user')",
field: "user.name",
sorter: "string",
headerFilter: 'input'
},
@endcan
+ @endif
{
title: "@lang('print.document')",
field: "filename",
@@ -79,12 +81,12 @@
},
{
title: "@lang('print.state')",
- field: "state",
+ field: "translatedState",
sorter: "string",
@can('viewAny', App\Models\PrintJob::class)
headerFilterParams: {
- @foreach(\App\Models\PrintJob::STATES as $key => $state)
- "{{ $state }}": "@lang('print.' . $state)",
+ @foreach(\App\Enums\PrintJobStatus::cases() as $state)
+ "{{ $state }}": "@lang('print.' . $state->value)",
@endforeach
}
@endcan
diff --git a/resources/views/dormitory/print/manage/account_history.blade.php b/resources/views/dormitory/print/manage/account_history.blade.php
index 7290b931b..aa60f2406 100644
--- a/resources/views/dormitory/print/manage/account_history.blade.php
+++ b/resources/views/dormitory/print/manage/account_history.blade.php
@@ -6,7 +6,7 @@
paginationSize: 20,
layout: "fitColumns",
pagination: "remote", //enable remote pagination
- ajaxURL: "{{ route('print.account_history') }}", //set url for ajax request
+ ajaxURL: "{{ route('print-account-history.index') }}", //set url for ajax request
ajaxSorting: true,
ajaxFiltering: true,
placeholder: "@lang('general.nothing_to_show')",
diff --git a/resources/views/dormitory/print/manage/app.blade.php b/resources/views/dormitory/print/manage/app.blade.php
index 5ec642741..30b931f4f 100644
--- a/resources/views/dormitory/print/manage/app.blade.php
+++ b/resources/views/dormitory/print/manage/app.blade.php
@@ -30,10 +30,10 @@
- @include("dormitory.print.free", ['route' => route('print.free_pages.list.all')])
+ @include("dormitory.print.free", ['route' => route('free-pages.index', ['filter' => 'all']), 'admin' => true])
- @include("dormitory.print.history", ['route' => route('print.print_jobs.list.all') ])
+ @include("dormitory.print.history", ['route' => route('print-job.index', ['filter' => 'all']), 'admin' => true])
diff --git a/resources/views/dormitory/print/manage/free.blade.php b/resources/views/dormitory/print/manage/free.blade.php
index 29892e9f1..7ceb953e8 100644
--- a/resources/views/dormitory/print/manage/free.blade.php
+++ b/resources/views/dormitory/print/manage/free.blade.php
@@ -1,10 +1,10 @@
@can('create', \App\Models\FreePages::class)
Ingyenes oldalak hozzáadása
- @include("dormitory.print.free", ['route' => route('free-pages.index', ['filter' => 'all']), 'admin' => true])
+ @include("dormitory.print.free", ['route' => route('free-pages.index.admin'), 'admin' => true])
@include("dormitory.print.history", ['route' => route('print-job.index', ['filter' => 'all']), 'admin' => true])
diff --git a/routes/web.php b/routes/web.php
index a151dd20d..b3c675438 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -121,8 +121,9 @@
Route::post('print-job', [PrintJobController::class, 'store'])->name('print-job.store'); // print.print
Route::put('/print-job/{job}', [PrintJobController::class, 'update'])->name('print-job.update'); // print.print_jobs.cancel
- Route::get('/free-pages/{filter?}', [FreePagesController::class, 'indexFreePages'])->name('free-pages.index'); // print.free_pages.list, print.free_pages.list.all
+ Route::get('/free-pages', [FreePagesController::class, 'index'])->name('free-pages.index'); // print.free_pages.list, print.free_pages.list.all
Route::post('/free-pages', [FreePagesController::class, 'store'])->name('free-pages.store'); // print.free_pages
+ Route::get('/free-pages/admin', [FreePagesController::class, 'adminIndex'])->name('free-pages.index.admin'); // print.free_pages.manage
Route::put('/print-account', [PrintAccountController::class, 'update'])->name('print-account.update'); // print.transfer-balance, print.modify
From 1b1685cd9137cced28df1475b32217ccf7d4e5fc Mon Sep 17 00:00:00 2001
From: "deepsource-autofix[bot]"
<62050782+deepsource-autofix[bot]@users.noreply.github.com>
Date: Thu, 9 May 2024 20:33:00 +0000
Subject: [PATCH 18/55] style: format code with PHP CS Fixer
This commit fixes the style issues introduced in d6fbe58 according to the output
from PHP CS Fixer.
Details: https://github.com/EotvosCollegium/mars/pull/394
---
.../Dormitory/Printing/FreePagesController.php | 3 ++-
.../Dormitory/Printing/PrintAccountController.php | 6 ++++--
app/Models/PrintAccount.php | 2 +-
app/Models/PrintJob.php | 5 +++--
app/Utils/PrinterHelper.php | 12 +++++++-----
app/Utils/Process.php | 12 +++++++-----
6 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/app/Http/Controllers/Dormitory/Printing/FreePagesController.php b/app/Http/Controllers/Dormitory/Printing/FreePagesController.php
index e15c1030a..02c1a4541 100644
--- a/app/Http/Controllers/Dormitory/Printing/FreePagesController.php
+++ b/app/Http/Controllers/Dormitory/Printing/FreePagesController.php
@@ -35,7 +35,8 @@ public function index()
* Returns a paginated list of all `FreePages`.
* @return LengthAwarePaginator
*/
- public function adminIndex() {
+ public function adminIndex()
+ {
$this->authorize('viewAny', FreePages::class);
return $this->freePagesPaginator(
diff --git a/app/Http/Controllers/Dormitory/Printing/PrintAccountController.php b/app/Http/Controllers/Dormitory/Printing/PrintAccountController.php
index 7f777041e..846456a88 100644
--- a/app/Http/Controllers/Dormitory/Printing/PrintAccountController.php
+++ b/app/Http/Controllers/Dormitory/Printing/PrintAccountController.php
@@ -57,7 +57,8 @@ public function update(Request $request)
* @param int $amount The amount of money to be transfered.
* @return RedirectResponse
*/
- private function transferBalance(PrintAccount $printAccount, PrintAccount $otherAccount, int $amount) {
+ private function transferBalance(PrintAccount $printAccount, PrintAccount $otherAccount, int $amount)
+ {
DB::beginTransaction();
// Cannot transfer to yourself
if ($otherAccount->user_id === $printAccount->user_id) {
@@ -102,7 +103,8 @@ private function transferBalance(PrintAccount $printAccount, PrintAccount $other
* @param int $amount The amount of money to be added or subtracted.
* @return RedirectResponse
*/
- private function modifyBalance(PrintAccount $printAccount, int $amount) {
+ private function modifyBalance(PrintAccount $printAccount, int $amount)
+ {
DB::beginTransaction();
// Only admins can modify accounts
$this->authorize('modify', $printAccount);
diff --git a/app/Models/PrintAccount.php b/app/Models/PrintAccount.php
index a96cac2a1..c63d59f29 100644
--- a/app/Models/PrintAccount.php
+++ b/app/Models/PrintAccount.php
@@ -100,5 +100,5 @@ public function hasEnoughBalance(int $pages, int $copies, bool $twoSided)
return $this->balance >= PrinterHelper::getBalanceNeeded($pages, $twoSided, $copies);
}
-
+
}
diff --git a/app/Models/PrintJob.php b/app/Models/PrintJob.php
index 8b111064c..a9e7baaaf 100644
--- a/app/Models/PrintJob.php
+++ b/app/Models/PrintJob.php
@@ -109,10 +109,11 @@ public function getTranslatedStateAttribute()
/**
* Attemts to cancel the given `PrintJob`. Returns wether it was successful.
- * @param PrintJob $this
+ * @param PrintJob $this
* @return PrinterCancelResult
*/
- public function cancel() {
+ public function cancel()
+ {
$printer = $this->printer ?? Printer::firstWhere('name', config('print.printer_name'));
$process = new Process(['cancel', $this->job_id, '-h', "$printer->ip:$printer->port"]);
$process->run();
diff --git a/app/Utils/PrinterHelper.php b/app/Utils/PrinterHelper.php
index 96e627dd7..27d761a2d 100644
--- a/app/Utils/PrinterHelper.php
+++ b/app/Utils/PrinterHelper.php
@@ -5,13 +5,15 @@
use App\Enums\PrintJobStatus;
use App\Utils\Process;
-class PrinterHelper {
+class PrinterHelper
+{
/**
* Returns the number of pages in the PDF document at the given path.
- * @param string $path
- * @return int
+ * @param string $path
+ * @return int
*/
- public static function getDocumentPageNumber(string $path): int {
+ public static function getDocumentPageNumber(string $path): int
+ {
$process = new Process(['pdfinfo', $path, '|', 'grep', "'^Pages'", '|', 'awk', "'{print $2}'"]);
$process->run();
$result = intval($process->getOutput(strval(rand(1, 10))));
@@ -82,4 +84,4 @@ public static function updateCompletedPrintJobs()
Printer::find($printer_id)->updateCompletedPrintJobs();
}
}
-}
\ No newline at end of file
+}
diff --git a/app/Utils/Process.php b/app/Utils/Process.php
index 547f4be28..db8e75b4c 100644
--- a/app/Utils/Process.php
+++ b/app/Utils/Process.php
@@ -4,16 +4,18 @@
use Symfony\Component\Process\Process as SymfonyProcess;
-class Process extends SymfonyProcess {
-
- public function run(?callable $callback = null, array $env = []): int {
+class Process extends SymfonyProcess
+{
+ public function run(?callable $callback = null, array $env = []): int
+ {
if (config('app.debug') === false) {
return parent::run($callback, $env);
}
return 0;
}
- public function getOutput(string $debugOutput = ''): string {
+ public function getOutput(string $debugOutput = ''): string
+ {
if (config('app.debug') === false) {
return parent::getOutput();
}
@@ -27,4 +29,4 @@ public function getExitCode(): ?int
}
return 0;
}
-}
\ No newline at end of file
+}
From 9499653d778878249c0632ae4b37e1a41c77ebfb Mon Sep 17 00:00:00 2001
From: Samuel Szajbely
Date: Mon, 23 Dec 2024 23:41:18 +0100
Subject: [PATCH 19/55] Resolving comments
---
.gitignore | 1 +
.vscode/launch.json | 48 -----
.vscode/settings.json | 3 -
app/Enums/PrintJobStatus.php | 1 +
.../PrintAccountHistoryController.php | 2 +-
.../Dormitory/Printing/PrintJobController.php | 178 +++++++-----------
.../Dormitory/Printing/PrinterController.php | 25 +--
app/Models/FreePages.php | 35 +++-
app/Models/PrintAccount.php | 55 +++++-
app/Models/PrintAccountHistory.php | 2 +-
app/Models/PrintJob.php | 38 +++-
app/Models/Printer.php | 29 +++
app/Utils/PrinterHelper.php | 2 +-
...3_12_25_184733_update_print_jobs_table.php | 7 +
resources/lang/hu/print.php | 4 +-
.../views/dormitory/print/history.blade.php | 2 +-
.../dormitory/print/manage/app.blade.php | 2 +-
.../views/dormitory/print/print.blade.php | 3 +-
resources/views/layouts/navbar.blade.php | 2 +-
routes/web.php | 22 ++-
20 files changed, 257 insertions(+), 204 deletions(-)
delete mode 100644 .vscode/launch.json
delete mode 100644 .vscode/settings.json
diff --git a/.gitignore b/.gitignore
index af039d7a1..9b7c676c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@ _ide_helper.php
_ide_helper_models.php
.phpstorm.meta.php
*.DS_Store
+.vscode
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 9dc6b4df6..000000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Listen for Xdebug",
- "type": "php",
- "request": "launch",
- "port": 9003
- },
- {
- "name": "Launch currently open script",
- "type": "php",
- "request": "launch",
- "program": "${file}",
- "cwd": "${fileDirname}",
- "port": 0,
- "runtimeArgs": [
- "-dxdebug.start_with_request=yes"
- ],
- "env": {
- "XDEBUG_MODE": "debug,develop",
- "XDEBUG_CONFIG": "client_port=${port}"
- }
- },
- {
- "name": "Launch Built-in web server",
- "type": "php",
- "request": "launch",
- "runtimeArgs": [
- "-dxdebug.mode=debug",
- "-dxdebug.start_with_request=yes",
- "-S",
- "localhost:0"
- ],
- "program": "",
- "cwd": "${workspaceRoot}",
- "port": 9003,
- "serverReadyAction": {
- "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
- "uriFormat": "http://localhost:%s",
- "action": "openExternally"
- }
- }
- ]
-}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 942c363bc..000000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "julia.environmentPath": "c:\\Users\\sszajbely\\mars"
-}
\ No newline at end of file
diff --git a/app/Enums/PrintJobStatus.php b/app/Enums/PrintJobStatus.php
index e64d38d0b..c485b55fa 100644
--- a/app/Enums/PrintJobStatus.php
+++ b/app/Enums/PrintJobStatus.php
@@ -7,4 +7,5 @@ enum PrintJobStatus: string {
case ERROR = 'ERROR';
case CANCELLED = 'CANCELLED';
case SUCCESS = 'SUCCESS';
+ // case REIMBURSED = 'REIMBURSED';
}
\ No newline at end of file
diff --git a/app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php b/app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php
index 30a6cb34e..66a9ba602 100644
--- a/app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php
+++ b/app/Http/Controllers/Dormitory/Printing/PrintAccountHistoryController.php
@@ -20,7 +20,7 @@ class PrintAccountHistoryController extends Controller
* @throws BindingResolutionException
* @throws InvalidArgumentException
*/
- public function indexPrintAccountHistory()
+ public function index()
{
$this->authorize('viewAny', PrintJob::class);
diff --git a/app/Http/Controllers/Dormitory/Printing/PrintJobController.php b/app/Http/Controllers/Dormitory/Printing/PrintJobController.php
index b229962c5..2387061b7 100644
--- a/app/Http/Controllers/Dormitory/Printing/PrintJobController.php
+++ b/app/Http/Controllers/Dormitory/Printing/PrintJobController.php
@@ -16,42 +16,28 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Validation\Rule;
use InvalidArgumentException;
class PrintJobController extends Controller
{
/**
- * Returns a paginated list of `PrintJob`s.
- * @param null|string $filter Decides wether all `PrintJob`s or just the user's `PrintJob`s should be listed.
+ * Returns a paginated list of the current user's `PrintJob`s.
* @return LengthAwarePaginator
*/
- public function indexPrintJobs(?string $filter = null)
+ public function index()
{
- if ($filter === "all") {
- $this->authorize('viewAny', PrintJob::class);
-
- PrinterHelper::updateCompletedPrintJobs();
-
- return $this->paginatorFrom(
- printJobs: PrintJob::query()
- ->with('user')
- ->orderBy('print_jobs.created_at', 'desc'),
- columns: [
- 'created_at',
- 'filename',
- 'cost',
- 'state',
- 'user.name',
- ]
- );
- }
-
$this->authorize('viewSelf', PrintJob::class);
PrinterHelper::updateCompletedPrintJobs();
+ PrinterHelper::updateCompletedPrintJobs();
+
+ PrinterHelper::updateCompletedPrintJobs();
+
return $this->paginatorFrom(
printJobs: user()
->printJobs()
@@ -65,6 +51,29 @@ public function indexPrintJobs(?string $filter = null)
);
}
+ /**
+ * Returns a paginated list of all `PrintJob`s.
+ * @return LengthAwarePaginator
+ */
+ public function adminIndex()
+ {
+ $this->authorize('viewAny', PrintJob::class);
+
+ PrinterHelper::updateCompletedPrintJobs();
+
+ return $this->paginatorFrom(
+ printJobs: PrintJob::with('user')
+ ->orderBy('print_jobs.created_at', 'desc'),
+ columns: [
+ 'created_at',
+ 'filename',
+ 'cost',
+ 'state',
+ 'user.name',
+ ]
+ );
+ }
+
/**
* Prints a document, then stores the corresponding `PrintJob`.
* @param Request $request
@@ -78,7 +87,6 @@ public function store(Request $request)
'file' => 'required|file',
'copies' => 'required|integer|min:1',
'two_sided' => 'in:on,off',
- 'printer_id' => 'exists:printers,id',
'use_free_pages' => 'in:on,off',
]);
@@ -88,75 +96,37 @@ public function store(Request $request)
$file = $request->file('file');
/** @var Printer */
- $printer = $request->has('printer_id') ? Printer::find($request->input("printer_id")) : Printer::firstWhere('name', config('print.printer_name'));
+ $printer = Printer::firstWhere('name', config('print.printer_name'));
$path = $file->store('print-documents');
+ $originalName = $file->getClientOriginalName();
$pageNumber = PrinterHelper::getDocumentPageNumber($path);
/** @var PrintAccount */
$printAccount = user()->printAccount;
- if (!(($useFreePages && $printAccount->hasEnoughFreePages($pageNumber, $copyNumber, $twoSided)) ||
- (!$useFreePages && $printAccount->hasEnoughBalance($pageNumber, $copyNumber, $twoSided)))
- ) {
+ if (!$printAccount->hasEnoughBalanceOrFreePages($useFreePages, $pageNumber, $copyNumber, $twoSided)) {
+ DB::rollBack();
return back()->with('error', __('print.no_balance'));
}
- $jobId = null;
+ $cost = $useFreePages ?
+ PrinterHelper::getFreePagesNeeded($pageNumber, $copyNumber, $twoSided) :
+ PrinterHelper::getBalanceNeeded($pageNumber, $copyNumber, $twoSided);
+
+ $printAccount->updateHistory($useFreePages, $cost);
+
try {
- $jobId = $printer->print($twoSided, $copyNumber, $path, user());
+ $printJob = $printer->createPrintJob($useFreePages, $cost, $path, $originalName, $twoSided, $copyNumber);
+ Log::info("User $printAccount->user_id started print job a document for $cost. Job ID: $printJob->job_id. Used free pages: $useFreePages. File: $originalName");
} catch (\Exception $e) {
+ DB::rollBack();
+ Log::error("Error while creating print job: " . $e->getMessage());
return back()->with('error', __('print.error_printing'));
} finally {
Storage::delete($path);
}
- $cost = $useFreePages ?
- PrinterHelper::getFreePagesNeeeded($pageNumber, $copyNumber, $twoSided) :
- PrinterHelper::getBalanceNeeded($pageNumber, $copyNumber, $twoSided);
-
- Log::info("Printjob cost: $cost");
-
- user()->printJobs()->create([
- 'state' => PrintJobStatus::QUEUED,
- 'job_id' => $jobId,
- 'cost' => $cost,
- 'used_free_pages' => $useFreePages,
- 'filename' => $file->getClientOriginalName(),
- ]);
-
- // Update the print account history
- $printAccount->last_modified_by = user()->id;
-
- if ($useFreePages) {
- $freePagesToSubtract = $cost;
- $freePages = $printAccount->available_free_pages->where('amount', '>', 0);
-
- /** @var FreePages */
- foreach ($freePages as $pages) {
- $subtractablePages = min($freePagesToSubtract, $pages->amount);
- $pages->update([
- 'last_modified_by' => user()->id,
- 'amount' => $pages->amount - $subtractablePages,
- ]);
-
- $freePagesToSubtract -= $subtractablePages;
-
- if ($freePagesToSubtract <= 0) { // < should not be necessary, but better safe than sorry
- break;
- }
- }
- // Set value in the session so that free page checkbox stays checked
- session()->put('use_free_pages', true);
- } else {
- $printAccount->balance -= $cost;
-
- // Remove value regarding the free page checkbox from the session
- session()->remove('use_free_pages');
- }
-
- $printAccount->save();
-
DB::commit();
return back()->with('message', __('print.success'));
@@ -167,48 +137,32 @@ public function store(Request $request)
* @param PrintJob $job
* @return RedirectResponse
*/
- public function update(PrintJob $job)
+ public function update(PrintJob $job, Request $request)
{
$this->authorize('update', $job);
- if ($job->state === PrintJobStatus::QUEUED) {
- $result = $job->cancel();
- switch ($result) {
- case PrinterCancelResult::Success:
- $job->update([
- 'state' => PrintJobStatus::CANCELLED,
- ]);
- $printAccount = $job->printAccount;
- $printAccount->last_modified_by = user()->id;
-
- if ($job->used_free_pages) {
- $pages = $printAccount->available_free_pages->first();
- $pages->update([
- 'last_modified_by' => user()->id,
- 'amount' => $pages->amount + $job->cost,
- ]);
- } else {
- $printAccount->balance += $job->cost;
- }
+ $data = $request->validate([
+ 'state' => ['required', Rule::enum(PrintJobStatus::class)->only(PrintJobStatus::CANCELLED)],
+ ]);
- $job->save();
- return back()->with('message', __('general.successful_modification'));
- case PrinterCancelResult::AlreadyCompleted:
- $job->update([
- 'state' => PrintJobStatus::SUCCESS,
- ]);
- break;
- case PrinterCancelResult::AlreadyCancelled:
- $job->update([
- 'state' => PrintJobStatus::CANCELLED,
- ]);
- break;
- }
-
- return back()->with('error', __("print.$result->value"));
+ /** @var PrintJobStatus */
+ $newState = $data['state'];
+
+ switch ($newState->value) {
+ case PrintJobStatus::CANCELLED:
+ if ($job->state === PrintJobStatus::QUEUED) {
+ /** @var PrinterCancelResult */
+ $result = $job->cancel();
+
+ if ($result === PrinterCancelResult::Success) {
+ return back()->with('message', __('general.successful_modification'));
+ }
+ return back()->with('error', __("print.$result->value"));
+ }
+ return back()->with('error', __('print.cannot_cancel'));
+ default:
+ abort(422);
}
-
- return back();
}
/**
diff --git a/app/Http/Controllers/Dormitory/Printing/PrinterController.php b/app/Http/Controllers/Dormitory/Printing/PrinterController.php
index 0b9035417..ea39e9764 100644
--- a/app/Http/Controllers/Dormitory/Printing/PrinterController.php
+++ b/app/Http/Controllers/Dormitory/Printing/PrinterController.php
@@ -15,18 +15,11 @@
class PrinterController extends Controller
{
/**
- * Returns the print page for either the admin or the normal page.
- * @param null|string $page Determines wether we are on the admin or the normal printing page.
+ * Returns the print page.
* @return View
*/
- public function index(?string $page = null)
+ public function index()
{
- if ($page === "admin") {
- $this->authorize('handleAny', PrintAccount::class);
-
- return view('dormitory.print.manage.app', ["users" => User::all()]);
- }
-
return view('dormitory.print.app', [
"users" => User::all(),
"user" => user(),
@@ -34,6 +27,17 @@ public function index(?string $page = null)
]);
}
+ /**
+ * Returns the admin print page.
+ * @return View
+ */
+ public function adminIndex()
+ {
+ $this->authorize('handleAny', PrintAccount::class);
+
+ return view('dormitory.print.manage.app', ["users" => User::all()]);
+ }
+
/**
* Sets the given printer's out of paper sign.
*/
@@ -47,8 +51,7 @@ public function update(Request $request, Printer $printer)
if ($printer->paper_out_at === null || now()->diffInMinutes($printer->paper_out_at) > 5) {
Mail::to(User::withRole(Role::SYS_ADMIN)->get())->queue(new NoPaper(user()->name));
}
- $printer->paper_out_at = now();
- $printer->save();
+ $printer->update(['paper_out_at' => now()]);
return redirect()->back()->with('message', __('mail.email_sent'));
} else {
$this->authorize('handleAny', PrintAccount::class);
diff --git a/app/Models/FreePages.php b/app/Models/FreePages.php
index 7d261219a..6e77d8069 100644
--- a/app/Models/FreePages.php
+++ b/app/Models/FreePages.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -78,9 +79,11 @@ public function printAccount()
* Wether the free pages are still available.
* @return bool
*/
- protected function getAvailableAttribute()
+ protected function available()
{
- return now()->isBefore($this->deadline);
+ return Attribute::make(
+ get: fn() => now()->isBefore($this->deadline)
+ );
}
/**
@@ -91,4 +94,32 @@ public function modifier(): BelongsTo
{
return $this->belongsTo(User::class, 'last_modified_by');
}
+
+
+ /**
+ * Returns the amount of pages that can be subtracted from the free pages.
+ * If the amount is greater than the available pages, only the available pages are subtracted.
+ * @param int $amount
+ * @return int
+ */
+ public function calculateSubtractablePages(int $amount)
+ {
+ return min($amount, $this->amount);
+ }
+
+ /**
+ * Subtracts the given amount of pages from the free pages.
+ * If the amount is greater than the available pages, only the available pages are subtracted.
+ * @param int $amount
+ */
+ public function subtractPages(int $amount)
+ {
+ if ($amount <= 0 || $amount > $this->amount) {
+ throw new \InvalidArgumentException("Amount must be greater than 0 and less than or equal to the available pages.");
+ }
+ $this->update([
+ 'last_modified_by' => user()->id,
+ 'amount' => $this->amount - $amount,
+ ]);
+ }
}
diff --git a/app/Models/PrintAccount.php b/app/Models/PrintAccount.php
index c63d59f29..347311ee6 100644
--- a/app/Models/PrintAccount.php
+++ b/app/Models/PrintAccount.php
@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Contracts\Container\BindingResolutionException;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -69,9 +70,11 @@ public function freePages()
* The free pages which are currently available. Sorts the free pages by their deadline.
* @return Collection
*/
- public function getAvailableFreePagesAttribute()
+ public function availableFreePages()
{
- return $this->freePages()->where('deadline', '>', now())->orderBy('deadline')->get();
+ return Attribute::make(
+ get: fn () => $this->freePages()->where('deadline', '>', now())->orderBy('deadline')->get()
+ );
}
/**
@@ -84,8 +87,8 @@ public function getAvailableFreePagesAttribute()
*/
public function hasEnoughFreePages(int $pages, int $copies, bool $twoSided)
{
- return $this->getAvailableFreePagesAttribute()->sum('amount') >
- PrinterHelper::getFreePagesNeeeded($pages, $copies, $twoSided);
+ return $this->available_free_pages->sum('amount') >
+ PrinterHelper::getFreePagesNeeded($pages, $copies, $twoSided);
}
/**
@@ -100,5 +103,49 @@ public function hasEnoughBalance(int $pages, int $copies, bool $twoSided)
return $this->balance >= PrinterHelper::getBalanceNeeded($pages, $twoSided, $copies);
}
+ /**
+ * Returns wether the user has enough balance or free pages to print a document.
+ * @param bool $useFreePages
+ * @param int $pages
+ * @param int $copies
+ * @param bool $twoSided
+ * @return bool
+ */
+ public function hasEnoughBalanceOrFreePages(bool $useFreePages, int $pages, int $copies, bool $twoSided)
+ {
+ return $useFreePages ? $this->hasEnoughFreePages($pages, $copies, $twoSided) : $this->hasEnoughBalance($pages, $copies, $twoSided);
+ }
+
+ public function updateHistory(bool $useFreePages, int $cost) {
+ // Update the print account history
+ $this->last_modified_by = user()->id;
+
+ if ($useFreePages) {
+ $freePagesToSubtract = $cost;
+ $availableFreePages = $this->available_free_pages->where('amount', '>', 0);
+
+ // Subtract the pages from the free pages pool, as many free pages as necessary
+ /** @var FreePages */
+ foreach ($availableFreePages as $freePages) {
+ $subtractablePages = $freePages->calculateSubtractablePages($freePagesToSubtract);
+ $freePages->subtractPages($subtractablePages);
+ $freePagesToSubtract -= $subtractablePages;
+
+ if ($freePagesToSubtract <= 0) { // < should not be necessary, but better safe than sorry
+ break;
+ }
+ }
+ // Set value in the session so that free page checkbox stays checked
+ session()->put('use_free_pages', true);
+ } else {
+ $this->balance -= $cost;
+
+ // Remove value regarding the free page checkbox from the session
+ session()->remove('use_free_pages');
+ }
+
+ $this->save();
+ }
+
}
diff --git a/app/Models/PrintAccountHistory.php b/app/Models/PrintAccountHistory.php
index ad03e5e63..3471eb89c 100644
--- a/app/Models/PrintAccountHistory.php
+++ b/app/Models/PrintAccountHistory.php
@@ -5,7 +5,7 @@
use Illuminate\Database\Eloquent\Model;
// Note: the elements of this class should no be changed manually.
-// Obeservers are set up.
+// Observers for updating entries are set up.
/**
* App\Models\PrintAccountHistory
*
diff --git a/app/Models/PrintJob.php b/app/Models/PrintJob.php
index a9e7baaaf..7def5c06f 100644
--- a/app/Models/PrintJob.php
+++ b/app/Models/PrintJob.php
@@ -4,6 +4,7 @@
use App\Enums\PrintJobStatus;
use App\Utils\Process;
+use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -94,17 +95,21 @@ public function printAccount()
/**
* Attribute for the translated cost.
*/
- public function getTranslatedCostAttribute()
+ public function translatedCost()
{
- return $this->used_free_pages ? "$this->cost ingyenes oldal" : "$this->cost HUF";
+ return Attribute::make(
+ get: fn() => $this->used_free_pages ? "$this->cost ingyenes oldal" : "$this->cost HUF"
+ );
}
/**
* Attribute for the translated state.
*/
- public function getTranslatedStateAttribute()
+ public function translatedState()
{
- return __("print." . strtoupper($this->state->value));
+ return Attribute::make(
+ get: fn() => __("print." . strtoupper($this->state->value))
+ );
}
/**
@@ -114,18 +119,41 @@ public function getTranslatedStateAttribute()
*/
public function cancel()
{
- $printer = $this->printer ?? Printer::firstWhere('name', config('print.printer_name'));
+ $printer = $this->printer;
$process = new Process(['cancel', $this->job_id, '-h', "$printer->ip:$printer->port"]);
$process->run();
$result = ['output' => $process->getOutput(), 'exit_code' => $process->getExitCode()];
if ($result['exit_code'] == 0) {
+ $this->update([
+ 'state' => PrintJobStatus::CANCELLED,
+ ]);
+ $printAccount = $this->printAccount;
+ $printAccount->last_modified_by = user()->id;
+
+ if ($this->used_free_pages) {
+ $pages = $printAccount->available_free_pages->first();
+ $pages->update([
+ 'last_modified_by' => user()->id,
+ 'amount' => $pages->amount + $this->cost,
+ ]);
+ } else {
+ $printAccount->balance += $this->cost;
+ }
+
+ $this->save();
return PrinterCancelResult::Success;
}
if (strpos($result['output'], "already canceled") !== false) {
+ $this->update([
+ 'state' => PrintJobStatus::CANCELLED,
+ ]);
return PrinterCancelResult::AlreadyCancelled;
}
if (strpos($result['output'], "already completed") !== false) {
+ $this->update([
+ 'state' => PrintJobStatus::SUCCESS,
+ ]);
return PrinterCancelResult::AlreadyCompleted;
}
return PrinterCancelResult::CannotCancel;
diff --git a/app/Models/Printer.php b/app/Models/Printer.php
index 4de688760..309f2aac9 100644
--- a/app/Models/Printer.php
+++ b/app/Models/Printer.php
@@ -10,6 +10,8 @@
use Psr\Container\NotFoundExceptionInterface;
use Psr\Container\ContainerExceptionInterface;
use App\Utils\Process;
+use Illuminate\Auth\AuthenticationException;
+use Illuminate\Database\Eloquent\MassAssignmentException;
class Printer extends Model {
use HasFactory;
@@ -35,6 +37,33 @@ public function printJobs() {
return $this->hasMany(PrintJob::class);
}
+
+ /**
+ * Starts a new print job for the current user and saves it.
+ * @param bool $useFreePages
+ * @param int $cost
+ * @param string $filePath
+ * @param string $originalName
+ * @param bool $twoSided
+ * @param int $copyNumber
+ * @return Model
+ * @throws AuthenticationException
+ * @throws PrinterException
+ * @throws MassAssignmentException
+ */
+ public function createPrintJob(bool $useFreePages, int $cost, string $filePath, string $originalName, bool $twoSided, int $copyNumber) {
+ $jobId = $this->print($twoSided, $copyNumber, $filePath, user());
+
+ return user()->printJobs()->create([
+ 'printer_id' => $this->id,
+ 'state' => PrintJobStatus::QUEUED,
+ 'job_id' => $jobId,
+ 'cost' => $cost,
+ 'used_free_pages' => $useFreePages,
+ 'filename' => $originalName,
+ ]);
+ }
+
/**
* Asks the printer to print a document with the given configuration.
* @param bool $twoSided
diff --git a/app/Utils/PrinterHelper.php b/app/Utils/PrinterHelper.php
index 27d761a2d..d79d9b64c 100644
--- a/app/Utils/PrinterHelper.php
+++ b/app/Utils/PrinterHelper.php
@@ -50,7 +50,7 @@ public static function getPageTypesNeeded(int $pages, bool $twoSided)
* @param mixed $twoSided
* @return int|float
*/
- public static function getFreePagesNeeeded(int $pages, $copies, $twoSided)
+ public static function getFreePagesNeeded(int $pages, $copies, $twoSided)
{
$pageTypesNeeded = self::getPageTypesNeeded($pages, $twoSided);
diff --git a/database/migrations/2023_12_25_184733_update_print_jobs_table.php b/database/migrations/2023_12_25_184733_update_print_jobs_table.php
index f40375192..9c5d410af 100644
--- a/database/migrations/2023_12_25_184733_update_print_jobs_table.php
+++ b/database/migrations/2023_12_25_184733_update_print_jobs_table.php
@@ -3,6 +3,7 @@
use App\Models\Printer;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
@@ -18,6 +19,12 @@ public function up()
$table->boolean('used_free_pages')->default(false)->after('cost');
$table->dropColumn('filepath');
});
+ DB::table('print_jobs')->update([
+ 'printer_id' => DB::table('printers')->first()->id
+ ]);
+ Schema::table('print_jobs', function (Blueprint $table) {
+ $table->foreignIdFor(Printer::class)->nullable(false)->change();
+ });
}
/**
diff --git a/resources/lang/hu/print.php b/resources/lang/hu/print.php
index 1fafbc5e3..572289b4e 100644
--- a/resources/lang/hu/print.php
+++ b/resources/lang/hu/print.php
@@ -6,7 +6,8 @@
'QUEUED' => 'Sorban áll',
'SUCCESS' => 'Kész',
'add' => 'Hozzáadás',
- 'already_cancelled' => 'A nyomtatás már vissza lett vonva.',
+ 'already_cancelled' => 'A nyomtatás már visszavonásra került.',
+ 'already_completed' => 'A nyomtatás már befejeződött.',
'amount' => 'Összeg',
'available_free_pages' => 'Ezen felül ingyenesen nyomtatható :number_of_free_pages oldal',
'available_money' => 'Rendelkezésre álló összeg',
@@ -14,6 +15,7 @@
'balance_change' => 'Egyenlegváltozás',
'cancel' => 'Mégse',
'cancel_job' => 'Megszakítás',
+ 'cannot_cancel' => 'Nem lehetett megszakítani a nyomtatást',
'changed_balance' => 'Megváltozott nyomtatási egyenleg',
'changed_balance_descr' => ':modifier módosította a nyomtatási egyenleged ennyivel: :amount. Az új egyenleged :balance Ft.',
'confirm_cancel' => 'Biztos, hogy meg szeretnéd szakítani a dokumentum nyomtatását?',
diff --git a/resources/views/dormitory/print/history.blade.php b/resources/views/dormitory/print/history.blade.php
index 1ae3a31c2..862258659 100644
--- a/resources/views/dormitory/print/history.blade.php
+++ b/resources/views/dormitory/print/history.blade.php
@@ -13,7 +13,7 @@
).click(function() {
$.ajax({
type: "PUT",
- url: "{{ route('print-job.update', [':job']) }}".replace(':job', data.id),
+ url: "{{ route('print-job.update', [':job', 'state' => 'CANCELLED']) }}".replace(':job', data.id),
success: function() {
cell.getTable().setPage(cell.getTable().getPage());
},
diff --git a/resources/views/dormitory/print/manage/app.blade.php b/resources/views/dormitory/print/manage/app.blade.php
index 346d6c3b5..e6b668e70 100644
--- a/resources/views/dormitory/print/manage/app.blade.php
+++ b/resources/views/dormitory/print/manage/app.blade.php
@@ -33,7 +33,7 @@
@include("dormitory.print.free", ['route' => route('free-pages.index.admin'), 'admin' => true])
- @include("dormitory.print.history", ['route' => route('print-job.index', ['filter' => 'all']), 'admin' => true])
+ @include("dormitory.print.history", ['route' => route('print-job.index.admin'), 'admin' => true])
diff --git a/resources/views/dormitory/print/print.blade.php b/resources/views/dormitory/print/print.blade.php
index 6d8c467b6..846fe1fdb 100644
--- a/resources/views/dormitory/print/print.blade.php
+++ b/resources/views/dormitory/print/print.blade.php
@@ -20,8 +20,7 @@
@lang('print.upload_money')
-