smtp user config: checks if Felamimail is available
[tine20] / tine20 / Admin / js / user / EditDialog.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) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8  
9 /*global Ext, Tine*/
10
11 Ext.ns('Tine.Admin.user');
12
13 /**
14  * @namespace   Tine.Admin.user
15  * @class       Tine.Admin.UserEditDialog
16  * @extends     Tine.widgets.dialog.EditDialog
17  * 
18  * NOTE: this class dosn't use the user namespace as this is not yet supported by generic grid
19  * 
20  * <p>User Edit Dialog</p>
21  * <p>
22  * </p>
23  * 
24  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
25  * @author      Cornelius Weiss <c.weiss@metaways.de>
26  * 
27  * @param       {Object} config
28  * @constructor
29  * Create a new Tine.Admin.UserEditDialog
30  */
31 Tine.Admin.UserEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
32     
33     /**
34      * @private
35      */
36     windowNamePrefix: 'userEditWindow_',
37     appName: 'Admin',
38     recordClass: Tine.Admin.Model.User,
39     recordProxy: Tine.Admin.userBackend,
40     evalGrants: false,
41     
42     /**
43      * @private
44      */
45     initComponent: function () {
46         var accountBackend = Tine.Tinebase.registry.get('accountBackend');
47         this.ldapBackend = (accountBackend === 'Ldap' || accountBackend === 'ActiveDirectory');
48
49         Tine.Admin.UserEditDialog.superclass.initComponent.call(this);
50     },
51
52     /**
53      * @private
54      */
55     onRecordLoad: function () {
56         // interrupt process flow until dialog is rendered
57         if (! this.rendered) {
58             this.onRecordLoad.defer(250, this);
59             return;
60         }
61         
62         // samba user
63         var response = {
64             responseText: Ext.util.JSON.encode(this.record.get('sambaSAM'))
65         };
66         this.samRecord = Tine.Admin.samUserBackend.recordReader(response);
67         // email user
68         var emailResponse = {
69             responseText: Ext.util.JSON.encode(this.record.get('emailUser'))
70         };
71         this.emailRecord = Tine.Admin.emailUserBackend.recordReader(emailResponse);
72         
73         // format dates
74         var dateTimeDisplayFields = ['accountLastLogin', 'accountLastPasswordChange', 'logonTime', 'logoffTime', 'pwdLastSet'];
75         for (var i = 0; i < dateTimeDisplayFields.length; i += 1) {
76             if (dateTimeDisplayFields[i] === 'accountLastLogin' || dateTimeDisplayFields[i] === 'accountLastPasswordChange') {
77                 this.record.set(dateTimeDisplayFields[i], Tine.Tinebase.common.dateTimeRenderer(this.record.get(dateTimeDisplayFields[i])));
78             } else {
79                 this.samRecord.set(dateTimeDisplayFields[i], Tine.Tinebase.common.dateTimeRenderer(this.samRecord.get(dateTimeDisplayFields[i])));
80             }
81         }
82
83         this.getForm().loadRecord(this.emailRecord);
84         this.getForm().loadRecord(this.samRecord);
85         this.record.set('sambaSAM', this.samRecord.data);
86
87         if (Tine.Admin.registry.get('manageSmtpEmailUser')) {
88             if (this.emailRecord.get('emailAliases')) {
89                 this.aliasesGrid.setStoreFromArray(this.emailRecord.get('emailAliases'));
90             }
91             if (this.emailRecord.get('emailForwards')) {
92                 this.forwardsGrid.setStoreFromArray(this.emailRecord.get('emailForwards'));
93             }
94         }
95         
96         // load stores for memberships
97         if (this.record.id) {
98             this.storeGroups.loadData(this.record.get('groups'));
99             this.storeRoles.loadData(this.record.get('accountRoles'));
100         }
101         
102         Tine.Admin.UserEditDialog.superclass.onRecordLoad.call(this);
103     },
104     
105     /**
106      * @private
107      */
108     onRecordUpdate: function () {
109         Tine.Admin.UserEditDialog.superclass.onRecordUpdate.call(this);
110         
111         Tine.log.debug('Tine.Admin.UserEditDialog::onRecordUpdate()');
112         
113         var form = this.getForm();
114         form.updateRecord(this.samRecord);
115         if (this.samRecord.dirty) {
116             // only update sam record if something changed
117             this.unsetLocalizedDateTimeFields(this.samRecord, ['logonTime', 'logoffTime', 'pwdLastSet']);
118             this.record.set('sambaSAM', '');
119             this.record.set('sambaSAM', this.samRecord.data);
120         }
121
122         form.updateRecord(this.emailRecord);
123         // get aliases / forwards
124         if (Tine.Admin.registry.get('manageSmtpEmailUser')) {
125             // forcing blur of quickadd grids
126             this.aliasesGrid.doBlur();
127             this.forwardsGrid.doBlur();
128             this.emailRecord.set('emailAliases', this.aliasesGrid.getFromStoreAsArray());
129             this.emailRecord.set('emailForwards', this.forwardsGrid.getFromStoreAsArray());
130             Tine.log.debug('Tine.Admin.UserEditDialog::onRecordUpdate() -> setting aliases and forwards in email record');
131             Tine.log.debug(this.emailRecord);
132         }
133         this.unsetLocalizedDateTimeFields(this.emailRecord, ['emailLastLogin']);
134         this.record.set('emailUser', '');
135         this.record.set('emailUser', this.emailRecord.data);
136         
137         var newGroups = [],
138             newRoles = [];
139         
140         this.storeGroups.each(function (rec) {
141             newGroups.push(rec.data.id);
142         });
143         // add selected primary group to new groups if not exists
144         if (newGroups.indexOf(this.record.get('accountPrimaryGroup')) === -1) {
145             newGroups.push(this.record.get('accountPrimaryGroup'));
146         }
147          
148         this.storeRoles.each(function (rec) {
149             newRoles.push(rec.data.id);
150         });
151         
152         this.record.set('groups', newGroups);
153         this.record.set('accountRoles', newRoles);
154         
155         this.unsetLocalizedDateTimeFields(this.record, ['accountLastLogin', 'accountLastPasswordChange']);
156     },
157     
158     /**
159      * need to unset localized datetime fields before saving
160      * 
161      * @param {Object} record
162      * @param {Array} dateTimeDisplayFields
163      */
164     unsetLocalizedDateTimeFields: function(record, dateTimeDisplayFields) {
165         Ext.each(dateTimeDisplayFields, function (dateTimeDisplayField) {
166             record.set(dateTimeDisplayField, '');
167         }, this);
168     },
169
170     /**
171      * is form valid?
172      * 
173      * @return {Boolean}
174      */
175     isValid: function() {
176         var result = Tine.Admin.UserEditDialog.superclass.isValid.call(this);
177         if (! result) {
178             return false;
179         }
180         
181         if (Tine.Admin.registry.get('manageSmtpEmailUser')) {
182             var emailValue = this.getForm().findField('accountEmailAddress').getValue();
183             if (! this.checkEmailDomain(emailValue)) {
184                 result = false;
185                 this.getForm().markInvalid([{
186                     id: 'accountEmailAddress',
187                     msg: this.app.i18n._("Domain is not allowed. Check your SMTP domain configuration.")
188                 }]);
189             }
190         }
191         
192         return result;
193     },
194     
195     /**
196      * check valid email domain (if email domain is set in config)
197      * 
198      * @param {String} email
199      * @return {Boolean}
200      * 
201      * TODO use this for smtp aliases, too
202      */
203     checkEmailDomain: function(email) {
204         if (! Tine.Admin.registry.get('primarydomain') || ! email) {
205             return true;
206         }
207         
208         var allowedDomains = [Tine.Admin.registry.get('primarydomain')],
209             emailDomain = email.split('@')[1];
210             
211         if (Ext.isString(Tine.Admin.registry.get('secondarydomains'))) {
212             allowedDomains = allowedDomains.concat(Tine.Admin.registry.get('secondarydomains').split(','));
213         }
214         
215         return (allowedDomains.indexOf(emailDomain) !== -1);
216     },
217     
218     /**
219      * Validate confirmed password
220      */
221     onPasswordConfirm: function () {
222         var confirmForm = this.passwordConfirmWindow.items.first().getForm(),
223             confirmValues = confirmForm.getValues(),
224             passwordStatus = confirmForm.findField('passwordStatus'),
225             passwordField = (this.getForm()) ? this.getForm().findField('accountPassword') : null;
226         
227         if (! passwordField) {
228             // oops: something went wrong, this should not happen
229             return false;
230         }
231         
232         if (confirmValues.passwordRepeat !== passwordField.getValue()) {
233             passwordStatus.el.setStyle('color', 'red');
234             passwordStatus.setValue(this.app.i18n.gettext('Passwords do not match!'));
235             
236             passwordField.passwordsMatch = false;
237             passwordField.markInvalid(this.app.i18n.gettext('Passwords do not match!'));
238         } else {
239             passwordStatus.el.setStyle('color', 'green');
240             passwordStatus.setValue(this.app.i18n.gettext('Passwords match!'));
241                         
242             passwordField.passwordsMatch = true;
243             passwordField.clearInvalid();
244         }
245         
246         return passwordField.passwordsMatch ? passwordField.passwordsMatch : passwordStatus.getValue();
247     },
248     
249     /**
250      * Get current primary group (selected from combobox or default primary group)
251      * 
252      * @return {String} - id of current primary group
253      */
254     getCurrentPrimaryGroupId: function () {
255         return this.getForm().findField('accountPrimaryGroup').getValue() || this.record.get('accountPrimaryGroup').id;
256     },
257     
258     /**
259      * Init User groups picker grid
260      * 
261      * @return {Tine.widgets.account.PickerGridPanel}
262      */
263     initUserGroups: function () {
264         this.storeGroups = new Ext.data.JsonStore({
265             root: 'results',
266             totalProperty: 'totalcount',
267             id: 'id',
268             fields: Tine.Admin.Model.Group
269         });
270         
271         var self = this;
272         
273         this.pickerGridGroups = new Tine.widgets.account.PickerGridPanel({
274             border: false,
275             frame: false,
276             store: this.storeGroups,
277             selectType: 'group',
278             selectAnyone: false,
279             selectTypeDefault: 'group',
280             groupRecordClass: Tine.Admin.Model.Group,
281             getColumnModel: function () {
282                 return new Ext.grid.ColumnModel({
283                     defaults: { sortable: true },
284                     columns:  [
285                         {id: 'name', header: _('Name'), dataIndex: this.recordPrefix + 'name', renderer: function (val, meta, record) {
286                             return record.data.id === self.getCurrentPrimaryGroupId() ? (record.data.name + '<span class="x-item-disabled"> (' + self.app.i18n.gettext('Primary group') + ')<span>') : record.data.name;
287                         }}
288                     ]
289                 });
290             }
291         });
292         // disable remove of group if equal to current primary group
293         this.pickerGridGroups.selModel.on('beforerowselect', function (sm, index, keep, record) {
294             if (record.data.id === this.getCurrentPrimaryGroupId()) {
295                 return false;
296             }
297         }, this);
298         
299         return this.pickerGridGroups;
300     },
301     
302     /**
303      * Init User roles picker grid
304      * 
305      * @return {Tine.widgets.account.PickerGridPanel}
306      */
307     initUserRoles: function () {
308         this.storeRoles = new Ext.data.JsonStore({
309             root: 'results',
310             totalProperty: 'totalcount',
311             id: 'id',
312             fields: Tine.Tinebase.Model.Role
313         });
314         
315         this.pickerGridRoles = new Tine.widgets.grid.PickerGridPanel({
316             border: false,
317             frame: false,
318             autoExpandColumn: 'name',
319             store: this.storeRoles,
320             recordClass: Tine.Tinebase.Model.Role,
321             columns: [{id: 'name', header: Tine.Tinebase.translation.gettext('Name'), sortable: true, dataIndex: 'name'}],
322             initActionsAndToolbars: function () {
323                 // for now removed abillity to edit role membership
324 //                Tine.widgets.grid.PickerGridPanel.prototype.initActionsAndToolbars.call(this);
325 //                
326 //                this.comboPanel = new Ext.Container({
327 //                    layout: 'hfit',
328 //                    border: false,
329 //                    items: this.getSearchCombo(),
330 //                    columnWidth: 1
331 //                });
332 //                
333 //                this.tbar = new Ext.Toolbar({
334 //                    items: this.comboPanel,
335 //                    layout: 'column'
336 //                });
337             },
338             onAddRecordFromCombo: function (recordToAdd) {
339                 // check if already in
340                 if (! this.recordStore.getById(recordToAdd.id)) {
341                     this.recordStore.add([recordToAdd]);
342                 }
343                 this.collapse();
344                 this.clearValue();
345                 this.reset();
346             }
347         });
348         // remove listeners for this grid selection model
349         this.pickerGridRoles.selModel.purgeListeners();
350         
351         return this.pickerGridRoles;
352     },
353     
354     /**
355      * Init Fileserver tab items
356      * 
357      * @return {Array} - array ff fileserver tab items
358      */
359     initFileserver: function () {
360         if (this.ldapBackend) {
361             return [{
362                 xtype: 'fieldset',
363                 title: this.app.i18n.gettext('Unix'),
364                 autoHeight: true,
365                 checkboxToggle: false,
366                 layout: 'hfit',
367                 items: [{
368                     xtype: 'columnform',
369                     labelAlign: 'top',
370                     formDefaults: {
371                         xtype: 'textfield',
372                         anchor: '100%',
373                         labelSeparator: '',
374                         columnWidth: 0.333
375                     },
376                     items: [[{
377                         fieldLabel: this.app.i18n.gettext('Home Directory'),
378                         name: 'accountHomeDirectory',
379                         columnWidth: 0.666
380                     }, {
381                         fieldLabel: this.app.i18n.gettext('Login Shell'),
382                         name: 'accountLoginShell'
383                     }]]
384                 }]
385             }, {
386                 xtype: 'fieldset',
387                 title: this.app.i18n.gettext('Windows'),
388                 autoHeight: true,
389                 checkboxToggle: false,
390                 layout: 'hfit',
391                 items: [{
392                     xtype: 'columnform',
393                     labelAlign: 'top',
394                     formDefaults: {
395                         xtype: 'textfield',
396                         anchor: '100%',
397                         labelSeparator: '',
398                         columnWidth: 0.333
399                     },
400                     items: [[{
401                         fieldLabel: this.app.i18n.gettext('Home Drive'),
402                         name: 'homeDrive',
403                         columnWidth: 0.666
404                     }, {
405                         xtype: 'displayfield',
406                         fieldLabel: this.app.i18n.gettext('Logon Time'),
407                         name: 'logonTime',
408                         emptyText: this.app.i18n.gettext('never logged in'),
409                         style: this.displayFieldStyle
410                     }], [{
411                         fieldLabel: this.app.i18n.gettext('Home Path'),
412                         name: 'homePath',
413                         columnWidth: 0.666
414                     }, {
415                         xtype: 'displayfield',
416                         fieldLabel: this.app.i18n.gettext('Logoff Time'),
417                         name: 'logoffTime',
418                         emptyText: this.app.i18n.gettext('never logged off'),
419                         style: this.displayFieldStyle
420                     }], [{
421                         fieldLabel: this.app.i18n.gettext('Profile Path'),
422                         name: 'profilePath',
423                         columnWidth: 0.666
424                     }, {
425                         xtype: 'displayfield',
426                         fieldLabel: this.app.i18n.gettext('Password Last Set'),
427                         name: 'pwdLastSet',
428                         emptyText: this.app.i18n.gettext('never'),
429                         style: this.displayFieldStyle
430                     }], [{
431                         fieldLabel: this.app.i18n.gettext('Logon Script'),
432                         name: 'logonScript',
433                         columnWidth: 0.666
434                     }], [{
435                         xtype: 'extuxclearabledatefield',
436                         fieldLabel: this.app.i18n.gettext('Password Can Change'),
437                         name: 'pwdCanChange',
438                         emptyText: this.app.i18n.gettext('not set')
439                     }, {
440                         xtype: 'extuxclearabledatefield',
441                         fieldLabel: this.app.i18n.gettext('Password Must Change'),
442                         name: 'pwdMustChange',
443                         emptyText: this.app.i18n.gettext('not set')
444                     }, {
445                         xtype: 'extuxclearabledatefield',
446                         fieldLabel: this.app.i18n.gettext('Kick Off Time'),
447                         name: 'kickoffTime',
448                         emptyText: this.app.i18n.gettext('not set')
449                     }]]
450                 }]
451             }];
452         }
453         
454         return [];
455     },
456     
457     /**
458      * Init IMAP tab items
459      * 
460      * @return {Array} - array of IMAP tab items
461      */
462     initImap: function () {
463         if (Tine.Admin.registry.get('manageImapEmailUser')) {
464             return [{
465                 xtype: 'fieldset',
466                 title: this.app.i18n.gettext('IMAP Quota (MB)'),
467                 autoHeight: true,
468                 checkboxToggle: true,
469                 layout: 'hfit',
470                 listeners: {
471                     scope: this,
472                     collapse: function() {
473                         this.getForm().findField('emailMailQuota').setValue(null);
474                     }
475                 },
476                 items: [{
477                     xtype: 'columnform',
478                     labelAlign: 'top',
479                     formDefaults: {
480                         xtype: 'textfield',
481                         anchor: '100%',
482                         columnWidth: 0.666
483                     },
484                     items: [[{
485                         fieldLabel: this.app.i18n.gettext('Quota'),
486                         emptyText: this.app.i18n.gettext('no quota set'),
487                         name: 'emailMailQuota',
488                         xtype: 'uxspinner',
489                         strategy: new Ext.ux.form.Spinner.NumberStrategy({
490                             incrementValue : 10,
491                             alternateIncrementValue: 50,
492                             minValue: 0,
493                             allowDecimals : false
494                         })
495                     }], [{
496                         fieldLabel: this.app.i18n.gettext('Current Mailbox size'),
497                         name: 'emailMailSize',
498                         xtype: 'displayfield',
499                         style: this.displayFieldStyle
500                     }]]
501                 }]
502             }, {
503                 xtype: 'fieldset',
504                 title: this.app.i18n.gettext('Sieve Quota (MB)'),
505                 autoHeight: true,
506                 checkboxToggle: true,
507                 layout: 'hfit',
508                 listeners: {
509                     scope: this,
510                     collapse: function() {
511                         this.getForm().findField('emailSieveQuota').setValue(null);
512                     }
513                 },
514                 items: [{
515                     xtype: 'columnform',
516                     labelAlign: 'top',
517                     formDefaults: {
518                         xtype: 'textfield',
519                         anchor: '100%',
520                         columnWidth: 0.666
521                     },
522                     items: [[{
523                         fieldLabel: this.app.i18n.gettext('Quota'),
524                         emptyText: this.app.i18n.gettext('no quota set'),
525                         name: 'emailSieveQuota',
526                         xtype: 'uxspinner',
527                         strategy: new Ext.ux.form.Spinner.NumberStrategy({
528                             incrementValue : 10,
529                             alternateIncrementValue: 50,
530                             minValue: 0,
531                             allowDecimals : false
532                         })
533                     }], [{
534                         fieldLabel: this.app.i18n.gettext('Current Sieve size'),
535                         name: 'emailSieveSize',
536                         xtype: 'displayfield',
537                         style: this.displayFieldStyle
538                     }]
539                     ]
540                 }]
541             }, {
542                 xtype: 'fieldset',
543                 title: this.app.i18n.gettext('Information'),
544                 autoHeight: true,
545                 checkboxToggle: false,
546                 layout: 'hfit',
547                 items: [{
548                     xtype: 'columnform',
549                     labelAlign: 'top',
550                     formDefaults: {
551                         xtype: 'displayfield',
552                         anchor: '100%',
553                         columnWidth: 0.666,
554                         style: this.displayFieldStyle
555                     },
556                     items: [[{
557                         fieldLabel: this.app.i18n.gettext('Last Login'),
558                         name: 'emailLastLogin'
559                     }]]
560                 }]
561             }];
562         }
563         
564         return [];
565     },
566     
567     /**
568      * @private
569      * 
570      * init email grids
571      * @return Array
572      * 
573      * TODO     add ctx menu
574      */
575     initSmtp: function () {
576         if (! Tine.Admin.registry.get('manageSmtpEmailUser')) {
577             return [];
578         }
579         
580         var commonConfig = {
581             autoExpandColumn: 'email',
582             quickaddMandatory: 'email',
583             frame: false,
584             useBBar: true,
585             dataField: 'email',
586             height: 200,
587             columnWidth: 0.5,
588             recordClass: Ext.data.Record.create([
589                 { name: 'email' }
590             ])
591         };
592         
593         // TODO how to fetch smtp config if Felamimail isn't installed?
594         var smtpConfig = Tine.Felamimail ? Tine.Felamimail.registry.get('defaults').smtp : null;
595         var domains = (smtpConfig && smtpConfig.secondarydomains && smtpConfig.secondarydomains.length) ? smtpConfig.secondarydomains.split(',') : [];
596         if (smtpConfig && smtpConfig.primarydomain.length) {
597             domains.push(smtpConfig.primarydomain);
598         }
599         var app = this.app,
600             record = this.record;
601             
602         this.aliasesGrid = new Tine.widgets.grid.QuickaddGridPanel(
603             Ext.apply({
604                 onNewentry: function(value) {
605                     var split = value.email.split('@');
606                     if (split.length != 2 || split[1].split('.').length < 2) {
607                         return false;
608                     }
609                     var domain = split[1];
610                     if (domains.indexOf(domain) > -1) {
611                         Tine.widgets.grid.QuickaddGridPanel.prototype.onNewentry.call(this, value);
612                     } else {
613                         Ext.MessageBox.show({
614                             buttons: Ext.Msg.OK,
615                             icon: Ext.MessageBox.WARNING,
616                             title: app.i18n._('Domain not allowed'),
617                             msg: String.format(app.i18n._('The domain {0} of the alias {1} you tried to add is neither configured as primary domain nor set as a secondary domain in the setup.'
618                                 + ' Please add this domain to the secondary domains in SMTP setup or use another domain which is configured already.'),
619                                 '<b>' + domain + '</b>', '<b>' + value.email + '</b>')
620                         });
621                         return false;
622                     }
623                 },
624                 cm: new Ext.grid.ColumnModel([{
625                     id: 'email', 
626                     header: this.app.i18n.gettext('Email Alias'), 
627                     dataIndex: 'email', 
628                     width: 300, 
629                     hideable: false, 
630                     sortable: true,
631                     quickaddField: new Ext.form.TextField({
632                         emptyText: this.app.i18n.gettext('Add an alias address...'),
633                         vtype: 'email'
634                     }),
635                     editor: new Ext.form.TextField({allowBlank: false})
636                 }])
637             }, commonConfig)
638         );
639         this.aliasesGrid.render(document.body);
640         
641         var aliasesStore = this.aliasesGrid.getStore();
642
643         this.forwardsGrid = new Tine.widgets.grid.QuickaddGridPanel(
644             Ext.apply({
645                 onNewentry: function(value) {
646                     if (value.email === record.get('accountEmailAddress') || aliasesStore.find('email', value.email) !== -1) {
647                         Ext.MessageBox.show({
648                             buttons: Ext.Msg.OK,
649                             icon: Ext.MessageBox.WARNING,
650                             title: app.i18n._('Forwarding to self'),
651                             msg: app.i18n._('You are not allowed to set a forward email address that is identical to the users primary email or one of his aliases.')
652                         });
653                         return false;
654                     } else {
655                         Tine.widgets.grid.QuickaddGridPanel.prototype.onNewentry.call(this, value);
656                     }
657                 },
658                 cm: new Ext.grid.ColumnModel([{
659                     id: 'email', 
660                     header: this.app.i18n.gettext('Email Forward'), 
661                     dataIndex: 'email', 
662                     width: 300, 
663                     hideable: false, 
664                     sortable: true,
665                     quickaddField: new Ext.form.TextField({
666                         emptyText: this.app.i18n.gettext('Add a forward address...'),
667                         vtype: 'email'
668                     }),
669                     editor: new Ext.form.TextField({allowBlank: false}) 
670                 }])
671             }, commonConfig)
672         );
673         this.forwardsGrid.render(document.body);
674         
675         return [
676             [this.aliasesGrid, this.forwardsGrid],
677             [{hidden: true},
678              {
679                 fieldLabel: this.app.i18n.gettext('Forward Only'),
680                 name: 'emailForwardOnly',
681                 xtype: 'checkbox',
682                 readOnly: false
683             }]
684         ];
685     },
686     
687     /**
688      * @private
689      */
690     getFormItems: function () {
691         this.displayFieldStyle = {
692             border: 'silver 1px solid',
693             padding: '3px',
694             height: '11px'
695         };
696         
697         this.passwordConfirmWindow = new Ext.Window({
698             title: this.app.i18n.gettext('Password confirmation'),
699             closeAction: 'hide',
700             modal: true,
701             width: 300,
702             height: 150,
703             items: [{
704                 xtype: 'form',
705                 bodyStyle: 'padding: 5px;',
706                 buttonAlign: 'right',
707                 labelAlign: 'top',
708                 anchor: '100%',
709                 monitorValid: true,
710                 defaults: { anchor: '100%' },
711                 items: [{
712                     xtype: 'textfield',
713                     inputType: 'password',
714                     id: 'passwordRepeat',
715                     fieldLabel: this.app.i18n.gettext('Repeat password'), 
716                     name: 'passwordRepeat',
717                     validator: this.onPasswordConfirm.createDelegate(this),
718                     listeners: {
719                         scope: this,
720                         specialkey: function (field, event) {
721                             if (event.getKey() === event.ENTER) {
722                                 // call OK button handler
723                                 this.passwordConfirmWindow.items.first().buttons[1].handler.call(this);
724                             }
725                         }
726                     }
727                 }, {
728                     xtype: 'displayfield',
729                     hideLabel: true,
730                     id: 'passwordStatus',
731                     value: this.app.i18n.gettext('Passwords do not match!')
732                 }],
733                 buttons: [{
734                     text: _('Cancel'),
735                     iconCls: 'action_cancel',
736                     scope: this,
737                     handler: function () {
738                         this.passwordConfirmWindow.hide();
739                     }
740                 }, {
741                     text: _('Ok'),
742                     formBind: true,
743                     iconCls: 'action_saveAndClose',
744                     scope: this,
745                     handler: function () {
746                         var confirmForm = this.passwordConfirmWindow.items.first().getForm();
747                         
748                         // check if confirm form is valid (we need this if special key called button handler)
749                         if (confirmForm.isValid()) {
750                             this.passwordConfirmWindow.hide();
751                             // focus email field
752                             this.getForm().findField('accountEmailAddress').focus(true, 100);
753                         }
754                     }
755                 }]
756             }],
757             listeners: {
758                 scope: this,
759                 show: function (win) {
760                     var confirmForm = this.passwordConfirmWindow.items.first().getForm();
761                     
762                     confirmForm.reset();
763                     confirmForm.findField('passwordRepeat').focus(true, 500);
764                 }
765             }
766         });
767         this.passwordConfirmWindow.render(document.body);
768         
769         var config = {
770             xtype: 'tabpanel',
771             deferredRender: false,
772             border: false,
773             plain: true,
774             activeTab: 0,
775             items: [{
776                 title: this.app.i18n.gettext('Account'),
777                 autoScroll: true,
778                 border: false,
779                 frame: true,
780                 layout: 'hfit',
781                 items: [{
782                     xtype: 'columnform',
783                     labelAlign: 'top',
784                     formDefaults: {
785                         xtype: 'textfield',
786                         anchor: '100%',
787                         labelSeparator: '',
788                         columnWidth: 0.333
789                     },
790                     items: [[{
791                         fieldLabel: this.app.i18n.gettext('First name'),
792                         name: 'accountFirstName',
793                         columnWidth: 0.5,
794                         tabIndex: 1,
795                         listeners: {
796                             render: function (field) {
797                                 field.focus(false, 250);
798                                 field.selectText();
799                             }
800                         }
801                     }, {
802                         fieldLabel: this.app.i18n.gettext('Last name'),
803                         name: 'accountLastName',
804                         allowBlank: false,
805                         tabIndex: 2,
806                         columnWidth: 0.5
807                     }], [{
808                         fieldLabel: this.app.i18n.gettext('Login name'),
809                         name: 'accountLoginName',
810                         allowBlank: false,
811                         tabIndex: 3,
812                         columnWidth: 0.5
813                     }, {
814                         fieldLabel: this.app.i18n.gettext('Password'),
815                         id: 'accountPassword',
816                         name: 'accountPassword',
817                         inputType: 'password',
818                         columnWidth: 0.5,
819                         tabIndex: 4,
820                         passwordsMatch: true,
821                         enableKeyEvents: true,
822                         listeners: {
823                             scope: this,
824                             blur: function (field) {
825                                 var fieldValue = field.getValue();
826                                 if (fieldValue !== '') {
827                                     // show password confirmation
828                                     // NOTE: we can't use Ext.Msg.prompt because field has to be of inputType: 'password'
829                                     this.passwordConfirmWindow.show.defer(100, this.passwordConfirmWindow);
830                                 }
831                             },
832                             destroy: function () {
833                                 // destroy password confirm window
834                                 this.passwordConfirmWindow.destroy();
835                             },
836                             keydown: function (field) {
837                                 field.passwordsMatch = false;
838                             }
839                         },
840                         validateValue: function (value) {
841                             return this.passwordsMatch;
842                         }
843                     }], [{
844                         vtype: 'email',
845                         fieldLabel: this.app.i18n.gettext('Email'),
846                         tabIndex: 5,
847                         name: 'accountEmailAddress',
848                         id: 'accountEmailAddress',
849                         columnWidth: 0.5
850                     }, {
851                         //vtype: 'email',
852                         fieldLabel: this.app.i18n.gettext('OpenID'),
853                         emptyText: '(' + this.app.i18n.gettext('Login name') + ')',
854                         tabIndex: 6,
855                         name: 'openid',
856                         columnWidth: 0.5
857                     }], [{
858                         xtype: 'tinerecordpickercombobox',
859                         fieldLabel: this.app.i18n.gettext('Primary group'),
860                         tabIndex: 7,
861                         listWidth: 250,
862                         name: 'accountPrimaryGroup',
863                         blurOnSelect: true,
864                         allowBlank: false,
865                         recordClass: Tine.Admin.Model.Group,
866                         listeners: {
867                             scope: this,
868                             'select': function (combo, record, index) {
869                                 // refresh grid
870                                 if (this.pickerGridGroups) {
871                                     this.pickerGridGroups.getView().refresh();
872                                 }
873                             }
874                         }
875                     }, {
876                         xtype: 'combo',
877                         fieldLabel: this.app.i18n.gettext('Status'),
878                         name: 'accountStatus',
879                         mode: 'local',
880                         triggerAction: 'all',
881                         allowBlank: false,
882                         tabIndex: 8,
883                         editable: false,
884                         store: [
885                             ['enabled',  this.app.i18n.gettext('enabled')],
886                             ['disabled', this.app.i18n.gettext('disabled')],
887                             ['expired',  this.app.i18n.gettext('expired')],
888                             ['blocked',  this.app.i18n.gettext('blocked')]
889                         ],
890                         listeners: {
891                             scope: this,
892                             select: function (combo, record) {
893                                 switch (record.data.field1) {
894                                     case 'blocked':
895                                         Ext.Msg.alert(this.app.i18n._('Invalid Status'),
896                                             this.app.i18n._('Blocked status is only valid if the user tried to login with a wrong password to often. It is not possible to set this status here.'));
897                                         combo.setValue(combo.startValue);
898                                         break;
899                                     case 'expired':
900                                         this.getForm().findField('accountExpires').setValue(new Date());
901                                         break;
902                                     case 'enabled':
903                                         var expiryDateField = this.getForm().findField('accountExpires'),
904                                             expiryDate = expiryDateField.getValue(),
905                                             now = new Date();
906                                             
907                                         if (expiryDate < now) {
908                                             expiryDateField.setValue('');
909                                         }
910                                         break;
911                                     default:
912                                         // do nothing
913                                 }
914                             }
915                         }
916                     }, {
917                         xtype: 'extuxclearabledatefield',
918                         fieldLabel: this.app.i18n.gettext('Expires'),
919                         name: 'accountExpires',
920                         tabIndex: 9,
921                         emptyText: this.app.i18n.gettext('never')
922                     }], [{
923                         xtype: 'combo',
924                         fieldLabel: this.app.i18n.gettext('Visibility'),
925                         name: 'visibility',
926                         mode: 'local',
927                         tabIndex: 10,
928                         triggerAction: 'all',
929                         allowBlank: false,
930                         editable: false,
931                         store: [['displayed', this.app.i18n.gettext('Display in addressbook')], ['hidden', this.app.i18n.gettext('Hide from addressbook')]],
932                         listeners: {
933                             scope: this,
934                             select: function (combo, record) {
935                                 // disable container_id combo if hidden
936                                 var addressbookContainerCombo = this.getForm().findField('container_id');
937                                 addressbookContainerCombo.setDisabled(record.data.field1 === 'hidden');
938                                 if (addressbookContainerCombo.getValue() === '') {
939                                     addressbookContainerCombo.setValue(null);
940                                 }
941                             }
942                         }
943                     }, {
944                         xtype: 'tinerecordpickercombobox',
945                         fieldLabel: this.app.i18n.gettext('Saved in Addressbook'),
946                         name: 'container_id',
947                         blurOnSelect: true,
948                         tabIndex: 11,
949                         allowBlank: false,
950                         forceSelection: true,
951                         listWidth: 250,
952                         recordClass: Tine.Tinebase.Model.Container,
953                         disabled: this.record.get('visibility') === 'hidden',
954                         recordProxy: Tine.Admin.sharedAddressbookBackend,
955                         listeners: {
956                             specialkey: function(combo, e) {
957                                 if (e.getKey() == e.TAB && ! e.shiftKey) {
958                                     // move cursor to first input field (skip display fields)
959                                     // @see 0008226: when tabbing in user edit dialog, wrong tab content is displayed
960                                     e.preventDefault();
961                                     e.stopEvent();
962                                     this.getForm().findField('accountFirstName').focus();
963                                 }
964                             },
965                             scope: this
966                         }
967                     }]] 
968                 }, {
969                     xtype: 'fieldset',
970                     title: this.app.i18n.gettext('Information'),
971                     autoHeight: true,
972                     checkboxToggle: false,
973                     layout: 'hfit',
974                     items: [{
975                         xtype: 'columnform',
976                         labelAlign: 'top',
977                         formDefaults: {
978                             xtype: 'displayfield',
979                             anchor: '100%',
980                             labelSeparator: '',
981                             columnWidth: 0.333,
982                             style: this.displayFieldStyle
983                         },
984                         items: [[{
985                             fieldLabel: this.app.i18n.gettext('Last login at'),
986                             name: 'accountLastLogin',
987                             emptyText: this.ldapBackend ? this.app.i18n.gettext("don't know") : this.app.i18n.gettext('never logged in')
988                         }, {
989                             fieldLabel: this.app.i18n.gettext('Last login from'),
990                             name: 'accountLastLoginfrom',
991                             emptyText: this.ldapBackend ? this.app.i18n.gettext("don't know") : this.app.i18n.gettext('never logged in')
992                         }, {
993                             fieldLabel: this.app.i18n.gettext('Password set'),
994                             name: 'accountLastPasswordChange',
995                             emptyText: this.app.i18n.gettext('never')
996                         }]]
997                     }]
998                 }]
999             }, {
1000                 title: this.app.i18n.gettext('User groups'),
1001                 border: false,
1002                 frame: true,
1003                 layout: 'fit',
1004                 items: this.initUserGroups()
1005             }, {
1006                 title: this.app.i18n.gettext('User roles'),
1007                 border: false,
1008                 frame: true,
1009                 layout: 'fit',
1010                 items: this.initUserRoles()
1011             }, {
1012                 title: this.app.i18n.gettext('Fileserver'),
1013                 disabled: !this.ldapBackend,
1014                 border: false,
1015                 frame: true,
1016                 items: this.initFileserver()
1017             }, {
1018                 title: this.app.i18n.gettext('IMAP'),
1019                 disabled: ! Tine.Admin.registry.get('manageImapEmailUser'),
1020                 autoScroll: true,
1021                 border: false,
1022                 frame: true,
1023                 layout: 'hfit',
1024                 items: this.initImap()
1025             }, {
1026                 xtype: 'columnform',
1027                 title: this.app.i18n.gettext('SMTP'),
1028                 disabled: ! Tine.Admin.registry.get('manageSmtpEmailUser'),
1029                 border: false,
1030                 frame: true,
1031                 labelAlign: 'top',
1032                 formDefaults: {
1033                     xtype: 'textfield',
1034                     anchor: '100%',
1035                     labelSeparator: '',
1036                     columnWidth: 0.5,
1037                     readOnly: true
1038                 },
1039                 items: this.initSmtp()
1040             }]
1041         };
1042         return config;
1043     }
1044 });
1045
1046 /**
1047  * User Edit Popup
1048  * 
1049  * @param   {Object} config
1050  * @return  {Ext.ux.Window}
1051  */
1052 Tine.Admin.UserEditDialog.openWindow = function (config) {
1053     var id = (config.record && config.record.id) ? config.record.id : 0;
1054     var window = Tine.WindowFactory.getWindow({
1055         width: 600,
1056         height: 400,
1057         name: Tine.Admin.UserEditDialog.prototype.windowNamePrefix + id,
1058         contentPanelConstructor: 'Tine.Admin.UserEditDialog',
1059         contentPanelConstructorConfig: config
1060     });
1061     return window;
1062 };