commit | author | age
|
383379
|
1 |
<?php |
AM |
2 |
|
|
3 |
/* |
|
4 |
+-----------------------------------------------------------------------+ |
|
5 |
| This file is part of the Roundcube Webmail client | |
|
6 |
| Copyright (C) 2008-2012, The Roundcube Dev Team | |
|
7 |
| | |
|
8 |
| Licensed under the GNU General Public License version 3 or | |
|
9 |
| any later version with exceptions for skins & plugins. | |
|
10 |
| See the README file for a full license statement. | |
|
11 |
| | |
|
12 |
| PURPOSE: | |
|
13 |
| CSV to vCard data conversion | |
|
14 |
+-----------------------------------------------------------------------+ |
|
15 |
| Author: Aleksander Machniak <alec@alec.pl> | |
|
16 |
+-----------------------------------------------------------------------+ |
|
17 |
*/ |
|
18 |
|
|
19 |
/** |
|
20 |
* CSV to vCard data converter |
|
21 |
* |
9ab346
|
22 |
* @package Framework |
AM |
23 |
* @subpackage Addressbook |
|
24 |
* @author Aleksander Machniak <alec@alec.pl> |
383379
|
25 |
*/ |
AM |
26 |
class rcube_csv2vcard |
|
27 |
{ |
|
28 |
/** |
|
29 |
* CSV to vCard fields mapping |
|
30 |
* |
|
31 |
* @var array |
|
32 |
*/ |
|
33 |
protected $csv2vcard_map = array( |
|
34 |
// MS Outlook 2010 |
|
35 |
'anniversary' => 'anniversary', |
|
36 |
'assistants_name' => 'assistant', |
|
37 |
'assistants_phone' => 'phone:assistant', |
|
38 |
'birthday' => 'birthday', |
|
39 |
'business_city' => 'locality:work', |
|
40 |
'business_countryregion' => 'country:work', |
|
41 |
'business_fax' => 'phone:work,fax', |
|
42 |
'business_phone' => 'phone:work', |
|
43 |
'business_phone_2' => 'phone:work2', |
|
44 |
'business_postal_code' => 'zipcode:work', |
|
45 |
'business_state' => 'region:work', |
|
46 |
'business_street' => 'street:work', |
|
47 |
//'business_street_2' => '', |
|
48 |
//'business_street_3' => '', |
|
49 |
'car_phone' => 'phone:car', |
|
50 |
'categories' => 'categories', |
|
51 |
//'children' => '', |
|
52 |
'company' => 'organization', |
|
53 |
//'company_main_phone' => '', |
|
54 |
'department' => 'department', |
|
55 |
//'email_2_address' => '', //@TODO |
c66b60
|
56 |
//'email_2_type' => '', |
383379
|
57 |
//'email_3_address' => '', //@TODO |
c66b60
|
58 |
//'email_3_type' => '', |
383379
|
59 |
'email_address' => 'email:main', |
c66b60
|
60 |
//'email_type' => '', |
383379
|
61 |
'first_name' => 'firstname', |
AM |
62 |
'gender' => 'gender', |
|
63 |
'home_city' => 'locality:home', |
|
64 |
'home_countryregion' => 'country:home', |
|
65 |
'home_fax' => 'phone:home,fax', |
|
66 |
'home_phone' => 'phone:home', |
|
67 |
'home_phone_2' => 'phone:home2', |
|
68 |
'home_postal_code' => 'zipcode:home', |
|
69 |
'home_state' => 'region:home', |
|
70 |
'home_street' => 'street:home', |
|
71 |
//'home_street_2' => '', |
|
72 |
//'home_street_3' => '', |
|
73 |
//'initials' => '', |
|
74 |
//'isdn' => '', |
|
75 |
'job_title' => 'jobtitle', |
|
76 |
//'keywords' => '', |
|
77 |
//'language' => '', |
|
78 |
'last_name' => 'surname', |
|
79 |
//'location' => '', |
|
80 |
'managers_name' => 'manager', |
|
81 |
'middle_name' => 'middlename', |
|
82 |
//'mileage' => '', |
|
83 |
'mobile_phone' => 'phone:cell', |
|
84 |
'notes' => 'notes', |
|
85 |
//'office_location' => '', |
|
86 |
'other_city' => 'locality:other', |
|
87 |
'other_countryregion' => 'country:other', |
|
88 |
'other_fax' => 'phone:other,fax', |
|
89 |
'other_phone' => 'phone:other', |
|
90 |
'other_postal_code' => 'zipcode:other', |
|
91 |
'other_state' => 'region:other', |
|
92 |
'other_street' => 'street:other', |
|
93 |
//'other_street_2' => '', |
|
94 |
//'other_street_3' => '', |
|
95 |
'pager' => 'phone:pager', |
|
96 |
'primary_phone' => 'phone:pref', |
|
97 |
//'profession' => '', |
|
98 |
//'radio_phone' => '', |
|
99 |
'spouse' => 'spouse', |
|
100 |
'suffix' => 'suffix', |
|
101 |
'title' => 'title', |
|
102 |
'web_page' => 'website:homepage', |
|
103 |
|
|
104 |
// Thunderbird |
|
105 |
'birth_day' => 'birthday-d', |
|
106 |
'birth_month' => 'birthday-m', |
|
107 |
'birth_year' => 'birthday-y', |
|
108 |
'display_name' => 'displayname', |
|
109 |
'fax_number' => 'phone:fax', |
|
110 |
'home_address' => 'street:home', |
|
111 |
//'home_address_2' => '', |
|
112 |
'home_country' => 'country:home', |
|
113 |
'home_zipcode' => 'zipcode:home', |
|
114 |
'mobile_number' => 'phone:cell', |
|
115 |
'nickname' => 'nickname', |
|
116 |
'organization' => 'organization', |
|
117 |
'pager_number' => 'phone:pager', |
|
118 |
'primary_email' => 'email:pref', |
|
119 |
'secondary_email' => 'email:other', |
|
120 |
'web_page_1' => 'website:homepage', |
|
121 |
'web_page_2' => 'website:other', |
|
122 |
'work_phone' => 'phone:work', |
|
123 |
'work_address' => 'street:work', |
|
124 |
//'work_address_2' => '', |
|
125 |
'work_country' => 'country:work', |
|
126 |
'work_zipcode' => 'zipcode:work', |
c59ef9
|
127 |
'last' => 'surname', |
AM |
128 |
'first' => 'firstname', |
|
129 |
'work_city' => 'locality:work', |
|
130 |
'work_state' => 'region:work', |
|
131 |
'home_city_short' => 'locality:home', |
|
132 |
'home_state_short' => 'region:home', |
609483
|
133 |
|
AM |
134 |
// Atmail |
|
135 |
'date_of_birth' => 'birthday', |
|
136 |
'email' => 'email:pref', |
|
137 |
'home_mobile' => 'phone:cell', |
|
138 |
'home_zip' => 'zipcode:home', |
|
139 |
'info' => 'notes', |
|
140 |
'user_photo' => 'photo', |
|
141 |
'url' => 'website:homepage', |
|
142 |
'work_company' => 'organization', |
|
143 |
'work_dept' => 'departament', |
|
144 |
'work_fax' => 'phone:work,fax', |
|
145 |
'work_mobile' => 'phone:work,cell', |
|
146 |
'work_title' => 'jobtitle', |
|
147 |
'work_zip' => 'zipcode:work', |
027208
|
148 |
'group' => 'groups', |
383379
|
149 |
); |
AM |
150 |
|
|
151 |
/** |
|
152 |
* CSV label to text mapping for English |
|
153 |
* |
|
154 |
* @var array |
|
155 |
*/ |
|
156 |
protected $label_map = array( |
|
157 |
// MS Outlook 2010 |
|
158 |
'anniversary' => "Anniversary", |
|
159 |
'assistants_name' => "Assistant's Name", |
|
160 |
'assistants_phone' => "Assistant's Phone", |
|
161 |
'birthday' => "Birthday", |
|
162 |
'business_city' => "Business City", |
|
163 |
'business_countryregion' => "Business Country/Region", |
|
164 |
'business_fax' => "Business Fax", |
|
165 |
'business_phone' => "Business Phone", |
|
166 |
'business_phone_2' => "Business Phone 2", |
|
167 |
'business_postal_code' => "Business Postal Code", |
|
168 |
'business_state' => "Business State", |
|
169 |
'business_street' => "Business Street", |
|
170 |
//'business_street_2' => "Business Street 2", |
|
171 |
//'business_street_3' => "Business Street 3", |
|
172 |
'car_phone' => "Car Phone", |
|
173 |
'categories' => "Categories", |
|
174 |
//'children' => "Children", |
|
175 |
'company' => "Company", |
|
176 |
//'company_main_phone' => "Company Main Phone", |
|
177 |
'department' => "Department", |
|
178 |
//'directory_server' => "Directory Server", |
c66b60
|
179 |
//'email_2_address' => "E-mail 2 Address", |
AM |
180 |
//'email_2_type' => "E-mail 2 Type", |
|
181 |
//'email_3_address' => "E-mail 3 Address", |
|
182 |
//'email_3_type' => "E-mail 3 Type", |
383379
|
183 |
'email_address' => "E-mail Address", |
c66b60
|
184 |
//'email_type' => "E-mail Type", |
383379
|
185 |
'first_name' => "First Name", |
AM |
186 |
'gender' => "Gender", |
|
187 |
'home_city' => "Home City", |
|
188 |
'home_countryregion' => "Home Country/Region", |
|
189 |
'home_fax' => "Home Fax", |
|
190 |
'home_phone' => "Home Phone", |
|
191 |
'home_phone_2' => "Home Phone 2", |
|
192 |
'home_postal_code' => "Home Postal Code", |
|
193 |
'home_state' => "Home State", |
|
194 |
'home_street' => "Home Street", |
|
195 |
//'home_street_2' => "Home Street 2", |
|
196 |
//'home_street_3' => "Home Street 3", |
|
197 |
//'initials' => "Initials", |
|
198 |
//'isdn' => "ISDN", |
|
199 |
'job_title' => "Job Title", |
|
200 |
//'keywords' => "Keywords", |
|
201 |
//'language' => "Language", |
|
202 |
'last_name' => "Last Name", |
|
203 |
//'location' => "Location", |
|
204 |
'managers_name' => "Manager's Name", |
|
205 |
'middle_name' => "Middle Name", |
|
206 |
//'mileage' => "Mileage", |
|
207 |
'mobile_phone' => "Mobile Phone", |
|
208 |
'notes' => "Notes", |
|
209 |
//'office_location' => "Office Location", |
|
210 |
'other_city' => "Other City", |
|
211 |
'other_countryregion' => "Other Country/Region", |
|
212 |
'other_fax' => "Other Fax", |
|
213 |
'other_phone' => "Other Phone", |
|
214 |
'other_postal_code' => "Other Postal Code", |
|
215 |
'other_state' => "Other State", |
|
216 |
'other_street' => "Other Street", |
|
217 |
//'other_street_2' => "Other Street 2", |
|
218 |
//'other_street_3' => "Other Street 3", |
|
219 |
'pager' => "Pager", |
|
220 |
'primary_phone' => "Primary Phone", |
|
221 |
//'profession' => "Profession", |
|
222 |
//'radio_phone' => "Radio Phone", |
|
223 |
'spouse' => "Spouse", |
|
224 |
'suffix' => "Suffix", |
|
225 |
'title' => "Title", |
|
226 |
'web_page' => "Web Page", |
|
227 |
|
|
228 |
// Thunderbird |
|
229 |
'birth_day' => "Birth Day", |
|
230 |
'birth_month' => "Birth Month", |
|
231 |
'birth_year' => "Birth Year", |
|
232 |
'display_name' => "Display Name", |
|
233 |
'fax_number' => "Fax Number", |
|
234 |
'home_address' => "Home Address", |
|
235 |
//'home_address_2' => "Home Address 2", |
|
236 |
'home_country' => "Home Country", |
|
237 |
'home_zipcode' => "Home ZipCode", |
|
238 |
'mobile_number' => "Mobile Number", |
|
239 |
'nickname' => "Nickname", |
|
240 |
'organization' => "Organization", |
|
241 |
'pager_number' => "Pager Namber", |
|
242 |
'primary_email' => "Primary Email", |
|
243 |
'secondary_email' => "Secondary Email", |
|
244 |
'web_page_1' => "Web Page 1", |
|
245 |
'web_page_2' => "Web Page 2", |
|
246 |
'work_phone' => "Work Phone", |
|
247 |
'work_address' => "Work Address", |
|
248 |
//'work_address_2' => "Work Address 2", |
38c19a
|
249 |
'work_city' => "Work City", |
383379
|
250 |
'work_country' => "Work Country", |
38c19a
|
251 |
'work_state' => "Work State", |
383379
|
252 |
'work_zipcode' => "Work ZipCode", |
609483
|
253 |
|
AM |
254 |
// Atmail |
|
255 |
'date_of_birth' => "Date of Birth", |
|
256 |
'email' => "Email", |
|
257 |
//'email_2' => "Email2", |
|
258 |
//'email_3' => "Email3", |
|
259 |
//'email_4' => "Email4", |
|
260 |
//'email_5' => "Email5", |
|
261 |
'home_mobile' => "Home Mobile", |
|
262 |
'home_zip' => "Home Zip", |
|
263 |
'info' => "Info", |
|
264 |
'user_photo' => "User Photo", |
|
265 |
'url' => "URL", |
|
266 |
'work_company' => "Work Company", |
|
267 |
'work_dept' => "Work Dept", |
|
268 |
'work_fax' => "Work Fax", |
|
269 |
'work_mobile' => "Work Mobile", |
|
270 |
'work_title' => "Work Title", |
|
271 |
'work_zip' => "Work Zip", |
027208
|
272 |
'groups' => "Group", |
383379
|
273 |
); |
AM |
274 |
|
92bd3a
|
275 |
protected $local_label_map = array(); |
383379
|
276 |
protected $vcards = array(); |
92bd3a
|
277 |
protected $map = array(); |
383379
|
278 |
|
AM |
279 |
|
|
280 |
/** |
|
281 |
* Class constructor |
|
282 |
* |
|
283 |
* @param string $lang File language |
|
284 |
*/ |
|
285 |
public function __construct($lang = 'en_US') |
|
286 |
{ |
|
287 |
// Localize fields map |
|
288 |
if ($lang && $lang != 'en_US') { |
9be2f4
|
289 |
if (file_exists(RCUBE_LOCALIZATION_DIR . "$lang/csv2vcard.inc")) { |
TB |
290 |
include RCUBE_LOCALIZATION_DIR . "$lang/csv2vcard.inc"; |
383379
|
291 |
} |
AM |
292 |
|
|
293 |
if (!empty($map)) { |
92bd3a
|
294 |
$this->local_label_map = array_merge($this->label_map, $map); |
383379
|
295 |
} |
AM |
296 |
} |
|
297 |
|
|
298 |
$this->label_map = array_flip($this->label_map); |
92bd3a
|
299 |
$this->local_label_map = array_flip($this->local_label_map); |
383379
|
300 |
} |
AM |
301 |
|
|
302 |
/** |
|
303 |
* |
|
304 |
*/ |
|
305 |
public function import($csv) |
|
306 |
{ |
|
307 |
// convert to UTF-8 |
|
308 |
$head = substr($csv, 0, 4096); |
a92beb
|
309 |
$charset = rcube_charset::detect($head, RCUBE_CHARSET); |
383379
|
310 |
$csv = rcube_charset::convert($csv, $charset); |
AM |
311 |
$head = ''; |
|
312 |
|
|
313 |
$this->map = array(); |
|
314 |
|
|
315 |
// Parse file |
3725cf
|
316 |
foreach (preg_split("/[\r\n]+/", $csv) as $line) { |
745d86
|
317 |
$elements = $this->parse_line($line); |
383379
|
318 |
if (empty($elements)) { |
AM |
319 |
continue; |
|
320 |
} |
|
321 |
|
|
322 |
// Parse header |
|
323 |
if (empty($this->map)) { |
|
324 |
$this->parse_header($elements); |
|
325 |
if (empty($this->map)) { |
|
326 |
break; |
|
327 |
} |
|
328 |
} |
|
329 |
// Parse data row |
|
330 |
else { |
|
331 |
$this->csv_to_vcard($elements); |
|
332 |
} |
|
333 |
} |
|
334 |
} |
|
335 |
|
|
336 |
/** |
|
337 |
* @return array rcube_vcard List of vcards |
|
338 |
*/ |
|
339 |
public function export() |
|
340 |
{ |
|
341 |
return $this->vcards; |
|
342 |
} |
|
343 |
|
|
344 |
/** |
745d86
|
345 |
* Parse CSV file line |
AM |
346 |
*/ |
|
347 |
protected function parse_line($line) |
|
348 |
{ |
|
349 |
$line = trim($line); |
|
350 |
if (empty($line)) { |
|
351 |
return null; |
|
352 |
} |
|
353 |
|
|
354 |
$fields = rcube_utils::explode_quoted_string(',', $line); |
|
355 |
|
|
356 |
// remove quotes if needed |
|
357 |
if (!empty($fields)) { |
|
358 |
foreach ($fields as $idx => $value) { |
|
359 |
if (($len = strlen($value)) > 1 && $value[0] == '"' && $value[$len-1] == '"') { |
|
360 |
// remove surrounding quotes |
|
361 |
$value = substr($value, 1, -1); |
|
362 |
// replace doubled quotes inside the string with single quote |
|
363 |
$value = str_replace('""', '"', $value); |
|
364 |
|
|
365 |
$fields[$idx] = $value; |
|
366 |
} |
|
367 |
} |
|
368 |
} |
|
369 |
|
|
370 |
return $fields; |
|
371 |
} |
|
372 |
|
|
373 |
/** |
383379
|
374 |
* Parse CSV header line, detect fields mapping |
AM |
375 |
*/ |
|
376 |
protected function parse_header($elements) |
92bd3a
|
377 |
{ |
AM |
378 |
$map1 = array(); |
|
379 |
$map2 = array(); |
|
380 |
$size = count($elements); |
|
381 |
|
|
382 |
// check English labels |
|
383 |
for ($i = 0; $i < $size; $i++) { |
383379
|
384 |
$label = $this->label_map[$elements[$i]]; |
AM |
385 |
if ($label && !empty($this->csv2vcard_map[$label])) { |
92bd3a
|
386 |
$map1[$i] = $this->csv2vcard_map[$label]; |
383379
|
387 |
} |
AM |
388 |
} |
92bd3a
|
389 |
// check localized labels |
AM |
390 |
if (!empty($this->local_label_map)) { |
|
391 |
for ($i = 0; $i < $size; $i++) { |
|
392 |
$label = $this->local_label_map[$elements[$i]]; |
0b0cae
|
393 |
|
AM |
394 |
// special localization label |
|
395 |
if ($label && $label[0] == '_') { |
|
396 |
$label = substr($label, 1); |
|
397 |
} |
|
398 |
|
92bd3a
|
399 |
if ($label && !empty($this->csv2vcard_map[$label])) { |
AM |
400 |
$map2[$i] = $this->csv2vcard_map[$label]; |
|
401 |
} |
|
402 |
} |
|
403 |
} |
|
404 |
|
|
405 |
$this->map = count($map1) >= count($map2) ? $map1 : $map2; |
383379
|
406 |
} |
AM |
407 |
|
|
408 |
/** |
|
409 |
* Convert CSV data row to vCard |
|
410 |
*/ |
|
411 |
protected function csv_to_vcard($data) |
|
412 |
{ |
|
413 |
$contact = array(); |
|
414 |
foreach ($this->map as $idx => $name) { |
|
415 |
$value = $data[$idx]; |
|
416 |
if ($value !== null && $value !== '') { |
|
417 |
$contact[$name] = $value; |
|
418 |
} |
|
419 |
} |
|
420 |
|
|
421 |
if (empty($contact)) { |
|
422 |
return; |
|
423 |
} |
|
424 |
|
|
425 |
// Handle special values |
|
426 |
if (!empty($contact['birthday-d']) && !empty($contact['birthday-m']) && !empty($contact['birthday-y'])) { |
|
427 |
$contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d']; |
|
428 |
} |
|
429 |
|
609483
|
430 |
// Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00" |
c66b60
|
431 |
foreach (array('birthday', 'anniversary') as $key) { |
609483
|
432 |
if (!empty($contact[$key])) { |
AM |
433 |
$date = preg_replace('/[0[:^word:]]/', '', $contact[$key]); |
|
434 |
if (empty($date)) { |
|
435 |
unset($contact[$key]); |
|
436 |
} |
c66b60
|
437 |
} |
AM |
438 |
} |
|
439 |
|
|
440 |
if (!empty($contact['gender']) && ($gender = strtolower($contact['gender']))) { |
|
441 |
if (!in_array($gender, array('male', 'female'))) { |
|
442 |
unset($contact['gender']); |
|
443 |
} |
|
444 |
} |
|
445 |
|
acf851
|
446 |
// Convert address(es) to rcube_vcard data |
AM |
447 |
foreach ($contact as $idx => $value) { |
|
448 |
$name = explode(':', $idx); |
|
449 |
if (in_array($name[0], array('street', 'locality', 'region', 'zipcode', 'country'))) { |
|
450 |
$contact['address:'.$name[1]][$name[0]] = $value; |
|
451 |
unset($contact[$idx]); |
|
452 |
} |
|
453 |
} |
|
454 |
|
383379
|
455 |
// Create vcard object |
AM |
456 |
$vcard = new rcube_vcard(); |
|
457 |
foreach ($contact as $name => $value) { |
|
458 |
$name = explode(':', $name); |
|
459 |
$vcard->set($name[0], $value, $name[1]); |
|
460 |
} |
|
461 |
|
|
462 |
// add to the list |
|
463 |
$this->vcards[] = $vcard; |
|
464 |
} |
|
465 |
} |