Fix xPDOQuery::set() SQL keyword handling; introduce xPDOExpression#273
Fix xPDOQuery::set() SQL keyword handling; introduce xPDOExpression#273opengeek wants to merge 2 commits intomodxcms:3.xfrom
Conversation
…g values Fixes the bug reported in modxcms/revolution#9487 where updateCollection() fails when a string value contains SQL operator keywords like "IN". The root cause was xPDOQuery::set() calling isConditionalClause() on SET values, which treated any string containing " IN " (or other operators) as a raw SQL expression and left it unquoted, producing invalid SQL. The fix has two parts: 1. Correctness fix: plain PHP strings in xPDOQuery::set() are now always assigned PDO::PARAM_STR (always quoted), regardless of content. The isConditionalClause() heuristic is removed from set() but preserved in parseConditions() where it belongs for WHERE-clause handling. 2. Escape hatch: introduce xPDO\Om\xPDOExpression, a final value object that explicitly marks a value as a raw SQL fragment, bypassing automatic quoting. Use xPDO::expression() as a factory. Updated xPDOQuery::set(), all four driver construct() methods, xPDOObject::set(), and xPDOObject::save() to recognise and inline xPDOExpression values. Example usage: // Plain strings now always work correctly $xpdo->updateCollection('MyClass', ['field' => 'The word IN is IN this strINg']); // Use xPDOExpression for intentional raw SQL $xpdo->updateCollection('MyClass', ['counter' => $xpdo->expression('counter + 1')]); Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The There is additional data filtering that would be worthwhile for sure, but this is how it's often done. With this PR applied, an attacker would very easily be able to submit a And if I understand correctly the same would be true for any sort of create/update logic that takes in some user input. I am fairly tired at the moment so maybe I'm misreading things, but those last few tests basically read as a vulnerability instead of a feature. I do see value in having this available and fixing the queries breaking, but instead of the magic coercion, let the implementation determine something may accept direct SQL with the |
These are solid points. I think I'll remove the string syntax. It seemed like a positive feature when I was experimenting with it, but I think you are correct in thinking it would create more problems than it solves. Thanks for looking! |
xPDOExpression uses typed class properties (private string $expression) which require PHP 7.4. Update composer.json and the CI workflow matrix to reflect the new minimum. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
d44185e to
a3bea91
Compare
Summary
Fixes #54. Supersedes #271.
Commit 1 — Bug fix:
xPDOQuery::set()misinterprets strings containing SQL keywordsBug:
updateCollection()(and anyxPDOQuery::set()call) silently produced invalid SQL when a string value contained SQL operator keywords such asIN,LIKE, orNOT. The root cause wasset()callingisConditionalClause()on values — any matching string was treated as a raw SQL expression and left unquoted.Fix: Plain PHP strings in
xPDOQuery::set()are now always assignedPDO::PARAM_STRand quoted, regardless of content.isConditionalClause()is removed fromset()but preserved inparseConditions()where it belongs.Escape hatch —
xPDOExpression: A new final value object (xPDO\Om\xPDOExpression) explicitly marks a value as a raw SQL fragment, bypassing automatic quoting. UsexPDO::expression()as a factory. All four driverconstruct()methods,xPDOQuery::set(),xPDOObject::set(), andxPDOObject::save()recognise and inlinexPDOExpressionvalues verbatim.Commit 2 — Bump minimum PHP version to 7.4; remove PHP 7.2/7.3 from CI
xPDOExpressionuses typed class properties (private string $expression) which require PHP 7.4. Updatescomposer.jsonand the CI workflow matrix accordingly.Files changed
src/xPDO/Om/xPDOExpression.phpsrc/xPDO/Om/xPDOQuery.phpset()to always quote strings; inlinexPDOExpressionverbatimsrc/xPDO/xPDO.phpexpression()factory methodsrc/xPDO/Om/mysql/xPDOQuery.phpxPDOExpressionin SET clausesrc/xPDO/Om/pgsql/xPDOQuery.phpxPDOExpressionin SET clausesrc/xPDO/Om/sqlite/xPDOQuery.phpxPDOExpressionin SET clausesrc/xPDO/Om/sqlsrv/xPDOQuery.phpxPDOExpressionin SET clausesrc/xPDO/Om/xPDOObject.phpxPDOExpressioninset()andsave()composer.json.github/workflows/ci.ymltest/xPDO/Test/Om/xPDOExpressionTest.phptest/xPDO/Test/Om/xPDOQueryTest.phptest/xPDO/Test/Om/xPDOObjectTest.phpTest plan
xPDOExpressionTest— 4 tests passingxPDOQueryTest— includes string-value bug fix tests andxPDOExpressionin SETxPDOObjectTest— includesxPDOExpressioninset()andsave()xPDOQueryHavingTest,xPDOQueryLimitTest,xPDOQuerySortByTest— all passingxPDOQueryConditionsTest— pre-existing failures unrelated to this PR🤖 Generated with Claude Code