From 9b1c2c8064af56106a89441a983e8256079174fc Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 29 Dec 2013 13:04:02 -0500 Subject: [PATCH] Fixes #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default --- docs/guide/query-builder.md | 13 +++++++---- .../yii/gii/generators/crud/templates/search.php | 1 - extensions/yii/sphinx/Query.php | 4 +++- extensions/yii/sphinx/QueryBuilder.php | 25 ++++++++++++++++------ framework/CHANGELOG.md | 1 + framework/yii/db/Query.php | 8 ++++--- framework/yii/db/QueryBuilder.php | 23 ++++++++++++++------ framework/yii/db/QueryInterface.php | 10 +++++---- 8 files changed, 59 insertions(+), 26 deletions(-) diff --git a/docs/guide/query-builder.md b/docs/guide/query-builder.md index f775c76..2bc215d 100644 --- a/docs/guide/query-builder.md +++ b/docs/guide/query-builder.md @@ -142,11 +142,16 @@ Operator can be one of the following: - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing the values that the column or DB expression should be like. - For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`. + For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`. When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated - using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate + using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate `name LIKE '%test%' AND name LIKE '%sample%'`. - The method will properly quote the column name and escape values in the range. + You may also provide an optional third operand to specify how to escape special characters in the values. + The operand should be an array of mappings from the special characters to their + escaped counterparts. If this operand is not provided, a default escape mapping will be used. + You may use `false` or an empty array to indicate the values are already escaped and no escape + should be applied. Note that when using an escape mapping (or the third operand is not provided), + the values will be automatically enclosed within a pair of percentage characters. - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` predicates when operand 2 is an array. - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE` @@ -162,7 +167,7 @@ $search = 'yii'; $query->where(['status' => $status]); if (!empty($search)) { - $query->addWhere('like', 'title', $search); + $query->addWhere(['like', 'title', $search]); } ``` diff --git a/extensions/yii/gii/generators/crud/templates/search.php b/extensions/yii/gii/generators/crud/templates/search.php index 1411896..bc55c60 100644 --- a/extensions/yii/gii/generators/crud/templates/search.php +++ b/extensions/yii/gii/generators/crud/templates/search.php @@ -74,7 +74,6 @@ class extends Model return; } if ($partialMatch) { - $value = '%' . strtr($value, ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']) . '%'; $query->andWhere(['like', $attribute, $value]); } else { $query->andWhere([$attribute => $value]); diff --git a/extensions/yii/sphinx/Query.php b/extensions/yii/sphinx/Query.php index dd13363..2a51996 100644 --- a/extensions/yii/sphinx/Query.php +++ b/extensions/yii/sphinx/Query.php @@ -429,6 +429,8 @@ class Query extends Component implements QueryInterface * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate * `name LIKE '%test%' AND name LIKE '%sample%'`. * The method will properly quote the column name and escape values in the range. + * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply + * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`. * * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` * predicates when operand 2 is an array. @@ -700,4 +702,4 @@ class Query extends Component implements QueryInterface ->callSnippets($this->from[0], $source, $match, $this->snippetOptions) ->queryColumn(); } -} \ No newline at end of file +} diff --git a/extensions/yii/sphinx/QueryBuilder.php b/extensions/yii/sphinx/QueryBuilder.php index 586b3a5..6bd6d0c 100644 --- a/extensions/yii/sphinx/QueryBuilder.php +++ b/extensions/yii/sphinx/QueryBuilder.php @@ -754,11 +754,19 @@ class QueryBuilder extends Object * Creates an SQL expressions with the `LIKE` operator. * @param IndexSchema[] $indexes list of indexes, which affected by query * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) - * @param array $operands the first operand is the column name. - * The second operand is a single value or an array of values that column value - * should be compared with. - * If it is an empty array the generated expression will be a `false` value if - * operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`. + * @param array $operands an array of two or three operands + * + * - The first operand is the column name. + * - The second operand is a single value or an array of values that column value + * should be compared with. If it is an empty array the generated expression will + * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator + * is `NOT LIKE` or `OR NOT LIKE`. + * - An optional third operand can also be provided to specify how to escape special characters + * in the value(s). The operand should be an array of mappings from the special characters to their + * escaped counterparts. If this operand is not provided, a default escape mapping will be used. + * You may use `false` or an empty array to indicate the values are already escaped and no escape + * should be applied. Note that when using an escape mapping (or the third operand is not provided), + * the values will be automatically enclosed within a pair of percentage characters. * @param array $params the binding parameters to be populated * @return string the generated SQL expression * @throws InvalidParamException if wrong number of operands have been given. @@ -769,6 +777,9 @@ class QueryBuilder extends Object throw new InvalidParamException("Operator '$operator' requires two operands."); } + $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']; + unset($operands[2]); + list($column, $values) = $operands; $values = (array)$values; @@ -791,7 +802,7 @@ class QueryBuilder extends Object $parts = []; foreach ($values as $value) { $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $value; + $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%'); $parts[] = "$column $operator $phName"; } @@ -902,4 +913,4 @@ class QueryBuilder extends Object return $phName; } } -} \ No newline at end of file +} diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 5a0b7fe..4260809 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -44,6 +44,7 @@ Yii Framework 2 Change Log - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Support for file aliases in console command 'message' (omnilight) - Enh: Sort and Pagination can now create absolute URLs (cebe) +- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue) - Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue) - Chg #1643: Added default value for `Captcha::options` (qiangxue) - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php index ee24c2f..9da4f14 100644 --- a/framework/yii/db/Query.php +++ b/framework/yii/db/Query.php @@ -387,11 +387,13 @@ class Query extends Component implements QueryInterface * * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing * the values that the column or DB expression should be like. - * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`. + * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`. * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated - * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate + * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate * `name LIKE '%test%' AND name LIKE '%sample%'`. - * The method will properly quote the column name and escape values in the range. + * The method will properly quote the column name and escape special characters in the values. + * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply + * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`. * * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` * predicates when operand 2 is an array. diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index a76f211..f819362 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -1017,11 +1017,19 @@ class QueryBuilder extends \yii\base\Object /** * Creates an SQL expressions with the `LIKE` operator. * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) - * @param array $operands the first operand is the column name. - * The second operand is a single value or an array of values that column value - * should be compared with. - * If it is an empty array the generated expression will be a `false` value if - * operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`. + * @param array $operands an array of two or three operands + * + * - The first operand is the column name. + * - The second operand is a single value or an array of values that column value + * should be compared with. If it is an empty array the generated expression will + * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator + * is `NOT LIKE` or `OR NOT LIKE`. + * - An optional third operand can also be provided to specify how to escape special characters + * in the value(s). The operand should be an array of mappings from the special characters to their + * escaped counterparts. If this operand is not provided, a default escape mapping will be used. + * You may use `false` or an empty array to indicate the values are already escaped and no escape + * should be applied. Note that when using an escape mapping (or the third operand is not provided), + * the values will be automatically enclosed within a pair of percentage characters. * @param array $params the binding parameters to be populated * @return string the generated SQL expression * @throws InvalidParamException if wrong number of operands have been given. @@ -1032,6 +1040,9 @@ class QueryBuilder extends \yii\base\Object throw new InvalidParamException("Operator '$operator' requires two operands."); } + $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']; + unset($operands[2]); + list($column, $values) = $operands; $values = (array)$values; @@ -1054,7 +1065,7 @@ class QueryBuilder extends \yii\base\Object $parts = []; foreach ($values as $value) { $phName = self::PARAM_PREFIX . count($params); - $params[$phName] = $value; + $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%'); $parts[] = "$column $operator $phName"; } diff --git a/framework/yii/db/QueryInterface.php b/framework/yii/db/QueryInterface.php index f3cc312..4cf802d 100644 --- a/framework/yii/db/QueryInterface.php +++ b/framework/yii/db/QueryInterface.php @@ -122,11 +122,13 @@ interface QueryInterface * * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing * the values that the column or DB expression should be like. - * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`. + * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`. * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated - * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate + * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate * `name LIKE '%test%' AND name LIKE '%sample%'`. - * The method will properly quote the column name and escape values in the range. + * The method will properly quote the column name and escape special characters in the values. + * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply + * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`. * * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` * predicates when operand 2 is an array. @@ -203,4 +205,4 @@ interface QueryInterface * @return static the query object itself */ public function offset($offset); -} \ No newline at end of file +}