Skip to content

Add safety guards in Generate item flow for invalid references and non-existent classes#578

Merged
stonebuzz merged 3 commits into
pluginsGLPI:mainfrom
bygadd:fix/glpi11-safety-guards-reference-and-link
May 21, 2026
Merged

Add safety guards in Generate item flow for invalid references and non-existent classes#578
stonebuzz merged 3 commits into
pluginsGLPI:mainfrom
bygadd:fix/glpi11-safety-guards-reference-and-link

Conversation

@bygadd
Copy link
Copy Markdown
Contributor

@bygadd bygadd commented May 14, 2026

Summary

The Generate item massive action throws fatal errors on GLPI 11 / PHP 8.3 under two scenarios that have become common after the GLPI 10 → 11 migration:

  1. Invalid item references in PluginOrderReference::getReferenceItemID()getItemForItemtype() returns false in HTTP context for some dynamically-generated classes (notably Glpi\CustomAsset\* in GLPI 11), causing Call to a member function getFromDB() on false. Existing references migrated from older itemtypes may also have templates_id = 0, which makes getFromDB() fail silently and downstream getField() calls operate on a half-initialised object.

  2. Non-existent classes in PluginOrderLink::generateItem()Toolbox::hasTrait() internally calls class_uses(). On PHP 8.3 with the Safe library wrapper, class_uses() on a non-existent class throws Safe\Exceptions\SplException instead of emitting a warning. This is triggered by stale itemtype values in glpi_plugin_order_orders_items that reference classes from uninstalled plugins (e.g. PluginGenericobjectOther after migrating to native custom assets via the GenericObject 3.0.0 EOL migration tool).

Implementation

Both call sites are now guarded:

  • inc/reference.class.php — bail out early with return 0 when the item cannot be instantiated or templates_id is empty.
  • inc/link.class.php — prefix both Toolbox::hasTrait() calls with class_exists() so non-existent classes are skipped cleanly, both in the iteration loop (where $row['assignableitem'] is set) and in the final TemplateRenderer::display() call.

The changes are defensive and minimal — they preserve all existing behaviour for valid native itemtypes, and only short-circuit when the underlying data cannot be resolved to a working class.

Testing

Tested on GLPI 11.0.7, PHP 8.3, Order plugin 2.12.6:

Scenario Result
Order with valid native itemtypes (Computer, Monitor, etc.) — unchanged behaviour
Order containing custom asset references (Glpi\CustomAsset\otherAsset) — form now renders fully and submits
Order containing stale itemtypes from uninstalled plugins (PluginGenericobjectOther) — items skipped without fatal error, remaining items render
Generate item massive action end-to-end (form → submit → items created)

Tested on a production instance with ~21,000 order line items and ~1,300 references, including a mix of native itemtypes, custom asset references, and stale references from a deinstalled plugin.

Checklist

  • 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.
  • This change requires a documentation update.

…n-existent classes

The Generate item massive action threw fatal errors on GLPI 11 / PHP 8.3
under two scenarios:

1. PluginOrderReference::getReferenceItemID() (reference.class.php)
   - getItemForItemtype() returns false in HTTP context for some
     dynamically-generated classes (e.g. Glpi\CustomAsset\* in GLPI 11),
     causing "Call to a member function getFromDB() on false".
   - Existing references migrated from older itemtypes may have
     templates_id = 0, which makes getFromDB() fail silently and the
     subsequent getField() calls operate on a half-initialised object.

2. PluginOrderLink::generateItem() (link.class.php)
   - Toolbox::hasTrait() internally calls class_uses(). On PHP 8.3 with
     the Safe library wrapper, class_uses() on a non-existent class
     throws Safe\Exceptions\SplException instead of emitting a warning.
   - This is triggered by stale itemtype values in
     glpi_plugin_order_orders_items that reference classes from
     uninstalled plugins (e.g. PluginGenericobjectOther after migrating
     to native custom assets).

Both call sites are now guarded:
- reference.class.php: bail out early with return 0 when the item
  cannot be instantiated or templates_id is empty.
- link.class.php: prefix both Toolbox::hasTrait() calls with
  class_exists() so non-existent classes are skipped cleanly.

Tested on GLPI 11.0.7, PHP 8.3, Order plugin 2.12.6:
- Order with valid native itemtypes (Computer, Monitor, etc.): unchanged.
- Order containing custom asset references (Glpi\CustomAsset\otherAsset):
  Generate item form now renders fully and submits successfully.
- Order containing stale itemtypes from uninstalled plugins
  (PluginGenericobjectOther): items are skipped without fatal error,
  remaining items render correctly.
@bygadd
Copy link
Copy Markdown
Contributor Author

bygadd commented May 14, 2026

Related: opened pluginsGLPI/room#64 today documenting similar GLPI 11 compatibility issues in the room plugin — including null-safety patterns analogous to the ones in this PR (guard before object instantiation, guard before trait checks).

The room patch set is in production use on the same instance where this PR's guards were developed (GLPI 11.0.7, ~700K warnings/day → 0).

@Rom1-B Rom1-B requested review from Rom1-B and stonebuzz May 18, 2026 08:14
Comment thread inc/reference.class.php Outdated
Comment on lines +476 to +481
if ($item === false || empty($row["templates_id"])) {
return 0;
}
if (!$item->getFromDB($row["templates_id"])) {
return 0;
}
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
if ($item === false || empty($row["templates_id"])) {
return 0;
}
if (!$item->getFromDB($row["templates_id"])) {
return 0;
}
if (
$item === false
|| (int) $row["templates_id"] === 0
|| !$item->getFromDB($row["templates_id"])
) {
return 0;
}

Copy link
Copy Markdown
Contributor

@Rom1-B Rom1-B left a comment

Choose a reason for hiding this comment

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

Please see my comment and fix CI

Apply review feedback from @Rom1-B: merge the two consecutive
early-return guards into a single if block, and use (int) cast for
templates_id check instead of empty(). This also satisfies the
Rector codeQuality ruleset enforced in CI.
@bygadd
Copy link
Copy Markdown
Contributor Author

bygadd commented May 21, 2026

@Rom1-B Applied your suggestion in 0d12da7, thanks!

Comment thread CHANGELOG.md Outdated
Per @stonebuzz review suggestion: use a shorter, action-oriented
phrasing for the changelog entry and drop the version-specific suffix.
@bygadd
Copy link
Copy Markdown
Contributor Author

bygadd commented May 21, 2026

@stonebuzz Applied your suggestion in dabdbba, thanks!

@bygadd bygadd requested a review from stonebuzz May 21, 2026 08:17
@stonebuzz stonebuzz merged commit 926b090 into pluginsGLPI:main May 21, 2026
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.

3 participants