Checks if properties have been declared.
1 | <?php |
||
2 | /** |
||
3 | * EGroupware Addressbook |
||
4 | * |
||
5 | * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License |
||
6 | * @package importexport |
||
7 | * @link http://www.egroupware.org |
||
8 | * @author Cornelius Weiss <[email protected]> |
||
9 | * @copyright Cornelius Weiss <[email protected]> |
||
10 | * @version $Id$ |
||
11 | */ |
||
12 | |||
13 | use EGroupware\Api; |
||
14 | |||
15 | /** |
||
16 | * class import_csv for addressbook |
||
17 | */ |
||
18 | class addressbook_import_contacts_csv extends importexport_basic_import_csv { |
||
19 | |||
20 | /** |
||
21 | * conditions for actions |
||
22 | * |
||
23 | * @var array |
||
24 | */ |
||
25 | protected static $conditions = array( 'exists', 'equal' ); |
||
26 | |||
27 | /** |
||
28 | * @var addressbook_bo |
||
29 | */ |
||
30 | private $bocontacts; |
||
31 | |||
32 | /** |
||
33 | * For figuring out if a contact has changed |
||
34 | * |
||
35 | * @var Api\Contacts\Tracking |
||
36 | */ |
||
37 | protected $tracking; |
||
38 | |||
39 | /** |
||
40 | * @var boolean If import file has no type, it can generate a lot of warnings. |
||
41 | * Users don't like this, so we only warn once. |
||
42 | */ |
||
43 | private $type_warned = false; |
||
44 | |||
45 | /** |
||
46 | * To empty addressbook before importing, we actually keep track of |
||
47 | * what's imported and delete the others to keep history. |
||
48 | * |
||
49 | * @var type |
||
50 | */ |
||
51 | private $ids = array(); |
||
52 | |||
53 | /** |
||
54 | * imports entries according to given definition object. |
||
55 | * @param resource $_stream |
||
56 | * @param string $_charset |
||
57 | * @param definition $_definition |
||
58 | */ |
||
59 | public function import( $_stream, importexport_definition $_definition ) { |
||
60 | parent::import($_stream, $_definition); |
||
61 | |||
62 | if($_definition->plugin_options['empty_addressbook']) |
||
63 | { |
||
64 | $this->empty_addressbook($this->user, $this->ids); |
||
65 | } |
||
66 | } |
||
67 | |||
68 | /** |
||
69 | * imports entries according to given definition object. |
||
70 | * @param resource $_stream |
||
71 | * @param string $_charset |
||
72 | * @param definition $_definition |
||
73 | */ |
||
74 | public function init(importexport_definition &$_definition ) { |
||
75 | |||
76 | // fetch the addressbook bo |
||
77 | $this->bocontacts = new addressbook_bo(); |
||
78 | |||
79 | // Get the tracker for changes |
||
80 | $this->tracking = new Api\Contacts\Tracking($this->bocontacts); |
||
81 | |||
82 | $this->lookups = array( |
||
83 | 'tid' => array('n'=>'contact') |
||
84 | ); |
||
85 | foreach($this->bocontacts->content_types as $tid => $data) |
||
86 | { |
||
87 | $this->lookups['tid'][$tid] = $data['name']; |
||
88 | } |
||
89 | |||
90 | // Try and set a default type, for use if file does not specify |
||
91 | if(!$this->lookups['tid'][Api\Contacts\Storage::DELETED_TYPE] && count($this->lookups['tid']) == 1 || |
||
92 | $this->lookups['tid'][Api\Contacts\Storage::DELETED_TYPE] && count($this->lookups['tid']) == 2) |
||
93 | { |
||
94 | reset($this->lookups['tid']); |
||
95 | $this->default_type = key($this->lookups['tid']); |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
96 | } |
||
97 | |||
98 | |||
99 | // set contact owner |
||
100 | $contact_owner = isset( $_definition->plugin_options['contact_owner'] ) ? |
||
101 | $_definition->plugin_options['contact_owner'] : $this->user; |
||
102 | |||
103 | // Check to make sure target addressbook is valid |
||
104 | if(!in_array($contact_owner, array_keys($this->bocontacts->get_addressbooks(Api\Acl::ADD)))) |
||
105 | { |
||
106 | $this->warnings[0] = lang("Unable to import into %1, using %2", |
||
0 ignored issues
–
show
|
|||
107 | $contact_owner . ' ('.Api\Accounts::username($record->owner) . ')', |
||
108 | Api\Accounts::username($this->user) |
||
109 | ); |
||
110 | $contact_owner = 'personal'; |
||
111 | } |
||
112 | |||
113 | // Import into importer's personal addressbook |
||
114 | if($contact_owner == 'personal') |
||
115 | { |
||
116 | $contact_owner = $this->user; |
||
117 | } |
||
118 | $this->user = $contact_owner; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Import a single record |
||
123 | * |
||
124 | * You don't need to worry about mappings or translations, they've been done already. |
||
125 | * You do need to handle the conditions and the actions taken. |
||
126 | * |
||
127 | * Updates the count of actions taken |
||
128 | * |
||
129 | * @return boolean success |
||
130 | */ |
||
131 | protected function import_record(importexport_iface_egw_record &$record, &$import_csv) |
||
132 | { |
||
133 | // Set owner, unless it's supposed to come from CSV file |
||
134 | if($this->definition->plugin_options['owner_from_csv'] && $record->owner) { |
||
135 | if(!is_numeric($record->owner)) { |
||
136 | // Automatically handle text owner without explicit translation |
||
137 | $new_owner = importexport_helper_functions::account_name2id($record->owner); |
||
138 | if($new_owner == '') { |
||
139 | $this->errors[$import_csv->get_current_position()] = lang( |
||
140 | 'Unable to convert "%1" to account ID. Using plugin setting (%2) for owner.', |
||
141 | $record->owner, |
||
142 | Api\Accounts::username($this->user) |
||
143 | ); |
||
144 | $record->owner = $this->user; |
||
145 | } else { |
||
146 | $record->owner = $new_owner; |
||
147 | } |
||
148 | } |
||
149 | } else { |
||
150 | $record->owner = $this->user; |
||
151 | } |
||
152 | |||
153 | // Check that owner (addressbook) is allowed |
||
154 | if(!array_key_exists($record->owner, $this->bocontacts->get_addressbooks())) |
||
155 | { |
||
156 | $this->errors[$import_csv->get_current_position()] = lang("Unable to import into %1, using %2", |
||
157 | Api\Accounts::username($record->owner), |
||
158 | Api\Accounts::username($this->user) |
||
159 | ); |
||
160 | $record->owner = $this->user; |
||
161 | } |
||
162 | |||
163 | // Do not allow owner == 0 (accounts) without an account_id |
||
164 | // It causes the contact to be filed as an account, and can't delete |
||
165 | if(!$record->owner && !$record->account_id) |
||
166 | { |
||
167 | $record->owner = $GLOBALS['egw_info']['user']['account_id']; |
||
168 | } |
||
169 | |||
170 | // Do not import into non-existing type, warn and change |
||
171 | if(!$record->tid || !$this->lookups['tid'][$record->tid]) |
||
172 | { |
||
173 | // Avoid lots of warnings about type (2 types are contact and deleted) |
||
174 | if($record->tid && !$this->type_warned[$record->tid] && !$this->lookups['tid'][$record->tid] ) |
||
175 | { |
||
176 | $this->warnings[$import_csv->get_current_position()] = lang('Unknown type %1, imported as %2',$record->tid,lang($this->lookups['tid']['n'])); |
||
0 ignored issues
–
show
|
|||
177 | $this->type_warned[$record->tid] = true; |
||
178 | } |
||
179 | $record->tid = $this->default_type; |
||
180 | } |
||
181 | |||
182 | // Also handle categories in their own field |
||
183 | $record_array = $record->get_record_array(); |
||
184 | $more_categories = array(); |
||
185 | foreach($this->definition->plugin_options['field_mapping'] as $field_name) { |
||
186 | if(!array_key_exists($field_name, $record_array) || |
||
187 | substr($field_name,0,3) != 'cat' || !$record->$field_name || $field_name == 'cat_id') continue; |
||
188 | list(, $cat_id) = explode('-', $field_name); |
||
189 | if(is_numeric($record->$field_name) && $record->$field_name != 1) { |
||
190 | // Column has a single category ID |
||
191 | $more_categories[] = $record->$field_name; |
||
192 | } elseif($record->$field_name == '1' || |
||
193 | (!is_numeric($record->$field_name) && strtolower($record->$field_name) == strtolower(lang('Yes')))) { |
||
194 | // Each category got its own column. '1' is the database value, lang('yes') is the human value |
||
195 | $more_categories[] = $cat_id; |
||
196 | } else { |
||
197 | // Text categories |
||
198 | $more_categories = array_merge($more_categories, importexport_helper_functions::cat_name2id(is_array($record->$field_name) ? $record->$field_name : explode(',',$record->$field_name), $cat_id)); |
||
199 | } |
||
200 | } |
||
201 | if(count($more_categories) > 0) $record->cat_id = array_merge(is_array($record->cat_id) ? $record->cat_id : explode(',',$record->cat_id), $more_categories); |
||
202 | |||
203 | // Private set but missing causes hidden entries |
||
204 | if(array_key_exists('private', $record_array) && (!isset($record_array['private']) || $record_array['private'] == '')) unset($record->private); |
||
205 | |||
206 | // Format birthday as backend requires - converter should give timestamp |
||
207 | if($record->bday && is_numeric($record->bday)) |
||
208 | { |
||
209 | $time = new Api\DateTime($record->bday); |
||
210 | $record->bday = $time->format('Y-m-d'); |
||
211 | } |
||
212 | |||
213 | if ( $this->definition->plugin_options['conditions'] ) { |
||
214 | foreach ( $this->definition->plugin_options['conditions'] as $condition ) { |
||
215 | $contacts = array(); |
||
216 | switch ( $condition['type'] ) { |
||
217 | // exists |
||
218 | case 'exists' : |
||
219 | if($record_array[$condition['string']]) { |
||
220 | $searchcondition = array( $condition['string'] => $record_array[$condition['string']]); |
||
221 | // if we use account_id for the condition, we need to set the owner for filtering, as this |
||
222 | // enables Api\Contacts\Storage to decide what backend is to be used |
||
223 | if ($condition['string']=='account_id') $searchcondition['owner']=0; |
||
224 | $contacts = $this->bocontacts->search( |
||
225 | //array( $condition['string'] => $record[$condition['string']],), |
||
226 | '', |
||
227 | $this->definition->plugin_options['update_cats'] == 'add' ? false : true, |
||
228 | '', '', '', false, 'AND', false, |
||
229 | $searchcondition |
||
230 | ); |
||
231 | } |
||
232 | if ( is_array( $contacts ) && count( array_keys( $contacts ) ) >= 1 ) { |
||
233 | // apply action to all contacts matching this exists condition |
||
234 | $action = $condition['true']; |
||
235 | foreach ( (array)$contacts as $contact ) { |
||
236 | $record->id = $contact['id']; |
||
237 | if ( $this->definition->plugin_options['update_cats'] == 'add' ) { |
||
238 | if ( !is_array( $contact['cat_id'] ) ) $contact['cat_id'] = explode( ',', $contact['cat_id'] ); |
||
239 | if ( !is_array( $record_array['cat_id'] ) ) $record->cat_id = explode( ',', $record->cat_id ); |
||
240 | $record->cat_id = implode( ',', array_unique( array_merge( $record->cat_id, $contact['cat_id'] ) ) ); |
||
241 | } |
||
242 | $success = $this->action( $action['action'], $record, $import_csv->get_current_position() ); |
||
243 | } |
||
244 | } else { |
||
245 | $action = $condition['false']; |
||
246 | $success = ($this->action( $action['action'], $record, $import_csv->get_current_position() )); |
||
247 | } |
||
248 | break; |
||
249 | case 'equal': |
||
250 | // Match on field |
||
251 | $result = $this->equal($record, $condition); |
||
252 | if($result) |
||
253 | { |
||
254 | // Apply true action to any matching records found |
||
255 | $action = $condition['true']; |
||
256 | $success = ($this->action( $action['action'], $record, $import_csv->get_current_position() )); |
||
257 | } |
||
258 | else |
||
259 | { |
||
260 | // Apply false action if no matching records found |
||
261 | $action = $condition['false']; |
||
262 | $success = ($this->action( $action['action'], $record, $import_csv->get_current_position() )); |
||
263 | } |
||
264 | break; |
||
265 | |||
266 | // not supported action |
||
267 | default : |
||
268 | die('condition / action not supported!!!'); |
||
269 | } |
||
270 | if ($action['stop']) break; |
||
271 | } |
||
272 | } else { |
||
273 | // unconditional insert |
||
274 | $success = $this->action( 'insert', $record, $import_csv->get_current_position() ); |
||
275 | } |
||
276 | return $success; |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * perform the required action |
||
281 | * |
||
282 | * @param int $_action one of $this->actions |
||
283 | * @param importexport_iface_egw_record $record contact data for the action |
||
284 | * @return bool success or not |
||
285 | */ |
||
286 | protected function action ( $_action, importexport_iface_egw_record &$record, $record_num = 0 ) { |
||
287 | $_data = $record->get_record_array(); |
||
288 | |||
289 | // Make sure picture is loaded/updated |
||
290 | if($_data['jpegphoto']) |
||
291 | { |
||
292 | $_data['photo_unchanged'] = false; |
||
293 | } |
||
294 | |||
295 | switch ($_action) { |
||
296 | case 'none' : |
||
297 | return true; |
||
298 | case 'delete': |
||
299 | if($_data['id']) |
||
300 | { |
||
301 | if ( $this->dry_run ) { |
||
302 | //print_r($_data); |
||
303 | $this->results[$_action]++; |
||
304 | return true; |
||
305 | } |
||
306 | $result = $this->bocontacts->delete($_data); |
||
307 | if($result && $result === true) |
||
308 | { |
||
309 | $this->results[$_action]++; |
||
310 | } |
||
311 | else |
||
312 | { |
||
313 | // Failure of some kind - unknown cause |
||
314 | $this->errors[$record_num] = lang('unable to delete'); |
||
315 | } |
||
316 | } |
||
317 | break; |
||
318 | case 'update' : |
||
319 | // Only update if there are changes |
||
320 | $old = $this->bocontacts->read($_data['id']); |
||
321 | // if we get countrycodes as countryname, try to translate them -> the rest should be handled by bo classes. |
||
322 | foreach(array('adr_one_', 'adr_two_') as $c_prefix) { |
||
323 | if (strlen(trim($_data[$c_prefix.'countryname']))==2) |
||
324 | $_data[$c_prefix.'countryname'] = $GLOBALS['egw']->country->get_full_name(trim($_data[$c_prefix.'countryname']), true); |
||
325 | } |
||
326 | // Don't change a user account into a contact |
||
327 | if($old['owner'] == 0) { |
||
328 | unset($_data['owner']); |
||
329 | } elseif(!$this->definition->plugin_options['change_owner']) { |
||
330 | // Don't change addressbook of an existing contact |
||
331 | unset($_data['owner']); |
||
332 | } |
||
333 | |||
334 | $this->ids[] = $_data['id']; |
||
335 | |||
336 | // Merge to deal with fields not in import record |
||
337 | $_data = array_merge($old, $_data); |
||
338 | $changed = $this->tracking->changed_fields($_data, $old); |
||
339 | if(count($changed) == 0) { |
||
340 | return true; |
||
341 | } else { |
||
342 | //error_log(__METHOD__.__LINE__.array2string($changed).' Old:'.$old['adr_one_countryname'].' ('.$old['adr_one_countrycode'].') New:'.$_data['adr_one_countryname'].' ('.$_data['adr_one_countryname'].')'); |
||
343 | } |
||
344 | |||
345 | // Make sure n_fn gets updated |
||
346 | unset($_data['n_fn']); |
||
347 | |||
348 | // Fall through |
||
349 | case 'insert' : |
||
350 | if($_action == 'insert') { |
||
351 | // Addressbook backend doesn't like inserting with ID specified, it screws up the owner & etag |
||
352 | unset($_data['id']); |
||
353 | } |
||
354 | if(!isset($_data['org_name'])) { |
||
355 | // org_name is a trigger to update n_fileas |
||
356 | $_data['org_name'] = ''; |
||
357 | } |
||
358 | |||
359 | if ( $this->dry_run ) { |
||
360 | //print_r($_data); |
||
361 | $this->results[$_action]++; |
||
362 | return true; |
||
363 | } else { |
||
364 | $result = $this->bocontacts->save( $_data, $this->is_admin); |
||
365 | if(!$result) { |
||
366 | $this->errors[$record_num] = $this->bocontacts->error; |
||
367 | } else { |
||
368 | $this->ids[] = $result; |
||
369 | $this->results[$_action]++; |
||
370 | // This does nothing (yet?) but update the identifier |
||
371 | $record->save($result); |
||
372 | } |
||
373 | return $result; |
||
374 | } |
||
375 | default: |
||
376 | throw new Api\Exception('Unsupported action: '. $_action); |
||
377 | |||
378 | } |
||
379 | } |
||
380 | |||
381 | |||
382 | /** |
||
383 | * Delete all contacts from the addressbook, except the given list |
||
384 | * |
||
385 | * @param int $addressbook Addressbook to clear |
||
386 | * @param array $ids Contacts to keep |
||
387 | */ |
||
388 | protected function empty_addressbook($addressbook, $ids) |
||
389 | { |
||
390 | // Get all IDs in addressbook |
||
391 | $contacts = $this->bocontacts->search(array('owner' => $addressbook), true); |
||
392 | $contacts = array_column($contacts, 'id'); |
||
393 | |||
394 | $delete = array_diff($contacts, $ids); |
||
395 | |||
396 | foreach($delete as $id) |
||
397 | { |
||
398 | if($this->dry_run || $this->bocontacts->delete($id)) |
||
399 | { |
||
400 | $this->results['deleted']++; |
||
401 | } |
||
402 | else |
||
403 | { |
||
404 | $this->warnings[] = lang('Unable to delete') . ': ' . Api\Link::title('addressbook', $id); |
||
0 ignored issues
–
show
|
|||
405 | } |
||
406 | } |
||
407 | } |
||
408 | |||
409 | /** |
||
410 | * returns translated name of plugin |
||
411 | * |
||
412 | * @return string name |
||
413 | */ |
||
414 | public static function get_name() { |
||
415 | return lang('Addressbook CSV import'); |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * returns translated (user) description of plugin |
||
420 | * |
||
421 | * @return string descriprion |
||
422 | */ |
||
423 | public static function get_description() { |
||
424 | return lang("Imports contacts into your Addressbook from a CSV File. CSV means 'Comma Separated Values'. However in the options Tab you can also choose other seperators."); |
||
425 | } |
||
426 | |||
427 | /** |
||
428 | * retruns file suffix(s) plugin can handle (e.g. csv) |
||
429 | * |
||
430 | * @return string suffix (comma seperated) |
||
431 | */ |
||
432 | public static function get_filesuffix() { |
||
433 | return 'csv'; |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * return etemplate components for options. |
||
438 | * @abstract We can't deal with etemplate objects here, as an uietemplate |
||
439 | * objects itself are scipt orientated and not "dialog objects" |
||
440 | * |
||
441 | * @return array ( |
||
442 | * name => string, |
||
443 | * content => array, |
||
444 | * sel_options => array, |
||
445 | * preserv => array, |
||
446 | * ) |
||
447 | */ |
||
448 | public function get_options_etpl(importexport_definition &$definition=null) |
||
449 | { |
||
450 | // lets do it! |
||
451 | } |
||
452 | |||
453 | /** |
||
454 | * returns etemplate name for slectors of this plugin |
||
455 | * |
||
456 | * @return string etemplate name |
||
457 | */ |
||
458 | public function get_selectors_etpl() { |
||
459 | // lets do it! |
||
460 | } |
||
461 | |||
462 | /** |
||
463 | * Returns warnings that were encountered during importing |
||
464 | * Maximum of one warning message per record, but you can append if you need to |
||
465 | * |
||
466 | * @return Array ( |
||
467 | * record_# => warning message |
||
468 | * ) |
||
469 | */ |
||
470 | public function get_warnings() { |
||
471 | return $this->warnings; |
||
472 | } |
||
473 | |||
474 | /** |
||
475 | * Returns errors that were encountered during importing |
||
476 | * Maximum of one error message per record, but you can append if you need to |
||
477 | * |
||
478 | * @return Array ( |
||
479 | * record_# => error message |
||
480 | * ) |
||
481 | */ |
||
482 | public function get_errors() { |
||
483 | return $this->errors; |
||
484 | } |
||
485 | |||
486 | /** |
||
487 | * Returns a list of actions taken, and the number of records for that action. |
||
488 | * Actions are things like 'insert', 'update', 'delete', and may be different for each plugin. |
||
489 | * |
||
490 | * @return Array ( |
||
491 | * action => record count |
||
492 | * ) |
||
493 | */ |
||
494 | public function get_results() { |
||
495 | return $this->results; |
||
496 | } |
||
497 | } |
||
498 |