3
* Horde_Data implementation for LDAP Data Interchange Format (LDIF).
5
* Copyright 2007-2013 Horde LLC (http://www.horde.org/)
7
* See the enclosed file LICENSE for license information (ASL). If you
8
* did not receive this file, see http://www.horde.org/licenses/apache.
10
* @author Rita Selsky <ritaselsky@gmail.com>
13
class Turba_Data_Ldif extends Horde_Data_Base
15
protected $_extension = 'ldif';
17
protected $_contentType = 'text/ldif';
20
* Useful Mozilla address book attribute names.
24
protected $_mozillaAttr = array(
25
'cn', 'givenName', 'sn', 'mail', 'mozillaNickname',
26
'homeStreet', 'mozillaHomeStreet2', 'mozillaHomeLocalityName',
27
'mozillaHomeState', 'mozillaHomePostalCode',
28
'mozillaHomeCountryName', 'street',
29
'mozillaWorkStreet2', 'l', 'st', 'postalCode',
30
'c', 'homePhone', 'telephoneNumber', 'mobile',
31
'fax', 'title', 'company', 'description', 'mozillaWorkUrl',
32
'department', 'mozillaNickname'
36
* Useful Turba address book attribute names.
40
protected $_turbaAttr = array(
41
'name', 'firstname', 'lastname', 'email', 'alias',
42
'homeAddress', 'homeStreet', 'homeCity',
43
'homeProvince', 'homePostalCode', 'homeCountry',
44
'workAddress', 'workStreet', 'workCity', 'workProvince',
45
'workPostalCode', 'workCountry',
46
'homePhone', 'workPhone', 'cellPhone',
47
'fax', 'title', 'company', 'notes', 'website',
48
'department', 'nickname'
52
* Turba address book attribute names and the corresponding Mozilla name.
56
protected $_turbaMozillaMap = array(
58
'firstname' => 'givenName',
61
'alias' => 'mozillaNickname',
62
'homePhone' => 'homePhone',
63
'workPhone' => 'telephoneNumber',
64
'cellPhone' => 'mobile',
67
'company' => 'company',
68
'notes' => 'description',
69
'homeAddress' => 'homeStreet',
70
'homeStreet' => 'mozillaHomeStreet2',
71
'homeCity' => 'mozillaHomeLocalityName',
72
'homeProvince' => 'mozillaHomeState',
73
'homePostalCode' => 'mozillaHomePostalCode',
74
'homeCountry' => 'mozillaHomeCountryName',
75
'workAddress' => 'street',
76
'workStreet' => 'mozillaWorkStreet2',
78
'workProvince' => 'st',
79
'workPostalCode' => 'postalCode',
81
'website' => 'mozillaWorkUrl',
82
'department' => 'department',
83
'nickname' => 'mozillaNickname'
86
public function importData($contents, $header = false)
89
$records = preg_split('/(\r?\n){2}/', $contents);
90
foreach ($records as $record) {
91
if (trim($record) == '') {
92
/* Ignore empty records */
95
/* one key:value pair per line */
96
$lines = preg_split('/\r?\n/', $record);
98
foreach ($lines as $line) {
99
// [0] = key, [1] = delimiter, [2] = value
100
$res = preg_split('/(:[:<]?) */', $line, 2, PREG_SPLIT_DELIM_CAPTURE);
101
if ((count($res) == 3) &&
102
in_array($res[0], $this->_mozillaAttr)) {
103
$hash[$res[0]] = ($res[1] == '::')
104
? base64_decode($res[2])
115
* Builds a LDIF file from a given data structure and triggers its download.
116
* It DOES NOT exit the current script but only outputs the correct headers
119
* @param string $filename The name of the file to be downloaded.
120
* @param array $data A two-dimensional array containing the data
122
* @param boolean $header If true, the rows of $data are associative
123
* arrays with field names as their keys.
125
public function exportFile($filename, $data, $header = false)
127
$export = $this->exportData($data, $header);
128
$GLOBALS['browser']->downloadHeaders($filename, 'text/ldif', false, strlen($export));
133
* Builds a LDIF file from a given data structure and returns it as a
136
* @param array $data A two-dimensional array containing the data set.
137
* @param boolean $header If true, the rows of $data are associative
138
* arrays with field names as their keys.
140
* @return string The LDIF data.
142
public function exportData($data, $header = false)
144
if (!is_array($data) || !count($data)) {
148
$mozillaTurbaMap = array_flip($this->_turbaMozillaMap) ;
149
foreach ($data as $row) {
151
foreach ($this->_mozillaAttr as $value) {
152
if (isset($row[$mozillaTurbaMap[$value]])) {
153
// Base64 encode each value as necessary and store it.
154
// Store cn and mail separately for use in record dn
155
if (!$this->_is_safe_string($row[$mozillaTurbaMap[$value]])) {
156
$recordData .= $value . ':: ' . base64_encode($row[$mozillaTurbaMap[$value]]) . "\n";
158
$recordData .= $value . ': ' . $row[$mozillaTurbaMap[$value]] . "\n";
163
$dn = 'cn=' . $row[$mozillaTurbaMap['cn']] . ',mail=' . $row[$mozillaTurbaMap['mail']];
164
if (!$this->_is_safe_string($dn)) {
165
$export .= 'dn:: ' . base64_encode($dn) . "\n";
167
$export .= 'dn: ' . $dn . "\n";
170
$export .= "objectclass: top\n"
171
. "objectclass: person\n"
172
. "objectclass: organizationalPerson\n"
173
. "objectclass: inetOrgPerson\n"
174
. "objectclass: mozillaAbPersonAlpha\n"
175
. $recordData . "modifytimestamp: 0Z\n\n";
182
* Takes all necessary actions for the given import step, parameters and
183
* form values and returns the next necessary step.
185
* @param integer $action The current step. One of the IMPORT_* constants.
186
* @param array $param An associative array containing needed
187
* parameters for the current step.
189
* @return mixed Either the next step as an integer constant or imported
190
* data set after the final step.
191
* @throws Horde_Data_Exception
193
public function nextStep($action, $param = array())
196
case Horde_Data::IMPORT_FILE:
197
parent::nextStep($action, $param);
199
$f_data = $this->importFile($_FILES['import_file']['tmp_name']);
202
foreach ($f_data as $record) {
203
$turbaHash = array();
204
foreach ($this->_turbaAttr as $value) {
207
// These are the keys we're interested in.
208
$keys = array('homeStreet', 'mozillaHomeStreet2',
209
'mozillaHomeLocalityName', 'mozillaHomeState',
210
'mozillaHomePostalCode', 'mozillaHomeCountryName');
212
// Grab all of them that exist in $record.
213
$values = array_intersect_key($record, array_flip($keys));
215
// Special handling for State if both State
216
// and Locality Name are set.
217
if (isset($values['mozillaHomeLocalityName'])
218
&& isset($values['mozillaHomeState'])) {
219
$values['mozillaHomeLocalityName'] .= ', ' . $values['mozillaHomeState'];
220
unset($values['mozillaHomeState']);
224
$turbaHash[$value] = implode("\n", $values);
229
// These are the keys we're interested in.
230
$keys = array('street', 'mozillaWorkStreet2', 'l',
231
'st', 'postalCode', 'c');
233
// Grab all of them that exist in $record.
234
$values = array_intersect_key($record, array_flip($keys));
236
// Special handling for "st" if both "st" and
238
if (isset($values['l']) && isset($values['st'])) {
239
$values['l'] .= ', ' . $values['st'];
240
unset($values['st']);
244
$turbaHash[$value] = implode("\n", $values);
249
if (isset($record[$this->_turbaMozillaMap[$value]])) {
250
$turbaHash[$value] = $record[$this->_turbaMozillaMap[$value]];
256
$data[] = $turbaHash;
259
$this->storage->set('data', null);
263
return parent::nextStep($action, $param);
268
* Checks if a string is safe according to RFC 2849, or if it needs to be
271
* @param string $str The string to check.
273
* @return boolean True if the string is safe.
275
protected function _is_safe_string($str)
277
/* SAFE-CHAR = %x01-09 / %x0B-0C / %x0E-7F
278
* ; any value <= 127 decimal except NUL, LF,
281
* SAFE-INIT-CHAR = %x01-09 / %x0B-0C / %x0E-1F /
282
* %x21-39 / %x3B / %x3D-7F
283
* ; any value <= 127 except NUL, LF, CR,
284
* ; SPACE, colon (":", ASCII 58 decimal)
285
* ; and less-than ("<" , ASCII 60 decimal) */
289
if ($str[0] == ' ' || $str[0] == ':' || $str[0] == '<') {
292
for ($i = 0; $i < strlen($str); ++$i) {
293
if (ord($str[$i]) > 127 || $str[$i] == NULL || $str[$i] == "\n" ||