Skip to content

fix(processors): normalize string 'true'/'false' to boolean in getProperty#16888

Open
Ibochkarev wants to merge 5 commits intomodxcms:3.xfrom
Ibochkarev:fix/processor-boolean-property-normalization
Open

fix(processors): normalize string 'true'/'false' to boolean in getProperty#16888
Ibochkarev wants to merge 5 commits intomodxcms:3.xfrom
Ibochkarev:fix/processor-boolean-property-normalization

Conversation

@Ibochkarev
Copy link
Collaborator

@Ibochkarev Ibochkarev commented Feb 24, 2026

What does it do?

Adds a new method Processor::getBooleanProperty($k, $default = false) that normalizes string values like "true", "false", "on", "off", "1", "0", "yes", "no" (case-insensitive, trimmed) to boolean. Processor::getProperty() is unchanged and still returns raw values. When JavaScript sends boolean parameters to processors (e.g. via ExtJS or fetch), they arrive as strings; processors that need a real boolean can call getBooleanProperty() so logic works without manual casting.

Why is it needed?

JavaScript/ExtJS sends boolean values as strings ("true"/"false"). Processors using getProperty('someFlag') receive strings, so if ($this->getProperty('someFlag')) is always truthy for non-empty strings and breaks when the intended value is false. Introducing a dedicated getBooleanProperty() avoids changing existing getProperty() behavior (and potential impact on core and extras) while giving processors an explicit way to read boolean params.

How to test

  1. In a processor, use $this->getBooleanProperty('someBooleanParam', false).
  2. Call it with someBooleanParam=false (e.g. via AJAX or connector).
  3. Assert getBooleanProperty('someBooleanParam', false) returns false (boolean).
  4. Call with someBooleanParam=true and assert it returns true (boolean).
  5. Call with someBooleanParam=TRUE, True, or true and assert case-insensitive, trimmed handling.
  6. Call with someBooleanParam=on, yes, 1 (true) and off, no, 0 (false) and assert normalization.
  7. Run _build/test/Tests/Processors/ProcessorTest.php for full getBooleanProperty() coverage.

Related issue(s)/PR(s)

Fixes #14016

…perty

JavaScript sends boolean params as strings. getProperty now converts
'true'/'false' (case-insensitive) to actual booleans so processor logic
works correctly without explicit type casting.
@Ibochkarev Ibochkarev marked this pull request as ready for review February 24, 2026 17:49
@Mark-H Mark-H requested a review from Copilot February 24, 2026 23:16
@Mark-H
Copy link
Collaborator

Mark-H commented Feb 24, 2026

I'm mildly worried this may have unintentional far-reaching consequences in processors that have been doing its own normalisation based on strings that is now going to break.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR attempts to fix an issue where JavaScript/ExtJS sends boolean parameters as strings ("true"/"false") to processors, causing them to be treated as truthy strings instead of proper booleans. The solution modifies Processor::getProperty() to automatically normalize case-insensitive string "true"/"false" (with trimming) to boolean values.

Changes:

  • Modified getProperty() method in Processor.php to convert string "true"/"false" (case-insensitive, trimmed) to boolean
  • Added inline documentation explaining the normalization behavior

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +225 to +234
$value = array_key_exists($k, $this->properties) ? $this->properties[$k] : $default;
if (is_string($value)) {
$normalized = strtolower(trim($value));
return match ($normalized) {
'true' => true,
'false' => false,
default => $value,
};
}
return $value;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The automatic normalization changes the data type globally for all processors without any opt-in mechanism. This is a breaking API change that affects the return type of a widely-used method. Consider that third-party code and extensions may rely on the current behavior where string values are returned as-is.

A safer approach would be to:

  1. Introduce a new method like getBooleanProperty() that performs normalization
  2. Or add an optional parameter to control normalization: getProperty($k, $default = null, $normalizeBoolean = false)
  3. Or use a trait/mixin that processors can opt into for boolean normalization

This would allow new code to benefit from the normalization while preserving backward compatibility for existing code.

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +234
$value = array_key_exists($k, $this->properties) ? $this->properties[$k] : $default;
if (is_string($value)) {
$normalized = strtolower(trim($value));
return match ($normalized) {
'true' => true,
'false' => false,
default => $value,
};
}
return $value;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This change lacks test coverage to verify:

  1. That string 'true' and 'false' (in various cases) are correctly normalized to boolean
  2. That the normalization handles whitespace correctly
  3. That non-string values are not affected
  4. That default values work correctly with normalization
  5. That existing code comparing with string 'false' is not broken

Given the widespread use of getProperty() throughout the codebase (hundreds of calls), comprehensive tests are critical to ensure this change doesn't introduce regressions. Tests should cover both the new normalization behavior and compatibility with existing usage patterns.

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +234
$value = array_key_exists($k, $this->properties) ? $this->properties[$k] : $default;
if (is_string($value)) {
$normalized = strtolower(trim($value));
return match ($normalized) {
'true' => true,
'false' => false,
default => $value,
};
}
return $value;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This automatic normalization of string "true"/"false" to boolean will break existing code that explicitly checks for the string "false". For example, in Security/Profile/Update.php (lines 76 and 111), there's code that checks if ($this->getProperty('newpassword') !== 'false'). With this change, getProperty('newpassword') will return boolean false instead of string "false", breaking the logic since false !== 'false' is always true.

Similarly, in Resource/Get.php (line 34), there's a check $this->getProperty('skipFormatDates') == 'false' which will no longer work as expected.

The setCheckbox method (line 261) also relies on comparing with string 'false': $this->properties[$k] === 'false'. Since this method accesses $this->properties directly instead of using getProperty(), it would still work, but this creates an inconsistency where the same property value behaves differently depending on how it's accessed.

Consider these alternatives:

  1. Only normalize in specific scenarios where you know the value should be boolean
  2. Add a separate method like getBooleanProperty() that performs this normalization
  3. Fix all existing code that compares with string 'false' before implementing this change
  4. Add a parameter to getProperty() to opt-in to normalization: getProperty($k, $default = null, $normalizeBoolean = false)

Copilot uses AI. Check for mistakes.
Comment on lines +228 to +232
return match ($normalized) {
'true' => true,
'false' => false,
default => $value,
};
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

This change will affect the behavior of code that uses string comparisons with 'on' values. In Context/Duplicate.php, there are comparisons like $this->getProperty('preserve_resources') == 'on'. Since 'on' is not normalized, this will continue to work, but it creates an inconsistency where boolean-like string values are handled differently ('true'/'false' vs 'on'/'off').

ExtJS checkboxes can send 'on' as their checked value, and there's existing code that handles this. The PR description mentions JavaScript sends "true"/"false", but doesn't address the 'on' pattern which is also used in the codebase. This inconsistency could be confusing for developers.

Copilot uses AI. Check for mistakes.
@Mark-H
Copy link
Collaborator

Mark-H commented Feb 24, 2026

Introduce a new method like getBooleanProperty() that performs normalization

I think this suggestion makes a lot of sense given the potential impact this may have on core and extras. Copilot already highlighted a few cases in the codebase that break.

Ibochkarev and others added 4 commits February 25, 2026 10:16
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- Revert getProperty() to original behavior (no boolean normalization)
- Add getBooleanProperty($k, $default) for explicit bool parsing
- Support 'true'/'false'/'on'/'off'/'1'/'0'/'yes'/'no' (case-insensitive)
- Resource/Get: use getBooleanProperty('skipFormatDates', false)
- formatDates: use !empty($resourceArray['publishedon']) for consistency
- Add ProcessorTest for getBooleanProperty() coverage
- Fix PSR12 file header spacing in Resource/Get.php

Addresses PR modxcms#16888 review (Mark-H, Copilot): avoid changing getProperty()
to prevent impact on core and extras.
@Ibochkarev
Copy link
Collaborator Author

@Mark-H please re-run review copilot. Thanks

@biz87
Copy link

biz87 commented Feb 25, 2026

Code Review

Summary

Adds Processor::getBooleanProperty($k, $default) to normalize string boolean values ("true", "false", "on", "off", "1", "0", "yes", "no") to proper PHP booleans. Existing getProperty() is unchanged. Includes comprehensive PHPUnit tests covering all edge cases (case insensitivity, trimming, native booleans, missing keys, unknown strings).

Assessment

Clean, non-breaking addition. The match expression (PHP 8.0+) is appropriate for the project's PHP 8.1+ requirement. Test coverage is thorough — 13 test methods covering all branches. The method properly falls through to $default for unrecognized strings and empty values, which is the correct behavior. No existing code is modified beyond adding the new method and test file.

Verdict: Approve

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.

Processors do not respect type of the parameter that was passed from javascript code

4 participants