Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * This file contains the class XML_Query2XML_Driver_LDAP.
4
 *
5
 * PHP version 5
6
 *
7
 * @category  XML
8
 * @package   XML_Query2XML
9
 * @author    Lukas Feiler <lukas.feiler@lukasfeiler.com>
10
 * @copyright 2007 Lukas Feiler
11
 * @license   http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
12
 * @version   CVS: $Id: LDAP.php 259451 2008-05-09 20:54:26Z lukasfeiler $
13
 * @link      http://pear.php.net/package/XML_Query2XML
14
 */
15
 
16
/**
17
 * XML_Query2XML_Driver_LDAP extends XML_Query2XML_Driver.
18
 */
19
require_once 'XML/Query2XML.php';
20
 
21
/**
22
 * XML_Query2XML_Driver_LDAP uses Net_LDAP.
23
 */
24
require_once 'Net/LDAP.php';
25
 
26
/**
27
 * Net_LDAP_Util is required for its escape_filter_value() method.
28
 */
29
require_once 'Net/LDAP/Util.php';
30
 
31
/**
32
 * PEAR is required for its isError() method.
33
 */
34
require_once 'PEAR.php';
35
 
36
/**
37
 * Driver for Net_LDAP.
38
 *
39
 * usage:
40
 * <code>
41
 * $driver = XML_Query2XML_Driver::factory(new Net_LDAP(...));
42
 * </code>
43
 *
44
 * This LDAP driver provides three features:
45
 * - prepare & execute like usage of placeholders in "base" and "filter"
46
 * - handling missing attributes
47
 *   in LDAP an entity does not have to use all available attributes,
48
 *   while XML_Query2XML expects every record to have the same columns;
49
 *   this driver solves the problem by setting all missing columns to null.
50
 * - handling multi-value attributes
51
 *   XML_Query2XML expects every record to be a one-dimensional associative
52
 *   array. In order to achieve this result this driver creates as many
53
 *   records for each LDAP entry as are necassary to accomodate all values
54
 *   of an attribute.
55
 *
56
 * @category  XML
57
 * @package   XML_Query2XML
58
 * @author    Lukas Feiler <lukas.feiler@lukasfeiler.com>
59
 * @copyright 2006 Lukas Feiler
60
 * @license   http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
61
 * @version   Release: 1.7.2
62
 * @link      http://pear.php.net/package/XML_Query2XML
63
 * @since     Release 1.6.0RC1
64
 */
65
class XML_Query2XML_Driver_LDAP extends XML_Query2XML_Driver
66
{
67
    /**
68
     * In instance of Net_LDAP
69
     * @var Net_LDAP
70
     */
71
    private $_ldap = null;
72
 
73
    /**
74
     * Constructor
75
     *
76
     * @param Net_LDAP $ldap An instance of PEAR Net_LDAP.
77
     */
78
    public function __construct(Net_LDAP $ldap)
79
    {
80
        $this->_ldap = $ldap;
81
    }
82
 
83
    /**
84
     * Pre-processes LDAP query specifications.
85
     *
86
     * @param array  &$query     An array optionally containing the elements
87
     *                           'base', 'filter', 'options' and 'data'.
88
     * @param string $configPath The config path; used for exception messages.
89
     *
90
     * @return string A string representation of $query
91
     */
92
    public function preprocessQuery(&$query, $configPath)
93
    {
94
        if (!is_array($query)) {
95
            /*
96
             * unit test: XML_Query2XML_Driver_LDAP-preprocessQuery/
97
             *  throwConfigException_queryNotAnArray.phpt
98
             */
99
            throw new XML_Query2XML_ConfigException(
100
                $configPath . ': array expected, ' . gettype($query) . ' given.'
101
            );
102
        }
103
        $queryStatement = 'basedn:';
104
        if (isset($query['base'])) {
105
            $queryStatement .= $query['base'];
106
        } else {
107
            $queryStatement .= 'default';
108
        }
109
        if (isset($query['filter'])) {
110
            if (class_exists('Net_LDAP_Filter') &&
111
                $query['filter'] instanceof Net_LDAP_Filter
112
            ) {
113
                $queryStatement .= '; filter:' . $query['filter']->asString();
114
            } else {
115
                $queryStatement .= '; filter:' . $query['filter'];
116
            }
117
        }
118
        if (isset($query['options'])) {
119
            $queryStatement .= '; options:' . print_r($query['options'], 1);
120
        }
121
        return $queryStatement;
122
    }
123
 
124
    /**
125
     * Execute a LDAP query stement and fetch all results.
126
     *
127
     * @param mixed  $query      The SQL query as a string or an array.
128
     * @param string $configPath The config path; used for exception messages.
129
     *
130
     * @return array An array of records.
131
     * @throws XML_Query2XML_LDAPException If Net_LDAP::search() returns an error.
132
     * @see XML_Query2XML_Driver::getAllRecords()
133
     */
134
    public function getAllRecords($query, $configPath)
135
    {
136
        $base    = null;
137
        $filter  = null;
138
        $options = array();
139
        if (isset($query['base'])) {
140
            $base = $query['base'];
141
        }
142
        if (isset($query['filter'])) {
143
            $filter = $query['filter'];
144
        }
145
        if (isset($query['options'])) {
146
            $options = $query['options'];
147
        }
148
 
149
        if (isset($options['query2xml_placeholder'])) {
150
            $placeholder = $options['query2xml_placeholder'];
151
        } else {
152
            $placeholder = '?';
153
        }
154
        unset($options['query2xml_placeholder']);
155
 
156
        if (isset($query['data']) && is_array($query['data'])) {
157
            $data = Net_LDAP_Util::escape_filter_value($query['data']);
158
            $base = self::_replacePlaceholders($base, $data, $placeholder);
159
            if (is_string($filter)) {
160
                $filter = self::_replacePlaceholders($filter, $data, $placeholder);
161
            }
162
        }
163
        $search = $this->_ldap->search($base, $filter, $options);
164
 
165
        if (PEAR::isError($search)) {
166
            /*
167
             * unit test: getXML/throwLDAPException_queryError.phpt
168
             */
169
            throw new XML_Query2XML_LDAPException(
170
                $configPath . ': Could not run LDAP search query: '
171
                . $search->toString()
172
            );
173
        }
174
 
175
        $records = array();
176
        $entries = $search->entries();
177
        foreach ($entries as $key => $entry) {
178
            $records[] = $entry->getValues();
179
        }
180
        $search->done();
181
 
182
        $records = self::_processMultiValueAttributes($records);
183
 
184
        // set missing attriubtes to null
185
        if (isset($options['attributes']) && is_array($options['attributes'])) {
186
            foreach ($options['attributes'] as $attribute) {
187
                for ($i = 0; $i < count($records); $i++) {
188
                    if (!array_key_exists($attribute, $records[$i])) {
189
                        $records[$i][$attribute] = null;
190
                    }
191
                }
192
            }
193
        }
194
        return $records;
195
    }
196
 
197
    /**
198
     * Creates multiple records for each entry that has mult-value attributes.
199
     * XML_Query2XML can only handle records represented by a one-dimensional
200
     * associative array. An entry like
201
     * <pre>
202
     * dn: cn=John Doe,ou=people,dc=example,dc=com
203
     * cn: John Doe
204
     * mail: john.doe@example.com
205
     * mail: jdoe@example.com
206
     * mail: jd@example.com
207
     * mobile: 555-666-777
208
     * mobile: 666-777-888
209
     * </pre>
210
     * therefore has to be converted into multiple one-dimensional associative
211
     * arrays (i.e. records):
212
     * <pre>
213
     * cn        mail                  mobile
214
     * -------------------------------------------------------
215
     * John Doe  john.doe@example.com  555-666-777
216
     * John Doe  jdoe@example.com      666-777-888
217
     * John Doe  jd@example.com        555-666-777
218
     * </pre>
219
     * Note that no cartasian product of the mail-values and the mobile-values
220
     * is produced. The number of records returned is equal to the number
221
     * values assigned to the attribute that has the most values (here
222
     * it's the mail attribute that has 3 values). To make sure that every
223
     * record has valid values for all attributes/columns, we start with
224
     * the first value after reaching the last one (e.g. the last record
225
     * for jd@example.com has a mobile of 555-666-777).
226
     *
227
     * @param array $entries A multi-dimensional associative array.
228
     *
229
     * @return void
230
     */
231
    private static function _processMultiValueAttributes($entries)
232
    {
233
        $records = array();
234
        foreach ($entries as $entry) {
235
            $multiValueAttributes = array();
236
 
237
            // will hold the name of the attribute with the most values
238
            $maxValuesAttribute = null;
239
            $maxValues          = 0;
240
 
241
            // loop over all attributes
242
            foreach ($entry as $attributeName => $attribute) {
243
                if (is_array($attribute)) {
244
                    $multiValueAttributes[$attributeName] = array($attribute, 0);
245
                    if ($maxValues < count($attribute)) {
246
                        $maxValues          = count($attribute);
247
                        $maxValuesAttribute = $attributeName;
248
                    }
249
                    $multiValueAttributesMap[$attributeName] = count($attribute);
250
                }
251
            }
252
 
253
            if (count($multiValueAttributes) > 0) {
254
                /*
255
                 * $multiValueAttributes is something like:
256
                 * array(
257
                 *   ['email'] => array(
258
                 *     array(
259
                 *       'john.doe@example.com'
260
                 *     ),
261
                 *     0  // index used to keep track of where we are
262
                 *   ['telephoneNumber'] => array(
263
                 *     array(
264
                 *       '555-111-222',
265
                 *       '555-222-333'
266
                 *     ),
267
                 *     0  // index used to keep track of where we are
268
                 *   )
269
                 * )
270
                 */
271
                $combinations = array();
272
 
273
                $maxValuesAttributeValues =
274
                    $multiValueAttributes[$maxValuesAttribute][0];
275
                unset($multiValueAttributes[$maxValuesAttribute]);
276
 
277
                foreach ($maxValuesAttributeValues as $value) {
278
                    $combination                      = array();
279
                    $combination[$maxValuesAttribute] = $value;
280
 
281
                    /*
282
                     * Get the next value for each multi-value attribute.
283
                     * When the last value has been reached start again at
284
                     * the first one.
285
                     */
286
                    foreach (array_keys($multiValueAttributes) as $attributeName) {
287
                        $values =& $multiValueAttributes[$attributeName][0];
288
                        $index  =& $multiValueAttributes[$attributeName][1];
289
                        $count  =& count($values);
290
 
291
                        if ($index == $count) {
292
                            $index = 0;
293
                        }
294
                        $combination[$attributeName] = $values[$index++];
295
                    }
296
                    $combinations[] = $combination;
297
                }
298
                foreach ($combinations as $combination) {
299
                    $records[] = array_merge($entry, $combination);
300
                }
301
            } else {
302
                $records[] = $entry;
303
            }
304
        }
305
        return $records;
306
    }
307
 
308
    /**
309
     * Replaces all placeholder strings (e.g. '?') with replacement strings.
310
     *
311
     * @param string $string        The string in which to replace the placeholder
312
     *                              strings.
313
     * @param array  &$replacements An array of replacement strings.
314
     * @param string $placeholder   The placeholder string.
315
     *
316
     * @return string The modified version of $string.
317
     */
318
    private static function _replacePlaceholders($string,
319
                                                 &$replacements,
320
                                                 $placeholder)
321
    {
322
        while (($pos = strpos($string, $placeholder)) !== false) {
323
            if (count($replacements) > 0) {
324
                $string = substr($string, 0, $pos) .
325
                          array_shift($replacements) .
326
                          substr($string, $pos+strlen($placeholder));
327
            } else {
328
                break;
329
            }
330
        }
331
        return $string;
332
    }
333
}
334
 
335
/**
336
 * Exception for LDAP errors
337
 *
338
 * @category XML
339
 * @package  XML_Query2XML
340
 * @author   Lukas Feiler <lukas.feiler@lukasfeiler.com>
341
 * @license  http://www.gnu.org/copyleft/lesser.html  LGPL Version 2.1
342
 * @link     http://pear.php.net/package/XML_Query2XML
343
 */
344
class XML_Query2XML_LDAPException extends XML_Query2XML_DriverException
345
{
346
    /**
347
     * Constructor
348
     *
349
     * @param string $message The error message.
350
     */
351
    public function __construct($message)
352
    {
353
        parent::__construct($message);
354
    }
355
}
356
?>