Skip to content

Bugfix: Validation consumption#225

Open
giovanny07 wants to merge 11 commits into
pluginsGLPI:mainfrom
giovanny07:bugfix/validation-consumption
Open

Bugfix: Validation consumption#225
giovanny07 wants to merge 11 commits into
pluginsGLPI:mainfrom
giovanny07:bugfix/validation-consumption

Conversation

@giovanny07

Copy link
Copy Markdown

Checklist before requesting a review

  • I have performed a self-review of my code.
  • I have added tests (when available) that prove my fix is effective or that my feature works.
  • I have updated the CHANGELOG with a short functional description of the fix or new feature.

Description

This PR centralizes credit consumption validation so both the manual ticket form and the hook-driven followup/task/solution flow
use the same server-side rules. It also prevents selecting vouchers outside their valid begin/end date window and fixes the
ticket-tab JavaScript that broke the quantity field when selecting a voucher.

Validation performed:

  • find inc front ajax -name '*.php' -print0 | xargs -0 -n1 php -l
  • git diff --check
  • Manual UI validation of voucher selection and quantity behavior in the ticket form

@stonebuzz stonebuzz requested review from Rom1-B and stonebuzz April 27, 2026 07:41
Comment thread front/ticket.form.php Outdated
ERROR,
);
Html::back();
if (!Session::haveRight('ticket', UPDATE) && !Session::haveRight(Entity::$rightname, UPDATE)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add a validation check when modifying an entity?
A technician updating a ticket (including data coming from the credit plugin) does not necessarily have the permissions required to update the associated entity.

Comment thread inc/ticket.class.php Outdated
Comment on lines +268 to +308
$ticket_id = filter_var(
$input['tickets_id'] ?? null,
FILTER_VALIDATE_INT,
['options' => ['min_range' => 1]],
);
if ($ticket_id === false) {
Session::addMessageAfterRedirect(
__s('Ticket is mandatory.', 'credit'),
true,
ERROR,
);
return false;
}

$credit_id = filter_var(
$input['plugin_credit_entities_id'] ?? null,
FILTER_VALIDATE_INT,
['options' => ['min_range' => 1]],
);
if ($credit_id === false) {
Session::addMessageAfterRedirect(
__s('Credit voucher entity must be selected.', 'credit'),
true,
ERROR,
);
return false;
}

$consumed = filter_var(
$input['consumed'] ?? null,
FILTER_VALIDATE_INT,
['options' => ['min_range' => 1]],
);
if ($consumed === false) {
Session::addMessageAfterRedirect(
__s('Credit voucher quantity must be greater than 0.', 'credit'),
true,
ERROR,
);
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant code with prepareInputForUpdate
This logic duplicates functionality already present in prepareInputForUpdate.
It could be refactored into a dedicated checkInput function, or merged into getValidatedConsumptionInput, which should be renamed accordingly to better reflect its broader responsibility.

Comment thread CHANGELOG.md Outdated

- Improve consumed credits modal readability and open related tickets in a new tab
- Show credit vouchers list in entity tab for read-only users while keeping add/config forms restricted to editable contexts.
- Centralize credit consumption validation, prevent invalid voucher selections outside the validity window, and fix the ticket tab quantity field behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Centralize credit consumption validation, prevent invalid voucher selections outside the validity window, and fix the ticket tab quantity field behavior.
- Improve credit validation and voucher handling.

Comment thread front/ticket.form.php Outdated
Comment thread front/ticket.form.php
Comment on lines -37 to -50
if ($_REQUEST['plugin_credit_entities_id'] == 0) {
Session::addMessageAfterRedirect(
__s('Credit voucher entity must be selected.', 'credit'),
true,
ERROR,
);
Html::back();
} elseif ($_REQUEST['plugin_credit_quantity'] == 0) {
Session::addMessageAfterRedirect(
__s('Credit voucher quantity must be greater than 0.', 'credit'),
true,
ERROR,
);
Html::back();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove these checks?

@stonebuzz

Copy link
Copy Markdown
Contributor

Hi @giovanny07

can you rebase ?

@giovanny07 giovanny07 force-pushed the bugfix/validation-consumption branch from 373344a to 9f28a7f Compare May 11, 2026 16:04
@giovanny07

Copy link
Copy Markdown
Author

Hi @stonebuzz, rebased on top of latest main.

@stonebuzz

Copy link
Copy Markdown
Contributor

please fix CI

@Herafia

Herafia commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Hi @giovanny07 could you resolve the requests and the CI ?

@giovanny07

Copy link
Copy Markdown
Author

Hi team!

Sorry for the delay, I was on vacation. I've fixed it now.

Regards

@stonebuzz

Copy link
Copy Markdown
Contributor

Let me check tomorrow =)

@stonebuzz stonebuzz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test checkbox is unchecked, and given that checkInput centralizes logic that was previously scattered and untested, it would be worth adding at least:

  • a test for consumeVoucher on an already-solved ticket (this is the exact regression this PR introduces)
  • a test for prepareInputForAdd with an out-of-window voucher (begin_date in the future, end_date in the past)

Comment thread inc/ticket.class.php Outdated
&& !in_array($ticket->fields['status'], array_merge(Ticket::getSolvedStatusArray(), Ticket::getClosedStatusArray()));
}

private static function checkInput(array $input, ?self $current_credit = null): array|false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static function checkInput(array $input, ?self $current_credit = null): array|false
private static function checkInput(array $input, ?self $current_credit = null, bool $skip_status_check = false): array|false

canUpdateCreditsForTicket returns false for non-entity-admins when the ticket is already solved or closed. consumeVoucher is registered on the item_add hook (setup.php line 74-76). For ITILSolution, GLPI sets the ticket status to "solved" inside ITILSolution::add(), before any item_add hooks fire.

Comment thread inc/ticket.class.php
Comment on lines +247 to +252
$ticket = new Ticket();
if (
!$ticket->getFromDB($ticket_id)
|| !$ticket->can($ticket_id, READ)
|| !self::canUpdateCreditsForTicket($ticket)
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$ticket = new Ticket();
if (
!$ticket->getFromDB($ticket_id)
|| !$ticket->can($ticket_id, READ)
|| !self::canUpdateCreditsForTicket($ticket)
) {
$ticket = new Ticket();
if (
!$ticket->getFromDB($ticket_id)
|| !$ticket->can($ticket_id, READ)
|| (!$skip_status_check && !self::canUpdateCreditsForTicket($ticket))
) {

Comment thread front/ticket.form.php Outdated
use Glpi\Exception\Http\BadRequestHttpException;

Session::haveRight("ticket", UPDATE);
Session::checkRight(Ticket::class, UPDATE);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Session::checkRight(Ticket::class, UPDATE);
Session::checkRight(\Ticket::class, UPDATE);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix here

Comment thread inc/ticket.class.php Outdated
}

$input = $validated_input + $input;
$input['users_id'] = (int) Session::getLoginUserID();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$input['users_id'] = (int) Session::getLoginUserID();

ALready set in front/ticket.form.php line 42

@giovanny07

Copy link
Copy Markdown
Author

Hi @stonebuzz, thanks for the detailed review.

Addressed in the latest pushed commit:

  • Added a skip_status_check parameter to PluginCreditTicket::checkInput() and use it only from consumeVoucher(). This keeps
    the normal status/editability checks for manual add/update paths, while allowing the ITILSolution hook path to consume credits after GLPI has already moved the ticket to solved.
  • Removed the redundant users_id assignment from prepareInputForAdd(); it remains set by the front controller / caller input.
  • Added PHPUnit coverage for consumeVoucher() on an already-solved ticket through the solution-hook path.
  • Added PHPUnit coverage for prepareInputForAdd() rejecting vouchers outside their validity window: one with begin_date in
    the future and one with end_date in the past.

On the Session::checkRight() suggestion: I kept Ticket::class without the leading slash because Rector failed the CI when the
code used \Ticket::class and explicitly rewrote it to Ticket::class.

Local validation done:

  • find inc front ajax tests -name '*.php' -print0 | xargs -0 -n1 php -l
  • git diff --check

I could not run PHPUnit locally because this checkout does not include the GLPI core vendor/; the plugin CI should now run the
tests because phpunit.xml is present.

Comment thread front/ticket.form.php Outdated
use Glpi\Exception\Http\BadRequestHttpException;

Session::haveRight("ticket", UPDATE);
Session::checkRight(Ticket::class, UPDATE);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix here

@stonebuzz stonebuzz requested review from Rom1-B and stonebuzz June 23, 2026 13:46

@stonebuzz stonebuzz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread front/ticket.form.php Outdated
Comment thread tests/TicketTest.php
'users_id' => Session::getLoginUserID(),
]));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No test covers the overconsumption paths in checkInput (lines 287-298 of inc/ticket.class.php): when overconsumption_allowed = 1, a WARNING should be emitted but the add should succeed; when overconsumption_allowed = 0, the add should fail. Can you add a test for each branch?


The deduction logic in checkInput (lines 278-282): when updating an existing credit entry for the same voucher, $quantity_consumed is reduced by the current record's consumed value so the user is not double-charged. This path has no test. Can you add a test for prepareInputForUpdate where the updated quantity stays within the adjusted remaining budget?

Thanks! you're rigth

Co-authored-by: Romain B. <8530352+Rom1-B@users.noreply.github.com>
@giovanny07

Copy link
Copy Markdown
Author

Those checks weren't dropped, they were centralized into PluginCreditTicket::checkInput() (inc/ticket.class.php), which both prepareInputForAdd() and prepareInputForUpdate() call. The same messages are still emitted there:

  • plugin_credit_entities_id not selected → Credit voucher entity must be selected. (ERROR)
  • consumed <= 0 → Credit voucher quantity must be greater than 0. (ERROR)

The goal was to have one source of truth for validation shared by the manual form and the hook-driven path (followup/task/solution), instead of duplicating the entity/quantity checks in the front controller.

Covers the two branches flagged by Rom1-B: WARNING+success when
overconsumption_allowed=1, ERROR+failure when disabled, and the
prepareInputForUpdate path where an existing record's consumption
is deducted before re-checking the remaining budget.
@giovanny07 giovanny07 force-pushed the bugfix/validation-consumption branch from a21d0c3 to 941f09d Compare June 30, 2026 19:08
@stonebuzz

Copy link
Copy Markdown
Contributor

@giovanny07

can you rebase to solve conflicts ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants