Commit 9b1c2c80 by Qiang Xue

Fixes #1586: `QueryBuilder::buildLikeCondition()` will now escape special…

Fixes #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default
parent f3450682
...@@ -142,11 +142,16 @@ Operator can be one of the following: ...@@ -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. - `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 - `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. 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 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%'`. `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` - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
predicates when operand 2 is an array. predicates when operand 2 is an array.
- `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE` - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
...@@ -162,7 +167,7 @@ $search = 'yii'; ...@@ -162,7 +167,7 @@ $search = 'yii';
$query->where(['status' => $status]); $query->where(['status' => $status]);
if (!empty($search)) { if (!empty($search)) {
$query->addWhere('like', 'title', $search); $query->addWhere(['like', 'title', $search]);
} }
``` ```
......
...@@ -74,7 +74,6 @@ class <?= $searchModelClass ?> extends Model ...@@ -74,7 +74,6 @@ class <?= $searchModelClass ?> extends Model
return; return;
} }
if ($partialMatch) { if ($partialMatch) {
$value = '%' . strtr($value, ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']) . '%';
$query->andWhere(['like', $attribute, $value]); $query->andWhere(['like', $attribute, $value]);
} else { } else {
$query->andWhere([$attribute => $value]); $query->andWhere([$attribute => $value]);
......
...@@ -429,6 +429,8 @@ class Query extends Component implements QueryInterface ...@@ -429,6 +429,8 @@ class Query extends Component implements QueryInterface
* 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%'`. * `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 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` * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* predicates when operand 2 is an array. * predicates when operand 2 is an array.
......
...@@ -754,11 +754,19 @@ class QueryBuilder extends Object ...@@ -754,11 +754,19 @@ class QueryBuilder extends Object
* Creates an SQL expressions with the `LIKE` operator. * Creates an SQL expressions with the `LIKE` operator.
* @param IndexSchema[] $indexes list of indexes, which affected by query * @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 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. * @param array $operands an array of two or three operands
* The second operand is a single value or an array of values that column value *
* should be compared with. * - The first operand is the column name.
* If it is an empty array the generated expression will be a `false` value if * - The second operand is a single value or an array of values that column value
* operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`. * 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 * @param array $params the binding parameters to be populated
* @return string the generated SQL expression * @return string the generated SQL expression
* @throws InvalidParamException if wrong number of operands have been given. * @throws InvalidParamException if wrong number of operands have been given.
...@@ -769,6 +777,9 @@ class QueryBuilder extends Object ...@@ -769,6 +777,9 @@ class QueryBuilder extends Object
throw new InvalidParamException("Operator '$operator' requires two operands."); throw new InvalidParamException("Operator '$operator' requires two operands.");
} }
$escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
unset($operands[2]);
list($column, $values) = $operands; list($column, $values) = $operands;
$values = (array)$values; $values = (array)$values;
...@@ -791,7 +802,7 @@ class QueryBuilder extends Object ...@@ -791,7 +802,7 @@ class QueryBuilder extends Object
$parts = []; $parts = [];
foreach ($values as $value) { foreach ($values as $value) {
$phName = self::PARAM_PREFIX . count($params); $phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value; $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
$parts[] = "$column $operator $phName"; $parts[] = "$column $operator $phName";
} }
......
...@@ -44,6 +44,7 @@ Yii Framework 2 Change Log ...@@ -44,6 +44,7 @@ Yii Framework 2 Change Log
- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
- Enh: Support for file aliases in console command 'message' (omnilight) - Enh: Support for file aliases in console command 'message' (omnilight)
- Enh: Sort and Pagination can now create absolute URLs (cebe) - 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 #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 #1643: Added default value for `Captcha::options` (qiangxue)
- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
......
...@@ -387,11 +387,13 @@ class Query extends Component implements QueryInterface ...@@ -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 * - `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. * 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 * 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%'`. * `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` * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* predicates when operand 2 is an array. * predicates when operand 2 is an array.
......
...@@ -1017,11 +1017,19 @@ class QueryBuilder extends \yii\base\Object ...@@ -1017,11 +1017,19 @@ class QueryBuilder extends \yii\base\Object
/** /**
* Creates an SQL expressions with the `LIKE` operator. * 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 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. * @param array $operands an array of two or three operands
* The second operand is a single value or an array of values that column value *
* should be compared with. * - The first operand is the column name.
* If it is an empty array the generated expression will be a `false` value if * - The second operand is a single value or an array of values that column value
* operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`. * 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 * @param array $params the binding parameters to be populated
* @return string the generated SQL expression * @return string the generated SQL expression
* @throws InvalidParamException if wrong number of operands have been given. * @throws InvalidParamException if wrong number of operands have been given.
...@@ -1032,6 +1040,9 @@ class QueryBuilder extends \yii\base\Object ...@@ -1032,6 +1040,9 @@ class QueryBuilder extends \yii\base\Object
throw new InvalidParamException("Operator '$operator' requires two operands."); throw new InvalidParamException("Operator '$operator' requires two operands.");
} }
$escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
unset($operands[2]);
list($column, $values) = $operands; list($column, $values) = $operands;
$values = (array)$values; $values = (array)$values;
...@@ -1054,7 +1065,7 @@ class QueryBuilder extends \yii\base\Object ...@@ -1054,7 +1065,7 @@ class QueryBuilder extends \yii\base\Object
$parts = []; $parts = [];
foreach ($values as $value) { foreach ($values as $value) {
$phName = self::PARAM_PREFIX . count($params); $phName = self::PARAM_PREFIX . count($params);
$params[$phName] = $value; $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
$parts[] = "$column $operator $phName"; $parts[] = "$column $operator $phName";
} }
......
...@@ -122,11 +122,13 @@ interface QueryInterface ...@@ -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 * - `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. * 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 * 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%'`. * `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` * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
* predicates when operand 2 is an array. * predicates when operand 2 is an array.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment