Make ImportDialog compatible to the new modelling system
[tine20] / tine20 / Tinebase / js / widgets / dialog / ImportDialog.js
1 /*
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Cornelius Weiss <c.weiss@metaways.de>
6  * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8 Ext.ns('Tine.widgets.dialog');
9
10 /**
11  * Generic 'Import' dialog
12  *
13  * @namespace   Tine.widgets.dialog
14  * @class       Tine.widgets.dialog.ImportDialog
15  * @extends     Tine.widgets.dialog.EditDialog
16  * @constructor
17  * @param       {Object} config The configuration options.
18  * 
19  * TODO add app grid to show results when dry run is selected
20  */
21 Tine.widgets.dialog.ImportDialog = Ext.extend(Tine.widgets.dialog.WizardPanel, {
22     /**
23      * @cfg {String} appName (required)
24      */
25     appName: null,
26     
27     /**
28      * @cfg {String} modelName (required)
29      */
30     modelName: null, 
31     
32     /**
33      * @cfg {String} defaultImportContainer
34      */
35     defaultImportContainer: null,
36     
37     /**
38      * @property allowedFileExtensions
39      * @type Array
40      */
41     allowedFileExtensions: null,
42     
43     /**
44      * @property recordClass
45      * @type Tine.Tinebase.data.Record
46      */
47     recordClass: null,
48     
49     /**
50      * @property definitionsStore
51      * @type Ext.data.JsonStore
52      */
53     definitionsStore: null,
54     
55     /**
56      * @property selectedDefinition
57      * @type Ext.data.Record
58      */
59     selectedDefinition: null,
60     
61     /**
62      * @property exceptionStore
63      * @type Ext.data.JsonStore
64      */
65     exceptionStore: null,
66     
67     /**
68      * @property lastImportResponse
69      * @type Objcet resutls of the last import request
70      */
71     lastImportResponse: null,
72     
73     // private config overrides
74     windowNamePrefix: 'ImportWindow_',
75     
76     /**
77      * init import wizard
78      */
79     initComponent: function() {
80
81         Tine.log.debug('Tine.widgets.dialog.ImportDialog::initComponent this');
82         Tine.log.debug(this);
83
84         this.app = Tine.Tinebase.appMgr.get(this.appName);
85         this.recordClass = Tine.Tinebase.data.RecordMgr.get(this.appName, this.modelName);
86
87         this.allowedFileExtensions = [];
88
89         // init definitions
90         this.definitionsStore = new Ext.data.JsonStore({
91             fields: Tine.Tinebase.Model.ImportExportDefinition,
92             root: 'results',
93             totalProperty: 'totalcount',
94             remoteSort: false
95         });
96
97         if (Tine[this.appName].registry.get('importDefinitions')) {
98             Ext.each(Tine[this.appName].registry.get('importDefinitions').results, function(defData) {
99                 var options = defData.plugin_options,
100                     extension = options ? options.extension : null;
101                 
102                 defData.label = this.app.i18n._hidden(options && options.label ? options.label : defData.name);
103                 
104                 if (this.allowedFileExtensions.indexOf(extension) == -1) {
105                     this.allowedFileExtensions = this.allowedFileExtensions.concat(extension);
106                 }
107                 
108                 this.definitionsStore.addSorted(new Tine.Tinebase.Model.ImportExportDefinition(defData, defData.id));
109             }, this);
110             this.definitionsStore.sort('label');
111         }
112         if (! this.selectedDefinition && Tine[this.appName].registry.get('defaultImportDefinition')) {
113             this.selectedDefinition = this.definitionsStore.getById(Tine[this.appName].registry.get('defaultImportDefinition').id);
114         }
115
116         // init exception store
117         this.exceptionStore = new Ext.data.JsonStore({
118             mode: 'local',
119             idProperty: 'index',
120             fields: ['index', 'code', 'message', 'exception', 'resolveStrategy', 'resolvedRecord', 'isResolved']
121         });
122
123         this.items = [
124             this.getFilePanel(),
125             this.getOptionsPanel(),
126             this.getConflictsPanel(),
127             this.getSummaryPanel()
128         ];
129
130         Tine.widgets.dialog.ImportDialog.superclass.initComponent.call(this);
131     },
132
133     /**
134      * close window on cancel
135      */
136     onCancelButton: function() {
137         this.fireEvent('cancel', this, this.layout.activeItem);
138         this.window.close();
139     },
140
141     /**
142      * do import request
143      * 
144      * @param {Function} callback
145      * @param {Object}   importOptions
146      */
147     doImport: function(callback, importOptions, clientRecordData) {
148         Ext.Ajax.request({
149             scope: this,
150             timeout: 1800000, // 30 minutes
151             callback: this.onImportResponse.createDelegate(this, [callback], true),
152             params: {
153                 method: this.appName + '.import' + this.recordClass.getMeta('modelName')  + 's',
154                 tempFileId: this.uploadButton.getTempFileId(),
155                 definitionId: this.definitionCombo.getValue(),
156                 importOptions: Ext.apply({
157                     container_id: this.containerCombo.getValue(),
158                     autotags: this.tagsPanel.getFormField().getValue()
159                 }, importOptions || {}),
160                 clientRecordData: clientRecordData
161             }
162         });
163     },
164     
165     /**
166      * called when import request sends response
167      * 
168      * @param {Object}   request
169      * @param {Boolean}  success
170      * @param {Object}   response
171      * @param {Function} callback
172      */
173     onImportResponse: function(request, success, response, callback) {
174         response = Ext.util.JSON.decode(response.responseText);
175         
176         Tine.log.debug('Tine.widgets.dialog.ImportDialog::onImportResponse server response');
177         Tine.log.debug(response);
178         
179         this.lastImportResponse = response;
180         
181         // load exception store
182         this.exceptionStore.loadData(response.exceptions);
183         this.exceptionStore.filterBy(this.exceptionStoreFilter, this);
184         
185         // update conflict panel
186 //            var duplicatecount = response.duplicatecount || 0,
187 //                recordsName = this.app.i18n.n_(this.recordClass.getMeta('recordName'), this.recordClass.getMeta('recordsName'), duplicatecount);
188 //                
189 //            this.conflictsLabel.setText(String.format(this.conflictsLabel.rawText, duplicatecount, recordsName), false);
190         if (this.exceptionStore.getCount()) {
191             this.loadConflict(0);
192         }
193         
194         // finlay apply callback
195         if (Ext.isFunction(callback)) {
196             callback.call(this, request, success, response);
197         }
198     },
199     
200     exceptionStoreFilter: function(record, id) {
201         return record.get('code') == 629 && ! record.get('isResolved');
202     },
203     
204     /********************************************************** FILE PANEL **********************************************************/
205     
206     /**
207      * returns the file panel of this wizard (step 1)
208      * 
209      * @TODO restrict allowed extensions on definition selection OR
210      *       restirct allowed definitions on file selection
211      */
212     getFilePanel: function() {
213         if (this.filePanel) {
214             return this.filePanel;
215         }
216         
217         var def = this.selectedDefinition,
218             description = def ? def.get('description') : '',
219             options = def ? def.get('plugin_options') : null,
220             example = options && options.example ? options.example : '';
221             
222         return {
223             title: _('Choose File and Format'),
224             layout: 'vbox',
225             border: false,
226             xtype: 'ux.displaypanel',
227             frame: true,
228             ref: '../filePanel',
229             items: [{
230                 xtype: 'panel',
231                 baseCls: 'ux-subformpanel',
232                 title: _('Choose Import File'),
233                 height: 100,
234                 items: [{
235                     xtype: 'label',
236                     html: '<p>' + _('Please choose the file that contains the records you want to add to Tine 2.0').replace(/Tine 2\.0/g, Tine.title) + '</p><br />'
237                 }, {
238                     xtype: 'tw.uploadbutton',
239                     ref: '../../uploadButton',
240                     text: String.format(_('Select file containing your {0}'), this.recordClass.getRecordsName()),
241                     handler: this.onFileReady,
242                     allowedTypes: this.allowedFileExtensions,
243                     scope: this
244                 }]
245             }, {
246                 xtype: 'panel',
247                 baseCls: 'ux-subformpanel',
248                 title: _('What should the file you upload look like?'),
249                 flex: 1,
250                 items: [/*{
251                     xtype: 'label',
252                     cls: 'tb-login-big-label',
253                     html: _('What should the file you upload look like?') + '<br />'
254                 },*/ {
255                     xtype: 'label',
256                     html: '<p>' + _('Tine 2.0 does not understand all kind of files you might want to upload. You will have to manually adjust your file so Tine 2.0 can handle it.').replace(/Tine 2\.0/g, Tine.title) + '</p><br />'
257                 }, {
258                     xtype: 'label',
259                     html: '<p>' + _('Following you find a list of all supported import formats and a sample file, how Tine 2.0 expects your file to look like.').replace(/Tine 2\.0/g, Tine.title) + '</p><br />'
260                 }, {
261                     xtype: 'label',
262                     html: '<p>' + _('Please select the import format of the file you want to upload').replace(/Tine 2\.0/g, Tine.title) + '</p>'
263                 }, {
264                     xtype: 'combo',
265                     ref: '../../definitionCombo',
266                     store: this.definitionsStore,
267                     displayField:'label',
268                     valueField:'id',
269                     mode: 'local',
270                     triggerAction: 'all',
271                     editable: false,
272                     allowBlank: false,
273                     forceSelection: true,
274                     width: 400,
275                     value: this.selectedDefinition ? this.selectedDefinition.id : null,
276                     listeners: {
277                         scope: this,
278                         'select': this.onDefinitionSelect
279                     }
280                 }, {
281                     xtype: 'label',
282                     ref: '../../exampleLink',
283                     html: example ? ('<p><a href="' + example + '">' + _('Download example file') + '</a></p>') : '<p>&nbsp;</p>'
284                 }, {
285                     xtype: 'displayfield',
286 //                    fieldLabel: _('Import description'),
287                     ref: '../../definitionDescription',
288                     height: 70,
289                     value: description,
290                     cls: 'x-ux-display-background-border',
291                     style: 'padding-left: 5px;'
292                 }]
293             }],
294             nextIsAllowed: (function() {
295                 return this.definitionCombo && this.definitionCombo.getValue() && this.uploadButton && this.uploadButton.fileRecord;
296             }).createDelegate(this)
297         };
298     },
299     
300     onFileReady: function() {
301         this.manageButtons();
302     },
303     
304     /**
305      * select handler of definition combo
306      */
307     onDefinitionSelect: function(combo, record, index) {
308         var description = record.get('description'),
309             options = record.get('plugin_options'),
310             example = options && options.example ? options.example : '';
311         
312         this.selectedDefinition = record;
313         
314         this.definitionDescription.setValue(description);
315         this.exampleLink.setText(example ? ('<p><a href="' + example + '">' + _('Download example file') + '</a></p>') : '<p>&nbsp;</p>', false);
316         
317         this.manageButtons();
318     },
319     
320     /**
321      * get options of the plugin from the currently selected definition
322      */
323     getImportPluginOptions: function() {
324         var options = this.selectedDefinition ? this.selectedDefinition.get('plugin_options') : null;
325             
326         return options || {};
327     },
328     
329     /********************************************************** OPTIONS PANEL **********************************************************/
330     getOptionsPanel: function() {
331         if (this.optionsPanel) {
332             return this.optionsPanel;
333         }
334         
335         return {
336             title: _('Set Import Options'),
337             layout: 'fit',
338             border: false,
339             xtype: 'form',
340             frame: true,
341             ref: '../optionsPanel',
342             items: [{
343                 xtype: 'label',
344                 html: '<p>' + String.format(_('Select {0} to add you {1} to:'), this.recordClass.getContainerName(), this.recordClass.getRecordsName()) + '</p>'
345             }, new Tine.widgets.container.selectionComboBox({
346                 id: this.app.appName + 'EditDialogContainerSelector',
347                 width: 300,
348                 ref: '../containerCombo',
349                 stateful: false,
350                 containerName: this.recordClass.getContainerName(),
351                 containersName: this.recordClass.getContainersName(),
352                 appName: this.appName,
353                 value: this.defaultImportContainer,
354                 requiredGrant: false
355             }), new Tine.widgets.tags.TagPanel({
356                 app: this.appName,
357                 ref: '../tagsPanel',
358                 style: 'margin-top: 15px; border: 1px solid silver; border-top: none;',
359                 border: true,
360                 collapsible: false,
361                 height: 200
362             })],
363             
364             listeners: {
365                 scope: this,
366                 show: function() {
367                    var options = this.getImportPluginOptions();
368                 
369                     if (options.autotags) {
370                         var tags = options.autotags;
371                         
372                         Ext.each([].concat(tags), function(tag) {
373                             tag.name = this.app.i18n._hidden(tag.name);
374                             tag.description = this.app.i18n._hidden(tag.description);
375                         }, this);
376                         
377                         this.tagsPanel.getFormField().setValue(tags);
378                     }
379                     
380                     this.containerCombo.setValue(options.container_id ? options.container_id : this.defaultImportContainer);
381                 }
382             },
383             
384             /**
385              * check if next button is allowed
386              */
387             nextIsAllowed: (function() {
388                 return this.containerCombo && this.containerCombo.getValue();
389             }).createDelegate(this),
390             
391             /**
392              * next button handler for this panel
393              */
394             onNextButton: (function() {
395                 if (! this.checkMask) {
396                     this.checkMask = new Ext.LoadMask(this.getEl(), {msg: _('Checking Import')});
397                 }
398                 
399                 this.checkMask.show();
400             
401                 this.doImport(function(request, success, response) {
402                     this.checkMask.hide();
403                     
404                     // jump to finish panel if no conflicts where detected
405                     if (! response.duplicatecount) {
406                         this.navigate(+2);
407                     } else {
408                         this.navigate(+1);
409                     }
410                 }, {dryrun: true});
411                 
412             }).createDelegate(this)
413         }
414     },
415     
416     
417     /********************************************************** CONFLICT PANEL **********************************************************/
418     
419     getConflictsPanel: function() {
420         if (this.conflictsPanel) {
421             return this.conflictsPanel;
422         }
423         
424         return {
425             title: _('Resolve Conflicts'),
426             layout: 'vbox',
427             border: false,
428             xtype: 'form',
429             frame: true,
430             ref: '../conflictsPanel',
431             items: [/*{
432                 xtype: 'label',
433                 ref: '../conflictsLabel',
434                 rawText: '<p>' + _('There are {0} {1} that might already exist.') + '</p>',
435                 html: '<p></p>',
436                 height: 20
437             },*/ {
438                 xtype: 'paging',
439                 ref: '../conflictPagingToolbar',
440                 pageSize: 1,
441                 beforePageText: _('Conflict'),
442                 firstText : _('First Conflict'),
443                 prevText : _('Previous Conflict'),
444                 nextText : _('Next Conflict'),
445                 lastText : _('Last Conflict'),
446                 store: this.exceptionStore,
447                 doLoad: this.loadConflict.createDelegate(this),
448                 onLoad: Ext.emptyFn,
449                 listeners: {afterrender: function(t){t.refresh.hide();}},
450                 items: [this.conflictIndexText = new Ext.Toolbar.TextItem({}), '->', {
451                     text: _('Conflict is resolved'),
452                     xtype: 'splitbutton',
453                     scope: this,
454                     handler: this.onResolveConflict,
455                     menu: [{
456                         text: _('Resolve all conflicts'),
457                         scope: this,
458                         handler: this.onResolveAllConflict
459                     }]
460                 }]
461             }, new Tine.widgets.dialog.DuplicateResolveGridPanel({
462                 flex: 1,
463                 ref: '../duplicateResolveGridPanel',
464                 header: false,
465                 app: this.app,
466                 store: new Tine.widgets.dialog.DuplicateResolveStore({
467                     app: this.app,
468                     recordClass: this.recordClass
469                 })
470             })],
471             listeners: {
472                 scope: this,
473                 show: function() {
474                     if (! this.exceptionStore.isFiltered()) {
475                         this.exceptionStore.filterBy(this.exceptionStoreFilter, this);
476                         this.manageButtons();
477                     }
478                 }
479             },
480             /**
481              * check if next button is allowed
482              */
483             nextIsAllowed: (function() {
484                 var nextIsAllowed = true;
485                 
486                 // check if all conflicts are resolved
487                 this.exceptionStore.each(function(exception) {
488                     if (! exception.get('isResolved')) {
489                         nextIsAllowed = false;
490                         return false;
491                     }
492                 }, this);
493                 
494                 return nextIsAllowed;
495                 
496             }).createDelegate(this)
497         };
498     },
499     
500     /**
501      * mark current conflict resolved
502      */
503     onResolveConflict: function() {
504         var index = this.conflictPagingToolbar.cursor,
505             record = this.exceptionStore.getAt(index),
506             resolveStore = this.duplicateResolveGridPanel.getStore(),
507             resolveStrategy = resolveStore.resolveStrategy,
508             resolveRecord = resolveStore.getResolvedRecord();
509         
510         // mark exception record resolved
511         record.set('resolveStrategy', resolveStrategy);
512         record.set('resolvedRecord', resolveRecord);
513         record.set('isResolved', true);
514         
515         // load next conflict
516         this.exceptionStore.filterBy(this.exceptionStoreFilter, this);
517         this.manageButtons();
518         
519         this.loadConflict(this.exceptionStore.getCount() > index ? index : index-1);
520         
521         if (Ext.isFunction(arguments[0])) {
522             return arguments[0].call(this);
523         }
524     },
525     
526     /**
527      * resolve all conflicts using current strategy
528      */
529     onResolveAllConflict: function() {
530         
531         // @TODO: disable grid to spedup things?
532         if (this.exceptionStore.getCount() > 0) {
533             this.conflictMask.show();
534             this.conflictMask.hidden = false;
535             
536             return this.onResolveConflict.defer(20, this, [this.onResolveAllConflict]);
537         } else {
538             this.conflictMask.hide();
539             this.conflictMask.hidden = true;
540         }
541     },
542     
543     /**
544      * load conflict with given index
545      * 
546      * @param {Number} index
547      */
548     loadConflict: function(index) {
549         if (! this.conflictMask) {
550             this.conflictMask = new Ext.LoadMask(this.getEl(), {msg: _('Processing Conflict Data'), hidden: true});
551         }
552
553         // give DOM the time to show loadMask
554         if (this.conflictMask.hidden) {
555             this.conflictMask.show();
556             this.conflictMask.hidden = false;
557             
558             return this.loadConflict.defer(200, this, arguments);
559         }
560             
561         var thisRecord = this.exceptionStore.getAt(this.conflictPagingToolbar.cursor),
562             nextRecord = this.exceptionStore.getAt(index),
563             resolveStore = this.duplicateResolveGridPanel.getStore();
564             
565         // preserv changes
566         if (this.conflictPagingToolbar.cursor != index && thisRecord && resolveStore.getCount()) {
567             thisRecord.set('resolvedRecord', resolveStore.getResolvedRecord());
568             thisRecord.set('resolveStrategy', resolveStore.resolveStrategy);
569         }
570         
571         if (nextRecord) {
572             resolveStore.loadData(nextRecord.get('exception'), nextRecord.get('resolveStrategy') || resolveStore.resolveStrategy, nextRecord.get('resolvedRecord'));
573         } else {
574             resolveStore.removeAll();
575             this.duplicateResolveGridPanel.getView().mainBody.update('<br />  ' + _('No conflict to resolve'));
576             // defer navigation to complete this step before
577             this.navigate.defer(200, this, [+1]);
578         }
579         
580         
581         // update paging toolbar
582         var p = this.conflictPagingToolbar,
583             ap = index+1,
584             ps = this.exceptionStore.getCount();
585             
586         p.cursor = index;
587         p.afterTextItem.setText(String.format(p.afterPageText, ps));
588         p.inputItem.setValue(ap);
589         p.first.setDisabled(ap == 1);
590         p.prev.setDisabled(ap == 1);
591         p.next.setDisabled(ap == ps);
592         p.last.setDisabled(ap == ps);
593         this.conflictIndexText.setText(nextRecord ? 
594             String.format(_('(This is record {0} in your import file)'), nextRecord.get('index') + 1) :
595             _('No conflict to resolve')
596         );
597         
598         this.conflictMask.hide();
599         this.conflictMask.hidden = true;
600     },
601     
602     /********************************************************** SUMMARY PANEL **********************************************************/
603     
604     getSummaryPanel: function() {
605         if (this.summaryPanel) {
606             return this.summaryPanel;
607         }
608         var exceptionExpander = new Ext.ux.grid.RowExpander({
609             tpl : new Ext.XTemplate('{[this.showClientRecord(values)]}', { showClientRecord: function(values) {
610                 if (values && values.exception && values.exception.clientRecord) {
611                     // there is no generic detailsPanel retirval yet
612 //                    if (Ext.isObject(values.exception.clientRecord)) {
613 //                        var detailsPanel = new Tine.Addressbook.ContactGridDetailsPanel({});
614 //                        
615 //                        return detailsPanel.tpl.apply(values.exception.clientRecord);
616 //                    }
617                     return Ext.util.Format.htmlEncode(Ext.encode(values.exception.clientRecord));
618                 } else {
619                     return _('No Detail Informations');
620                 }
621             }})
622         });
623         return {
624             title: _('Summary'),
625             border: false,
626             xtype: 'ux.displaypanel',
627             frame: true,
628             ref: '../summaryPanel',
629             autoScroll: true,
630             items: [{
631                 height: 100,
632                 ref: '../summaryPanelInfo',
633                 border: false,
634                 layout: 'ux.display',
635                 layoutConfig: {
636                     background: 'border'
637                 }
638             }, {
639                 ref: '../summaryPanelFailures',
640                 baseCls: 'ux-arrowcollapse',
641                 cls: 'ux-arrowcollapse-plain',
642                 collapsible: true,
643                 hidden: true,
644                 flex: 1,
645                 title:'',
646                 items: [{
647                     xtype: 'grid',
648                     store: this.exceptionStore,
649                     autoHeight: true,
650                     plugins: exceptionExpander,
651                     columns: [
652                         exceptionExpander,
653                         { id: 'index', header: _('Index'), width: 60, sortable: false, dataIndex: 'index'}, 
654                         { id: 'failure', header: _('Failure'), width: 60, sortable: false, dataIndex: 'message'}
655                     ],
656                     autoExpandColumn: 'failure'
657                 }]
658             }],
659             listeners: {
660                 scope: this,
661                 show: this.onSummaryPanelShow
662             },
663             
664             /**
665              * finish button handler for this panel
666              */
667             onFinishButton: (function() {
668                 if (! this.importMask) {
669                     this.importMask = new Ext.LoadMask(this.getEl(), {msg: String.format(_('Importing {0}'), this.recordClass.getRecordsName())});
670                 }
671                 this.importMask.show();
672                 
673                 // collect client data
674                 var clientRecordData = [];
675                 var importOptions = {};
676                 
677                 this.exceptionStore.clearFilter(true);
678                 this.exceptionStore.each(function(r) {
679                     clientRecordData.push({
680                         recordData: r.get('resolvedRecord').data,
681                         resolveStrategy: r.get('resolveStrategy') || 'discard',
682                         index: r.get('index')
683                     });
684                 });
685                 
686                 this.doImport(function(request, success, response) {
687                     this.importMask.hide();
688                     
689                     this.fireEvent('finish', this, this.layout.activeItem);
690                     
691                     if (Ext.isArray(response.exceptions) && response.exceptions.length > 0) {
692                         // show errors and fence finish/back btn
693                         this.backButton.setDisabled(true);
694                         this.finishButton.setHandler(function() {this.window.close()}, this);
695                         
696                         this.summaryPanelInfo.hide();
697                         this.exceptionStore.clearFilter(true);
698                         
699                         this.summaryPanelFailures.show();
700                         this.summaryPanelFailures.setTitle(String.format(_('{0} records had failures and where discarded.'), response.exceptions.length));
701                     } else {
702                         this.window.close();
703                     }
704                 }, importOptions, clientRecordData);
705                 
706             }).createDelegate(this)
707             
708         }
709     },
710     
711     /**
712      * summary panel show handler
713      */
714     onSummaryPanelShow: function() {
715         if (! this.summaryPanelInfo.rendered) {
716             return this.onSummaryPanelShow.defer(100, this);
717         }
718
719         // calc metrics
720         var rsp = this.lastImportResponse,
721             totalcount = rsp.totalcount,
722             failcount = 0,
723             mergecount = 0,
724             discardcount = 0;
725             
726         this.exceptionStore.clearFilter();
727         this.exceptionStore.each(function(r) {
728             var strategy = r.get('resolveStrategy');
729             if (! strategy || !Ext.isString(strategy)) {
730                 failcount++;
731             } else if (strategy == 'keep') {
732                 totalcount++;
733             } else if (strategy.match(/^merge.*/)) {
734                 mergecount++;
735             } else if (strategy == 'discard') {
736                 discardcount++;
737             }
738         }, this);
739         
740         var tags = this.tagsPanel.getFormField().getValue(),
741             container = this.containerCombo.selectedContainer,
742             info = [String.format(_('In total we found {0} records in your import file.'), rsp.totalcount + rsp.duplicatecount + rsp.failcount)];
743             
744             if (totalcount) {
745                 info.push(String.format(_('{0} of them will be added as new records into: "{1}".'), 
746                     totalcount, 
747                     Tine.Tinebase.common.containerRenderer(container).replace('<div', '<span').replace('</div>', '</span>')
748                 ));
749             }
750             
751             if (mergecount + discardcount) {
752                 info.push(String.format(_('{0} of them where identified as duplicates.'), mergecount + discardcount));
753                 
754                 if (mergecount) {
755                     info.push(String.format(_('From the identified duplicates {0} will be merged into the existing records.'), mergecount));
756                 }
757                 
758                 if (discardcount) {
759                     info.push(String.format(_('From the identified duplicates {0} will be discarded.'), discardcount));
760                 }
761             }
762             
763             if (Ext.isArray(tags) && tags.length) {
764                 var tagNames = [],
765                     tagRecord = null;
766                 Ext.each(tags, function(tag) {
767                     if (Ext.isString(tag)) {
768                         tagRecord = this.tagsPanel.recordTagsStore.getById(tag);
769                         tag = (tagRecord) ? tagRecord.data : null;
770                     }
771                     if (tag) {
772                         tagNames.push(tag.name)
773                     }
774                 }, this);
775                 info.push(String.format(_('All records will be tagged with: "{0}" so you can find them easily.'), tagNames.join(', ')));
776             }
777             
778         this.summaryPanelInfo.update('<div style="padding: 5px;">' + info.join('<br />') + '</div>');
779         
780         // failures
781         if (failcount) {
782             this.exceptionStore.filterBy(function(record, id) {
783                 return ! (record.get('code') == 629 && ! record.get('isResolved'));
784             });
785             this.summaryPanelFailures.show();
786             this.summaryPanelFailures.setTitle(String.format(_('{0} records have failures and will be discarded.'), failcount));
787             
788         }
789     }
790 });
791
792 /**
793  * credentials dialog popup / window
794  */
795 Tine.widgets.dialog.ImportDialog.openWindow = function (config) {
796     var window = Tine.WindowFactory.getWindow({
797         width: 800,
798         height: 600,
799         name: Tine.widgets.dialog.ImportDialog.windowNamePrefix + Ext.id(),
800         contentPanelConstructor: 'Tine.widgets.dialog.ImportDialog',
801         contentPanelConstructorConfig: config//,
802 //        modal: true
803     });
804     return window;
805 };