1 | <?php |
||||
2 | /** |
||||
3 | * @link https://github.com/chrmorandi/yii2-ldap for the source repository |
||||
4 | * @package yii2-ldap |
||||
5 | * @author Christopher Mota <[email protected]> |
||||
6 | * @license MIT License - view the LICENSE file that was distributed with this source code. |
||||
7 | * @since 1.0.0 |
||||
8 | */ |
||||
9 | |||||
10 | namespace chrmorandi\ldap; |
||||
11 | |||||
12 | use yii\base\InvalidParamException; |
||||
13 | use yii\base\BaseObject; |
||||
14 | |||||
15 | /** |
||||
16 | * FilterBuilder builds a Filter for search in LDAP. |
||||
17 | * |
||||
18 | * FilterBuilder is also used by [[Query]] to build Filters. |
||||
19 | * |
||||
20 | * @author Christopher Mota <[email protected]> |
||||
21 | * @since 1.0.0 |
||||
22 | */ |
||||
23 | class FilterBuilder extends BaseObject |
||||
24 | { |
||||
25 | /** |
||||
26 | * @var string the separator between different fragments of a SQL statement. |
||||
27 | * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. |
||||
28 | */ |
||||
29 | public $separator = ' '; |
||||
30 | |||||
31 | /** |
||||
32 | * @var array map of query condition to builder methods. |
||||
33 | * These methods are used by [[buildCondition]] to build SQL conditions from array syntax. |
||||
34 | */ |
||||
35 | protected $conditionBuilders = [ |
||||
36 | 'NOT' => 'buildAndCondition', |
||||
37 | 'AND' => 'buildAndCondition', |
||||
38 | 'OR' => 'buildAndCondition', |
||||
39 | 'IN' => 'buildInCondition', |
||||
40 | 'NOT IN' => 'buildInCondition', |
||||
41 | 'LIKE' => 'buildLikeCondition', |
||||
42 | 'NOT LIKE' => 'buildLikeCondition', |
||||
43 | 'OR LIKE' => 'buildLikeCondition', |
||||
44 | 'OR NOT LIKE' => 'buildLikeCondition', |
||||
45 | ]; |
||||
46 | |||||
47 | /** |
||||
48 | * @var array map of operator for builder methods. |
||||
49 | */ |
||||
50 | protected $operator = [ |
||||
51 | 'NOT' => '!', |
||||
52 | 'AND' => '&', |
||||
53 | 'OR' => '|', |
||||
54 | ]; |
||||
55 | |||||
56 | /** |
||||
57 | * Parses the condition specification and generates the corresponding filters. |
||||
58 | * @param string|array $condition the condition specification. Please refer to [[Query::where()]] |
||||
59 | * on how to specify a condition. |
||||
60 | * @return string the generated |
||||
61 | */ |
||||
62 | public function build($condition) |
||||
63 | { |
||||
64 | if (!is_array($condition)) { |
||||
65 | return (string) $condition; |
||||
66 | } elseif (empty($condition)) { |
||||
67 | return ''; |
||||
68 | } |
||||
69 | |||||
70 | if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... |
||||
71 | $operator = strtoupper($condition[0]); |
||||
72 | if (isset($this->conditionBuilders[$operator])) { |
||||
73 | $method = $this->conditionBuilders[$operator]; |
||||
74 | } else { |
||||
75 | $method = 'buildSimpleCondition'; |
||||
76 | } |
||||
77 | array_shift($condition); |
||||
78 | return $this->$method($operator, $condition); |
||||
79 | } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... |
||||
80 | return $this->buildHashCondition($condition); |
||||
81 | } |
||||
82 | } |
||||
83 | |||||
84 | /** |
||||
85 | * Creates a condition based on column-value pairs. |
||||
86 | * @param array $condition the condition specification. |
||||
87 | * @return string the generated |
||||
88 | */ |
||||
89 | public function buildHashCondition($condition) |
||||
90 | { |
||||
91 | $parts = []; |
||||
92 | foreach ($condition as $attribute => $value) { |
||||
93 | if (is_array($value)) { |
||||
94 | // IN condition |
||||
95 | $parts[] = $this->buildInCondition('IN', [$attribute, $value]); |
||||
96 | } elseif ($value === null) { |
||||
97 | $parts[] = "(!($attribute=*))"; |
||||
98 | } elseif ($attribute === 'dn') { |
||||
99 | $parts[] = '(' . LdapHelper::getRdnFromDn($value) . ')'; |
||||
100 | } else { |
||||
101 | $parts[] = "($attribute=$value)"; |
||||
102 | } |
||||
103 | } |
||||
104 | return count($parts) === 1 ? $parts[0] : '(&' . implode('', $parts) . ')'; |
||||
105 | } |
||||
106 | |||||
107 | /** |
||||
108 | * Connects two or more filters expressions with the `AND`(&) or `OR`(|) operator. |
||||
109 | * @param string $operator The operator to use for connecting the given operands |
||||
110 | * @param array $operands The filter expressions to connect. |
||||
111 | * @return string The generated filter |
||||
112 | */ |
||||
113 | public function buildAndCondition($operator, $operands) |
||||
114 | { |
||||
115 | if ($operator === 'NOT' && count($operands) !== 1) { |
||||
116 | throw new InvalidParamException("Operator '$operator' requires exactly one operand."); |
||||
0 ignored issues
–
show
Deprecated Code
introduced
by
Loading history...
|
|||||
117 | } |
||||
118 | |||||
119 | $parts = []; |
||||
120 | foreach ($operands as $operand) { |
||||
121 | if (is_array($operand)) { |
||||
122 | $parts[] = $this->build($operand); |
||||
123 | } elseif ($operand !== '') { |
||||
124 | $parts[] = $operand; |
||||
125 | } |
||||
126 | } |
||||
127 | |||||
128 | return empty($parts) ? '' : '(' . $this->operator[$operator] . implode('', $parts) . ')'; |
||||
129 | } |
||||
130 | |||||
131 | /** |
||||
132 | * Creates an filter expressions with the `IN` operator. |
||||
133 | * @param string $operator the operator to use (e.g. `IN` or `NOT IN`) |
||||
134 | * @param array $operands the first operand is the column name. If it is an array |
||||
135 | * a composite IN condition will be generated. |
||||
136 | * The second operand is an array of values that column value should be among. |
||||
137 | * If it is an empty array the generated expression will be a `false` value if |
||||
138 | * operator is `IN` and empty if operator is `NOT IN`. |
||||
139 | * @return string the generated SQL expression |
||||
140 | * @throws Exception if wrong number of operands have been given. |
||||
141 | */ |
||||
142 | public function buildInCondition($operator, $operands) |
||||
143 | { |
||||
144 | if (!isset($operands[0], $operands[1])) { |
||||
145 | throw new InvalidParamException("Operator '$operator' requires two operands."); |
||||
0 ignored issues
–
show
The class
yii\base\InvalidParamException has been deprecated: since 2.0.14. Use [[InvalidArgumentException]] instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
146 | } |
||||
147 | |||||
148 | list($attribute, $values) = $operands; |
||||
149 | if (!is_array($values)) { |
||||
150 | $values = [$values]; |
||||
151 | } |
||||
152 | |||||
153 | $parts = []; |
||||
154 | foreach ($values as $value) { |
||||
155 | if (is_array($value)) { |
||||
156 | $value = isset($value[$attribute]) ? $value[$attribute] : null; |
||||
157 | } |
||||
158 | if ($value === null) { |
||||
159 | $parts[] = "(!$attribute=*)"; |
||||
160 | } else { |
||||
161 | $parts[] = "($attribute=$value)"; |
||||
162 | } |
||||
163 | } |
||||
164 | |||||
165 | if (empty($parts)) { |
||||
166 | return ''; |
||||
167 | } elseif ($operator === 'NOT IN') { |
||||
168 | return '(!' . implode('', $parts) . ')'; |
||||
169 | } |
||||
170 | return count($parts) === 1 ? $parts[0] : '(|' . implode('', $parts) . ')'; |
||||
171 | } |
||||
172 | |||||
173 | /** |
||||
174 | * Creates an LDAP filter expressions with the `LIKE` operator. |
||||
175 | * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) |
||||
176 | * @param array $operands an array of two or three operands |
||||
177 | * |
||||
178 | * - The first operand is the column name. |
||||
179 | * - The second operand is a single value or an array of values that column value |
||||
180 | * should be compared with. If it is an empty array the generated expression will |
||||
181 | * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator |
||||
182 | * is `NOT LIKE` or `OR NOT LIKE`. |
||||
183 | * - An optional third operand can also be provided to specify how to escape special characters |
||||
184 | * in the value(s). The operand should be an array of mappings from the special characters to their |
||||
185 | * escaped counterparts. If this operand is not provided, a default escape mapping will be used. |
||||
186 | * You may use `false` or an empty array to indicate the values are already escaped and no escape |
||||
187 | * should be applied. Note that when using an escape mapping (or the third operand is not provided), |
||||
188 | * the values will be automatically enclosed within a pair of percentage characters. |
||||
189 | * @return string the generated LDAP filter expression. |
||||
190 | * @throws InvalidParamException if wrong number of operands have been given. |
||||
191 | */ |
||||
192 | public function buildLikeCondition($operator, $operands) |
||||
193 | { |
||||
194 | if (!isset($operands[0], $operands[1])) { |
||||
195 | throw new InvalidParamException("Operator '$operator' requires two operands."); |
||||
0 ignored issues
–
show
The class
yii\base\InvalidParamException has been deprecated: since 2.0.14. Use [[InvalidArgumentException]] instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
196 | } |
||||
197 | |||||
198 | unset($operands[2]); |
||||
199 | |||||
200 | if (!preg_match('/^(OR|)(((NOT|))LIKE)/', $operator, $matches)) { |
||||
201 | throw new InvalidParamException("Invalid operator '$operator'."); |
||||
0 ignored issues
–
show
The class
yii\base\InvalidParamException has been deprecated: since 2.0.14. Use [[InvalidArgumentException]] instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
202 | } |
||||
203 | $andor = (!empty($matches[1]) ? $matches[1] : 'AND'); |
||||
204 | $not = !empty($matches[3]); |
||||
205 | |||||
206 | list($attribute, $values) = $operands; |
||||
207 | if (!is_array($values)) { |
||||
208 | $values = [$values]; |
||||
209 | } |
||||
210 | if (empty($values)) { |
||||
211 | return ''; |
||||
212 | } |
||||
213 | |||||
214 | $parts = []; |
||||
215 | foreach ($values as $value) { |
||||
216 | $parts[] = ($not ? '(!' : '') . '(' . $attribute . '=*' . $value . '*)' . ($not ? ')' : ''); |
||||
217 | } |
||||
218 | |||||
219 | return '(' . $this->operator[$andor] . implode($parts) . ')'; |
||||
220 | } |
||||
221 | |||||
222 | /** |
||||
223 | * Creates an LDAP filter expressions like `(attribute operator value)`. |
||||
224 | * @param string $operator the operator to use. A valid list could be used e.g. `=`, `>=`, `<=`, `~<`. |
||||
225 | * @param array $operands contains two column names. |
||||
226 | * @return string the generated LDAP filter expression. |
||||
227 | * @throws InvalidParamException if wrong number of operands have been given. |
||||
228 | */ |
||||
229 | public function buildSimpleCondition($operator, $operands) |
||||
230 | { |
||||
231 | if (count($operands) !== 2) { |
||||
232 | throw new InvalidParamException("Operator '$operator' requires two operands."); |
||||
0 ignored issues
–
show
The class
yii\base\InvalidParamException has been deprecated: since 2.0.14. Use [[InvalidArgumentException]] instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
233 | } |
||||
234 | |||||
235 | list($attribute, $value) = $operands; |
||||
236 | |||||
237 | if ($value === null) { |
||||
238 | return "(!($attribute = *))"; |
||||
239 | } else { |
||||
240 | return "($attribute $operator $value)"; |
||||
241 | } |
||||
242 | } |
||||
243 | |||||
244 | } |
||||
245 |