Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Utilities often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Utilities, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | class Utilities |
||
17 | { |
||
18 | /** |
||
19 | * Converts a DN string into an array of RDNs. |
||
20 | * |
||
21 | * This will also decode hex characters into their true |
||
22 | * UTF-8 representation embedded inside the DN as well. |
||
23 | * |
||
24 | * @param string $dn |
||
25 | * @param bool $removeAttributePrefixes |
||
26 | * |
||
27 | * @return array |
||
28 | */ |
||
29 | public static function explodeDn($dn, $removeAttributePrefixes = true) |
||
30 | { |
||
31 | $dn = ldap_explode_dn($dn, ($removeAttributePrefixes ? 1 : 0)); |
||
32 | |||
33 | if (is_array($dn) && array_key_exists('count', $dn)) { |
||
34 | foreach ($dn as $rdn => $value) { |
||
35 | $dn[$rdn] = self::unescape($value); |
||
36 | } |
||
37 | } |
||
38 | |||
39 | return $dn; |
||
40 | } |
||
41 | |||
42 | /** |
||
43 | * Returns true / false if the current |
||
44 | * PHP install supports escaping values. |
||
45 | * |
||
46 | * @return bool |
||
47 | */ |
||
48 | public static function isEscapingSupported() |
||
49 | { |
||
50 | return function_exists('ldap_escape'); |
||
51 | } |
||
52 | |||
53 | /** |
||
54 | * Returns an escaped string for use in an LDAP filter. |
||
55 | * |
||
56 | * @param string $value |
||
57 | * @param string $ignore |
||
58 | * @param $flags |
||
59 | * |
||
60 | * @return string |
||
61 | */ |
||
62 | public static function escape($value, $ignore = '', $flags = 0) |
||
63 | { |
||
64 | if (!static::isEscapingSupported()) { |
||
65 | return static::escapeManual($value, $ignore, $flags); |
||
66 | } |
||
67 | |||
68 | return ldap_escape($value, $ignore, $flags); |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * Escapes the inserted value for LDAP. |
||
73 | * |
||
74 | * @param string $value |
||
75 | * @param string $ignore |
||
76 | * @param int $flags |
||
77 | * |
||
78 | * @return string |
||
79 | */ |
||
80 | protected static function escapeManual($value, $ignore = '', $flags = 0) |
||
81 | { |
||
82 | // If a flag was supplied, we'll send the value off |
||
83 | // to be escaped using the PHP flag values |
||
84 | // and return the result. |
||
85 | if ($flags) { |
||
86 | return static::escapeManualWithFlags($value, $ignore, $flags); |
||
87 | } |
||
88 | |||
89 | // Convert ignore string into an array. |
||
90 | $ignores = static::ignoreStrToArray($ignore); |
||
91 | |||
92 | // Convert the value to a hex string. |
||
93 | $hex = bin2hex($value); |
||
94 | |||
95 | // Separate the string, with the hex length of 2, and |
||
96 | // place a backslash on the end of each section. |
||
97 | $value = chunk_split($hex, 2, '\\'); |
||
98 | |||
99 | // We'll append a backslash at the front of the string |
||
100 | // and remove the ending backslash of the string. |
||
101 | $value = '\\'.substr($value, 0, -1); |
||
102 | |||
103 | // Go through each character to ignore. |
||
104 | foreach ($ignores as $charToIgnore) { |
||
105 | // Convert the character to ignore to a hex. |
||
106 | $hexed = bin2hex($charToIgnore); |
||
107 | |||
108 | // Replace the hexed variant with the original character. |
||
109 | $value = str_replace('\\'.$hexed, $charToIgnore, $value); |
||
110 | } |
||
111 | |||
112 | // Finally we can return the escaped value. |
||
113 | return $value; |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Escapes the inserted value with flags. Supplying either 1 |
||
118 | * or 2 into the flags parameter will escape only certain values. |
||
119 | * |
||
120 | * |
||
121 | * @param string $value |
||
122 | * @param string $ignore |
||
123 | * @param int $flags |
||
124 | * |
||
125 | * @return string |
||
126 | */ |
||
127 | protected static function escapeManualWithFlags($value, $ignore = '', $flags = 0) |
||
128 | { |
||
129 | // Convert ignore string into an array |
||
130 | $ignores = static::ignoreStrToArray($ignore); |
||
131 | |||
132 | // The escape characters for search filters |
||
133 | $escapeFilter = ['\\', '*', '(', ')']; |
||
134 | |||
135 | // The escape characters for distinguished names |
||
136 | $escapeDn = ['\\', ',', '=', '+', '<', '>', ';', '"', '#']; |
||
137 | |||
138 | switch ($flags) { |
||
139 | case 1: |
||
140 | // Int 1 equals to LDAP_ESCAPE_FILTER |
||
141 | $escapes = $escapeFilter; |
||
142 | break; |
||
143 | case 2: |
||
144 | // Int 2 equals to LDAP_ESCAPE_DN |
||
145 | $escapes = $escapeDn; |
||
146 | break; |
||
147 | case 3: |
||
148 | // If both LDAP_ESCAPE_FILTER and LDAP_ESCAPE_DN are used |
||
149 | $escapes = array_unique(array_merge($escapeDn, $escapeFilter)); |
||
150 | break; |
||
151 | default: |
||
152 | // We've been given an invalid flag, we'll escape everything to be safe. |
||
153 | return static::escapeManual($value, $ignore); |
||
154 | } |
||
155 | |||
156 | foreach ($escapes as $escape) { |
||
157 | // Make sure the escaped value isn't being ignored. |
||
158 | if (!in_array($escape, $ignores)) { |
||
159 | $hexed = static::escape($escape); |
||
160 | |||
161 | $value = str_replace($escape, $hexed, $value); |
||
162 | } |
||
163 | } |
||
164 | |||
165 | return $value; |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * Un-escapes a hexadecimal string into |
||
170 | * its original string representation. |
||
171 | * |
||
172 | * @param string $value |
||
173 | * |
||
174 | * @return string |
||
175 | */ |
||
176 | View Code Duplication | public static function unescape($value) |
|
|
|||
177 | { |
||
178 | $callback = function ($matches) { |
||
179 | return chr(hexdec($matches[1])); |
||
180 | }; |
||
181 | |||
182 | return preg_replace_callback('/\\\([0-9A-Fa-f]{2})/', $callback, $value); |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * Convert a binary SID to a string SID. |
||
187 | * |
||
188 | * @param string $binSid A Binary SID |
||
189 | * |
||
190 | * @return string |
||
191 | */ |
||
192 | public static function binarySidToString($binSid) |
||
193 | { |
||
194 | if (trim($binSid) == '' || is_null($binSid)) { |
||
195 | return; |
||
196 | } |
||
197 | |||
198 | $hex = bin2hex($binSid); |
||
199 | |||
200 | $rev = hexdec(substr($hex, 0, 2)); |
||
201 | |||
202 | $subCount = hexdec(substr($hex, 2, 2)); |
||
203 | |||
204 | $auth = hexdec(substr($hex, 4, 12)); |
||
205 | |||
206 | $result = "$rev-$auth"; |
||
207 | |||
208 | $subauth = []; |
||
209 | |||
210 | for ($x = 0; $x < $subCount; $x++) { |
||
211 | $subauth[$x] = hexdec(static::littleEndian(substr($hex, 16 + ($x * 8), 8))); |
||
212 | |||
213 | $result .= '-'.$subauth[$x]; |
||
214 | } |
||
215 | |||
216 | return 'S-'.$result; |
||
217 | } |
||
218 | |||
219 | /** |
||
220 | * Convert a binary GUID to a string GUID. |
||
221 | * |
||
222 | * @param string $binGuid |
||
223 | * |
||
224 | * @return string |
||
225 | */ |
||
226 | public static function binaryGuidToString($binGuid) |
||
227 | { |
||
228 | if (trim($binGuid) == '' || is_null($binGuid)) { |
||
229 | return; |
||
230 | } |
||
231 | |||
232 | $hex = unpack('H*hex', $binGuid)['hex']; |
||
233 | |||
234 | $hex1 = substr($hex, -26, 2).substr($hex, -28, 2).substr($hex, -30, 2).substr($hex, -32, 2); |
||
235 | $hex2 = substr($hex, -22, 2).substr($hex, -24, 2); |
||
236 | $hex3 = substr($hex, -18, 2).substr($hex, -20, 2); |
||
237 | $hex4 = substr($hex, -16, 4); |
||
238 | $hex5 = substr($hex, -12, 12); |
||
239 | |||
240 | $guid = sprintf('%s-%s-%s-%s-%s', $hex1, $hex2, $hex3, $hex4, $hex5); |
||
241 | |||
242 | return $guid; |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Converts a little-endian hex number to one that hexdec() can convert. |
||
247 | * |
||
248 | * @param string $hex A hex code |
||
249 | * |
||
250 | * @return string |
||
251 | */ |
||
252 | public static function littleEndian($hex) |
||
253 | { |
||
254 | $result = ''; |
||
255 | |||
256 | for ($x = strlen($hex) - 2; $x >= 0; $x = $x - 2) { |
||
257 | $result .= substr($hex, $x, 2); |
||
258 | } |
||
259 | |||
260 | return $result; |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Encode a password for transmission over LDAP. |
||
265 | * |
||
266 | * @param string $password The password to encode |
||
267 | * |
||
268 | * @return string |
||
269 | */ |
||
270 | public static function encodePassword($password) |
||
271 | { |
||
272 | return iconv('UTF-8', 'UTF-16LE', '"'.$password.'"'); |
||
273 | } |
||
274 | |||
275 | /** |
||
276 | * Round a Windows timestamp down to seconds and remove |
||
277 | * the seconds between 1601-01-01 and 1970-01-01. |
||
278 | * |
||
279 | * @param float $windowsTime |
||
280 | * |
||
281 | * @return float |
||
282 | */ |
||
283 | public static function convertWindowsTimeToUnixTime($windowsTime) |
||
284 | { |
||
285 | return round($windowsTime / 10000000) - 11644473600; |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * Convert a Unix timestamp to Windows timestamp. |
||
290 | * |
||
291 | * @param float $unixTime |
||
292 | * |
||
293 | * @return float |
||
294 | */ |
||
295 | public static function convertUnixTimeToWindowsTime($unixTime) |
||
296 | { |
||
297 | return ($unixTime + 11644473600) * 10000000; |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * Validates that the inserted string is an object SID. |
||
302 | * |
||
303 | * @param string $sid |
||
304 | * |
||
305 | * @return bool |
||
306 | */ |
||
307 | public static function isValidSid($sid) |
||
308 | { |
||
309 | preg_match("/S-1-5-21-\d+-\d+\-\d+\-\d+/", $sid, $matches); |
||
310 | |||
311 | if (count($matches) > 0) { |
||
312 | return true; |
||
313 | } |
||
314 | |||
315 | return false; |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Converts an ignore string into an array. |
||
320 | * |
||
321 | * @param string $ignore |
||
322 | * |
||
323 | * @return array |
||
324 | */ |
||
325 | protected static function ignoreStrToArray($ignore) |
||
326 | { |
||
327 | $ignore = trim($ignore); |
||
328 | |||
329 | if (!empty($ignore)) { |
||
330 | return str_split($ignore); |
||
331 | } |
||
332 | |||
333 | return []; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Regex to match a GUID. |
||
338 | */ |
||
339 | const MATCH_GUID = '/^([0-9a-fA-F]){8}(-([0-9a-fA-F]){4}){3}-([0-9a-fA-F]){12}$/'; |
||
340 | |||
341 | /** |
||
342 | * Regex to match a Windows SID. |
||
343 | */ |
||
344 | const MATCH_SID = '/^S-\d-(\d+-){1,14}\d+$/i'; |
||
345 | |||
346 | /** |
||
347 | * Regex to match an OID. |
||
348 | */ |
||
349 | const MATCH_OID = '/^[0-9]+(\.[0-9]+?)*?$/'; |
||
350 | |||
351 | /** |
||
352 | * Regex to match an attribute descriptor. |
||
353 | */ |
||
354 | const MATCH_DESCRIPTOR = '/^\pL[\pL\pN-]+$/iu'; |
||
355 | |||
356 | /** |
||
357 | * The prefix for a LDAP DNS SRV record. |
||
358 | */ |
||
359 | const SRV_PREFIX = '_ldap._tcp.'; |
||
360 | |||
361 | /** |
||
362 | * The mask to use when sanitizing arrays with LDAP password information. |
||
363 | */ |
||
364 | const MASK = '******'; |
||
365 | |||
366 | /** |
||
367 | * The attributes to mask in a batch/attribute array. |
||
368 | */ |
||
369 | const MASK_ATTRIBUTES = [ |
||
370 | 'unicodepwd', |
||
371 | 'userpassword', |
||
372 | ]; |
||
373 | |||
374 | /** |
||
375 | * Escape any special characters for LDAP to their hexadecimal representation. |
||
376 | * |
||
377 | * @param mixed $value The value to escape. |
||
378 | * @param null|string $ignore The characters to ignore. |
||
379 | * @param null|int $flags The context for the escaped string. LDAP_ESCAPE_FILTER or LDAP_ESCAPE_DN. |
||
380 | * @return string The escaped value. |
||
381 | */ |
||
382 | public static function escapeValue($value, $ignore = null, $flags = null) |
||
383 | { |
||
384 | // If this is a hexadecimal escaped string, then do not escape it. |
||
385 | $value = preg_match('/^(\\\[0-9a-fA-F]{2})+$/', (string) $value) ? $value : ldap_escape($value, $ignore, $flags); |
||
386 | |||
387 | // Per RFC 4514, leading/trailing spaces should be encoded in DNs, as well as carriage returns. |
||
388 | if ((int)$flags & LDAP_ESCAPE_DN) { |
||
389 | View Code Duplication | if (!empty($value) && $value[0] === ' ') { |
|
390 | $value = '\\20' . substr($value, 1); |
||
391 | } |
||
392 | View Code Duplication | if (!empty($value) && $value[strlen($value) - 1] === ' ') { |
|
393 | $value = substr($value, 0, -1) . '\\20'; |
||
394 | } |
||
395 | // Only carriage returns seem to be valid, not line feeds (per testing of AD anyway). |
||
396 | $value = str_replace("\r", '\0d', $value); |
||
397 | } |
||
398 | |||
399 | return $value; |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Un-escapes a value from its hexadecimal form back to its string representation. |
||
404 | * |
||
405 | * @param string $value |
||
406 | * @return string |
||
407 | */ |
||
408 | View Code Duplication | public static function unescapeValue($value) |
|
409 | { |
||
410 | $callback = function ($matches) { |
||
411 | return chr(hexdec($matches[1])); |
||
412 | }; |
||
413 | |||
414 | return preg_replace_callback('/\\\([0-9A-Fa-f]{2})/', $callback, $value); |
||
415 | } |
||
416 | |||
417 | /** |
||
418 | * Converts a string distinguished name into its separate pieces. |
||
419 | * |
||
420 | * @param string $dn |
||
421 | * @param int $withAttributes Set to 0 to get the attribute names along with the value. |
||
422 | * @return array |
||
423 | */ |
||
424 | public static function explodeDn($dn, $withAttributes = 1) |
||
438 | |||
439 | /** |
||
440 | * Given a DN as an array in ['cn=Name', 'ou=Employees', 'dc=example', 'dc=com'] form, return it as its string |
||
441 | * representation that is safe to pass back to a query or to save back to LDAP for a DN. |
||
442 | * |
||
443 | * @param array $dn |
||
444 | * @return string |
||
445 | */ |
||
446 | public static function implodeDn(array $dn) |
||
447 | { |
||
448 | foreach ($dn as $index => $piece) { |
||
449 | $values = explode('=', $piece, 2); |
||
450 | if (count($values) === 1) { |
||
451 | throw new InvalidArgumentException(sprintf('Unable to parse DN piece "%s".', $values[0])); |
||
452 | } |
||
453 | $dn[$index] = $values[0].'='.self::escapeValue($values[1], null, LDAP_ESCAPE_DN); |
||
458 | |||
459 | /** |
||
460 | * Encode a string for LDAP with a specific encoding type. |
||
461 | * |
||
462 | * @param string $value The value to encode. |
||
463 | * @param string $toEncoding The encoding type to use (ie. UTF-8) |
||
464 | * @return string The encoded value. |
||
465 | */ |
||
466 | public static function encode($value, $toEncoding) |
||
482 | |||
483 | /** |
||
484 | * Given a string, try to determine if it is a valid distinguished name for a LDAP object. This is a somewhat |
||
485 | * unsophisticated approach. A regex might be a better solution, but would probably be rather difficult to get |
||
486 | * right. |
||
487 | * |
||
488 | * @param string $dn |
||
489 | * @return bool |
||
490 | */ |
||
491 | public static function isValidLdapObjectDn($dn) |
||
495 | |||
496 | /** |
||
497 | * Determine whether a value is a valid attribute name or OID. The name should meet the format described in RFC 2252. |
||
498 | * However, the regex is fairly forgiving for each. |
||
499 | * |
||
500 | * @param string $value |
||
501 | * @return bool |
||
502 | */ |
||
503 | public static function isValidAttributeFormat($value) |
||
507 | |||
508 | /** |
||
509 | * Attempts to mask passwords in a LDAP batch array while keeping the rest intact. |
||
510 | * |
||
511 | * @param array $batch |
||
512 | * @return array |
||
513 | */ |
||
514 | public static function maskBatchArray(array $batch) |
||
528 | |||
529 | /** |
||
530 | * Attempts to mask password attribute values used in logging. |
||
531 | * |
||
532 | * @param array $attributes |
||
533 | * @return array |
||
534 | */ |
||
535 | public static function maskAttributeArray(array $attributes) |
||
545 | |||
546 | /** |
||
547 | * Get an array of all the LDAP servers for a domain by querying DNS. |
||
548 | * |
||
549 | * @param string $domain The domain name to query. |
||
550 | * @return string[] |
||
551 | */ |
||
552 | public static function getLdapServersForDomain($domain) |
||
558 | |||
559 | /** |
||
560 | * Given a full escaped DN return the RDN in escaped form. |
||
561 | * |
||
562 | * @param string $dn |
||
563 | * @return string |
||
564 | */ |
||
565 | public static function getRdnFromDn($dn) |
||
572 | |||
573 | /** |
||
574 | * Given an attribute, split it between its alias and attribute. This will return an array where the first value |
||
575 | * is the alias and the second is the attribute name. If there is no alias then the first value will be null. |
||
576 | * |
||
577 | * ie. list($alias, $attribute) = LdapUtilities::getAliasAndAttribute($attribute); |
||
578 | * |
||
579 | * @param string $attribute |
||
580 | * @return array |
||
581 | */ |
||
582 | public static function getAliasAndAttribute($attribute) |
||
594 | |||
595 | /** |
||
596 | * Looks up an array value in a case-insensitive way and return it how it appears in the array. |
||
597 | * |
||
598 | * @param string $needle |
||
599 | * @param array $haystack |
||
600 | * @return string |
||
601 | */ |
||
602 | public static function getValueCaseInsensitive($needle, array $haystack) |
||
613 | } |
||
614 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.