Completed
Push — develop ( ed6c74...ceecfe )
by Patrick
04:06
created

LDAPServer   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 284
Duplicated Lines 2.46 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 7
loc 284
rs 8.4
c 0
b 0
f 0
wmc 50
lcom 1
cbo 2

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A __destruct() 0 3 1
A __wakeup() 0 6 1
A getConnectString() 0 12 3
A connect() 0 17 3
A disconnect() 0 9 2
A bind() 0 26 5
A unbind() 0 8 2
A fixChildArray() 7 11 3
B fixObject() 0 25 6
A create() 0 11 2
A filterToString() 0 12 3
A searchResultToArray() 0 18 4
A read() 0 27 5
A count() 0 21 4
A update() 0 21 4
A delete() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like LDAPServer 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 LDAPServer, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Flipside\LDAP;
3
4
/**
5
 * function ldap_escape
6
 * @author Chris Wright
7
 * @version 2.0
8
 * @param string $subject The subject string
9
 * @param bool $dn Treat subject as a DN if TRUE
0 ignored issues
show
Bug introduced by
There is no parameter named $dn. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
10
 * @param string|array $ignore Set of characters to leave untouched
11
 * @return string The escaped string
12
 */
13
function ldap_escape($subject, $distinguishedName = false, $ignore = NULL)
14
{
15
    // The base array of characters to escape
16
    // Flip to keys for easy use of unset()
17
    $search = array_flip($distinguishedName ? array('\\', '+', '<', '>', ';', '"', '#') : array('\\', '*', '(', ')', "\x00"));
18
19
    // Process characters to ignore
20
    if(is_array($ignore))
21
    {
22
        $ignore = array_values($ignore);
23
    }
24
    for($char = 0; isset($ignore[$char]); $char++)
25
    {
26
        unset($search[$ignore[$char]]);
27
    }
28
29
    // Flip $search back to values and build $replace array
30
    $search = array_keys($search);
31
    $replace = array();
32
    foreach($search as $char)
33
    {
34
        $replace[] = sprintf('\\%02x', ord($char));
35
    }
36
37
    // Do the main replacement
38
    $result = str_replace($search, $replace, $subject);
39
40
    // Encode leading/trailing spaces in DN values
41
    if($distinguishedName)
42
    {
43
        $result = cleanupDN($result);
44
    }
45
46
    return $result;
47
}
48
49
function cleanupDN($distinguishedName)
50
{
51
    if($distinguishedName[0] == ' ')
52
    {
53
        $distinguishedName = '\\20'.substr($distinguishedName, 1);
54
    }
55
    if($distinguishedName[strlen($distinguishedName) - 1] == ' ')
56
    {
57
        $distinguishedName = substr($distinguishedName, 0, -1).'\\20';
58
    }
59
    return $distinguishedName;
60
}
61
62
class LDAPServer extends \Flipside\Singleton
63
{
64
    protected $ldapLink;
65
    protected $connect;
66
    protected $binder;
67
    public $user_base;
68
    public $group_base;
69
70
    protected function __construct()
71
    {
72
        $this->ldapLink = null;
73
        $this->binder = null;
74
    }
75
76
    public function __destruct()
77
    {
78
    }
79
80
    public function __wakeup()
81
    {
82
        $this->ldapLink = ldap_connect($this->connect);
83
        ldap_set_option($this->ldapLink, LDAP_OPT_PROTOCOL_VERSION, 3);
84
        ldap_set_option($this->ldapLink, LDAP_OPT_REFERRALS, false);
85
    }
86
87
    private function getConnectString($name, $proto = false)
88
    {
89
        if(strstr($name, ':') !== false)
90
        {
91
            return $name;
92
        }
93
        if($proto !== 'ldap')
94
        {
95
            return $proto.'://'.$name;
96
        }
97
        return $name;
98
    }
99
100
    public function connect($name, $proto = false)
101
    {
102
        $connectStr = $this->getConnectString($name, $proto);
103
        if($this->ldapLink !== null)
104
        {
105
            ldap_close($this->ldapLink);
106
        }
107
        $this->connect = $connectStr;
108
        $this->ldapLink = ldap_connect($this->connect);
109
        if($this->ldapLink === false)
110
        {
111
            $this->ldapLink = null;
112
            return false;
113
        }
114
        ldap_set_option($this->ldapLink, LDAP_OPT_PROTOCOL_VERSION, 3);
115
        return true;
116
    }
117
118
    public function disconnect()
119
    {
120
        if($this->ldapLink !== null)
121
        {
122
            ldap_close($this->ldapLink);
123
            $this->ldapLink = null;
124
        }
125
        $this->connect = false;
126
    }
127
128
    /**
129
     * Bind (login0 to the LDAP Server
130
     *
131
     * @param string $commonName The common name to bind with, null to bind anonymously
132
     * @param string $password The password to bind with, null to bind anonymously
133
     */
134
    public function bind($commonName = null, $password = null)
135
    {
136
        $res = false;
137
        if($this->ldapLink === null)
138
        {
139
            throw new \Exception('Not connected');
140
        }
141
        $this->binder = $commonName;
142
        if($commonName === null || $password === null)
143
        {
144
            return @ldap_bind($this->ldapLink);
145
        }
146
        try
147
        {
148
            $this->ldapLink = ldap_connect($this->connect);
149
            ldap_set_option($this->ldapLink, LDAP_OPT_PROTOCOL_VERSION, 3);
150
            $res = @ldap_bind($this->ldapLink, $commonName, $password);
151
        }
152
        catch(\Exception $ex)
153
        {
154
            $this->ldapLink = ldap_connect($this->connect);
155
            ldap_set_option($this->ldapLink, LDAP_OPT_PROTOCOL_VERSION, 3);
156
            $res = @ldap_bind($this->ldapLink, $commonName, $password);
157
        }
158
        return $res;
159
    }
160
161
    public function unbind()
162
    {
163
        if($this->ldapLink === null)
164
        {
165
            return true;
166
        }
167
        return @ldap_unbind($this->ldapLink);
168
    }
169
170
    private function fixChildArray(&$array, $key, &$entity)
171
    {
172
        $count = count($array);
173 View Code Duplication
        for($i = 0; $i < $count; $i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
174
        {
175
            if(isset($array[$i]))
176
            {
177
                $entity[$key][$i] = $array[$i];
178
            }
179
        }
180
    }
181
182
    private function fixObject($object, &$delete = false)
183
    {
184
        $entity = $object;
185
        if(!is_array($object))
186
        {
187
            $entity = $object->to_array();
188
        }
189
        unset($entity['dn']);
190
        $keys = array_keys($entity);
191
        $count = count($keys);
192
        for($i = 0; $i < $count; $i++)
193
        {
194
            if(is_array($entity[$keys[$i]]))
195
            {
196
                $this->fixChildArray($entity[$keys[$i]], $keys[$i], $entity);
197
                //unset($entity[$keys[$i]]);
198
            }
199
            else if($delete !== false && $entity[$keys[$i]] === null)
200
            {
201
                $delete[$keys[$i]] = array();
202
                unset($entity[$keys[$i]]);
203
            }
204
        }
205
        return $entity;
206
    }
207
208
    public function create($object)
209
    {
210
        $distinguishedName = ldap_escape($object['dn'], true);
211
        $entity = $this->fixObject($object);
212
        $ret = ldap_add($this->ldapLink, $distinguishedName, $entity);
213
        if($ret === false)
214
        {
215
            throw new \Exception('Failed to create object with dn='.$distinguishedName);
216
        }
217
        return $ret;
218
    }
219
220
    /**
221
     * Get the LDAP filter represented by the passed object
222
     *
223
     * @param boolean|string|\Data\Filter $filter The fiter to use
224
     *
225
     * @return string The filter in LDAP format
226
     */
227
    private function filterToString($filter)
228
    {
229
        if($filter === false)
230
        {
231
            return '(objectclass=*)';
232
        }
233
        if(is_string($filter))
234
        {
235
            return $filter;
236
        }
237
        return $filter->to_ldap_string();
238
    }
239
240
    private function searchResultToArray($searchResult)
241
    {
242
        if($searchResult === false)
243
        {
244
            return false;
245
        }
246
        $res = ldap_get_entries($this->ldapLink, $searchResult);
247
        if(is_array($res))
248
        {
249
            $ldap = $res;
250
            $res = array();
251
            for($i = 0; $i < $ldap['count']; $i++)
252
            {
253
                array_push($res, new LDAPObject($ldap[$i], $this));
0 ignored issues
show
Documentation introduced by
$this is of type this<Flipside\LDAP\LDAPServer>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
254
            }
255
        }
256
        return $res;
257
    }
258
259
    /**
260
     * Get data from the LDAP Server
261
     *
262
     * @param string $baseDN The distinguished name to start the search from
263
     * @param boolean|string|\Data\Filter $filter The fiter to use
264
     * @param boolean $single Read only the base DN
265
     * @param boolean|array $attributes The list of attributes to read
266
     *
267
     * @return boolean|array The results from the LDAP Server
268
     */
269
    public function read($baseDN, $filter = false, $single = false, $attributes = false)
270
    {
271
        $filterStr = $this->filterToString($filter);
272
        if($this->ldapLink === null)
273
        {
274
            throw new \Exception('Not connected');
275
        }
276
        try
277
        {
278
            if($single === true)
279
            {
280
                $searchResult = @ldap_read($this->ldapLink, $baseDN, $filterStr);
281
                return $this->searchResultToArray($searchResult);
282
            }
283
            if($attributes !== false)
284
            {
285
                $searchResult = @ldap_list($this->ldapLink, $baseDN, $filterStr, $attributes);
286
                return $this->searchResultToArray($searchResult);
287
            }
288
            $searchResult = @ldap_list($this->ldapLink, $baseDN, $filterStr);
289
            return $this->searchResultToArray($searchResult);
290
        }
291
        catch(\Exception $e)
292
        {
293
            throw new \Exception($e->getMessage().' '.$filterStr, $e->getCode(), $e);
294
        }
295
    }
296
297
    public function count($baseDN, $filter = false)
298
    {
299
        $filterStr = $this->filterToString($filter);
300
        if($this->ldapLink === null)
301
        {
302
            throw new \Exception('Not connected');
303
        }
304
        try
305
        {
306
            $searchResult = ldap_list($this->ldapLink, $baseDN, $filterStr, array('dn'));
307
        }
308
        catch(\Exception $e)
309
        {
310
            throw new \Exception($e->getMessage().' '.$filterStr, $e->getCode(), $e);
311
        }
312
        if($searchResult === false)
313
        {
314
            return 0;
315
        }
316
        return ldap_count_entries($this->ldapLink, $searchResult);
317
    }
318
319
    public function update($object)
320
    {
321
        $distinguishedName = ldap_escape($object['dn'], true);
322
        $delete = array();
323
        $entity = $this->fixObject($object, $delete);
0 ignored issues
show
Documentation introduced by
$delete is of type array, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
324
        $ret = false;
325
        if(!empty($entity))
326
        {
327
            $ret = @ldap_mod_replace($this->ldapLink, $distinguishedName, $entity);
328
            if($ret === false)
329
            {
330
                $string = 'Failed to update object with dn='.$distinguishedName.' ('.ldap_errno($this->ldapLink).':'.ldap_error($this->ldapLink).') '.print_r($entity, true);
331
                throw new \Exception($string);
332
            }
333
        }
334
        if(!empty($delete))
335
        {
336
            $ret = @ldap_mod_del($this->ldapLink, $distinguishedName, $delete);
337
        }
338
        return $ret;
339
    }
340
341
    public function delete($distinguishedName)
342
    {
343
        return ldap_delete($this->ldapLink, $distinguishedName);
344
    }
345
}
346
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
347