Skip to content

🐛 Only offer states from classes with a lifecycle in AttributeClassState#893

Open
larhip wants to merge 1 commit intoCombodo:developfrom
itomig-de:fix/attribute-class-state-lifecycle-filter
Open

🐛 Only offer states from classes with a lifecycle in AttributeClassState#893
larhip wants to merge 1 commit intoCombodo:developfrom
itomig-de:fix/attribute-class-state-lifecycle-filter

Conversation

@larhip
Copy link
Copy Markdown
Contributor

@larhip larhip commented Apr 24, 2026

Base information

Question Answer
Related to a SourceForge thread / Another PR / A GitHub Issue / Combodo ticket? N°6464
Type of change? Bug fix

Symptom

When configuring a TriggerOnStateChange (or its subclasses TriggerOnStateEnter / TriggerOnStateLeave), the State dropdown offers states from classes that have a state attribute but no lifecycle transitions. Selecting such a state results in a trigger that will never fire, since TriggerOnStateChange only activates on actual lifecycle state changes (i.e. when a stimulus is applied).

Example: A class like Person may have an status attribute with values active/inactive, but no stimuli/transitions defined. Its states appeared in the dropdown even though no lifecycle change can ever occur on it.

Reproduction procedure

  1. Any iTop instance (Community or Professional), any version with TriggerOnStateChange
  2. Go to Admin Tools → Notifications → Triggers
  3. Create a new TriggerOnStateEnter or TriggerOnStateLeave
  4. Set Target class to a class that has a state attribute but no lifecycle (e.g. Personactive/inactive states, no transitions)
  5. Open the State dropdown
  6. Observed: States from classes without a lifecycle are listed (e.g. active, inactive for Person)
  7. Expected: Only states from classes that have a full lifecycle (state attribute and at least one stimulus) should be listed

Cause

AttributeClassState::GetAllowedValues() iterates over all child classes of the selected target class and collects their states via MetaModel::EnumStates(). This method returns states for any class with a state attribute, regardless of whether that class has lifecycle transitions (stimuli).

TriggerOnStateChange (and its concrete subclasses) only fires when a state change is triggered by a stimulus — i.e. only for classes with a full lifecycle. The distinction already exists in the codebase:

  • MetaModel::HasStateAttributeCode() — class has a state attribute
  • MetaModel::HasLifecycle() — class has a state attribute and at least one stimulus

GetAllowedValues() was using the weaker check (implicitly via EnumStates()), but should use the stricter HasLifecycle().

Proposed solution

In AttributeClassState::GetAllowedValues(), skip child classes for which MetaModel::HasLifecycle() returns false before collecting their states:

foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
    if (!MetaModel::HasLifecycle($sChildClass)) {
        continue;
    }
    $aValues = MetaModel::EnumStates($sChildClass);
    // ...
}

MetaModel::HasLifecycle() already encapsulates exactly the right condition:

return self::HasStateAttributeCode($sClass) && !empty(self::EnumStimuli($sClass));

The change is 3 lines, confined to sources/Core/AttributeDefinition/AttributeClassState.php, and has no impact on classes that do have a lifecycle.

Checklist before requesting a review

  • I have performed a self-review of my code
  • I have tested all changes I made on an iTop instance
  • I have added a unit test, otherwise I have explained why I couldn't
    • A unit test would require a datamodel with a class having a state attribute but no stimuli. This is an edge case that is hard to cover with the existing test infrastructure without a custom XML delta. The fix itself is a one-condition guard using an already-tested MetaModel method.
  • Is the PR clear and detailed enough so anyone can understand without digging in the code?

When enumerating allowed states in AttributeClassState::GetAllowedValues(),
child classes that have a state attribute but no lifecycle transitions
(stimuli) were included. TriggerOnStateChange only fires on actual
lifecycle state changes, so states from classes without a lifecycle
would never trigger — but were still offered in the UI.

Fix: skip child classes where MetaModel::HasLifecycle() returns false.
@Hipska
Copy link
Copy Markdown
Contributor

Hipska commented Apr 24, 2026

There is an existing bug: N°6464 - Make TriggerOnStateChange working when the state attribute is not in a lifecycle

@larhip
Copy link
Copy Markdown
Contributor Author

larhip commented Apr 24, 2026

There is an existing bug: N°6464 - Make TriggerOnStateChange working when the state attribute is not in a lifecycle

Wasn't aware of that bug, since I am not able to see this one 😅

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

Labels

None yet

Projects

Status: Pending review

Development

Successfully merging this pull request may close these issues.

3 participants