c01490954bf55354d8e936a38d7e74c9f13113ed
[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         var smtpConfig = Tine.Felamimail.registry.get('defaults').smtp;
594         var domains = (smtpConfig.secondarydomains && smtpConfig.secondarydomains.length) ? smtpConfig.secondarydomains.split(',') : [];
595         if (smtpConfig.primarydomain.length) {
596             domains.push(smtpConfig.primarydomain);
597         }
598         var app = this.app,
599             record = this.record;
600             
601         this.aliasesGrid = new Tine.widgets.grid.QuickaddGridPanel(
602             Ext.apply({
603                 onNewentry: function(value) {
604                     var split = value.email.split('@');
605                     if (split.length != 2 || split[1].split('.').length < 2) {
606                         return false;
607                     }
608                     var domain = split[1];
609                     if (domains.indexOf(domain) > -1) {
610                         Tine.widgets.grid.QuickaddGridPanel.prototype.onNewentry.call(this, value);
611                     } else {
612                         Ext.MessageBox.show({
613                             buttons: Ext.Msg.OK,
614                             icon: Ext.MessageBox.WARNING,
615                             title: app.i18n._('Domain not allowed'),
616                             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.'
617                                 + ' Please add this domain to the secondary domains in SMTP setup or use another domain which is configured already.'),
618                                 '<b>' + domain + '</b>', '<b>' + value.email + '</b>')
619                         });
620                         return false;
621                     }
622                 },
623                 cm: new Ext.grid.ColumnModel([{
624                     id: 'email', 
625                     header: this.app.i18n.gettext('Email Alias'), 
626                     dataIndex: 'email', 
627                     width: 300, 
628                     hideable: false, 
629                     sortable: true,
630                     quickaddField: new Ext.form.TextField({
631                         emptyText: this.app.i18n.gettext('Add an alias address...'),
632                         vtype: 'email'
633                     }),
634                     editor: new Ext.form.TextField({allowBlank: false})
635                 }])
636             }, commonConfig)
637         );
638         this.aliasesGrid.render(document.body);
639         
640         var aliasesStore = this.aliasesGrid.getStore();
641
642         this.forwardsGrid = new Tine.widgets.grid.QuickaddGridPanel(
643             Ext.apply({
644                 onNewentry: function(value) {
645                     if (value.email === record.get('accountEmailAddress') || aliasesStore.find('email', value.email) !== -1) {
646                         Ext.MessageBox.show({
647                             buttons: Ext.Msg.OK,
648                             icon: Ext.MessageBox.WARNING,
649                             title: app.i18n._('Forwarding to self'),
650                             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.')
651                         });
652                         return false;
653                     } else {
654                         Tine.widgets.grid.QuickaddGridPanel.prototype.onNewentry.call(this, value);
655                     }
656                 },
657                 cm: new Ext.grid.ColumnModel([{
658                     id: 'email', 
659                     header: this.app.i18n.gettext('Email Forward'), 
660                     dataIndex: 'email', 
661                     width: 300, 
662                     hideable: false, 
663                     sortable: true,
664                     quickaddField: new Ext.form.TextField({
665                         emptyText: this.app.i18n.gettext('Add a forward address...'),
666                         vtype: 'email'
667                     }),
668                     editor: new Ext.form.TextField({allowBlank: false}) 
669                 }])
670             }, commonConfig)
671         );
672         this.forwardsGrid.render(document.body);
673         
674         return [
675             [this.aliasesGrid, this.forwardsGrid],
676             [{hidden: true},
677              {
678                 fieldLabel: this.app.i18n.gettext('Forward Only'),
679                 name: 'emailForwardOnly',
680                 xtype: 'checkbox',
681                 readOnly: false
682             }]
683         ];
684     },
685     
686     /**
687      * @private
688      */
689     getFormItems: function () {
690         this.displayFieldStyle = {
691             border: 'silver 1px solid',
692             padding: '3px',
693             height: '11px'
694         };
695         
696         this.passwordConfirmWindow = new Ext.Window({
697             title: this.app.i18n.gettext('Password confirmation'),
698             closeAction: 'hide',
699             modal: true,
700             width: 300,
701             height: 150,
702             items: [{
703                 xtype: 'form',
704                 bodyStyle: 'padding: 5px;',
705                 buttonAlign: 'right',
706                 labelAlign: 'top',
707                 anchor: '100%',
708                 monitorValid: true,
709                 defaults: { anchor: '100%' },
710                 items: [{
711                     xtype: 'textfield',
712                     inputType: 'password',
713                     id: 'passwordRepeat',
714                     fieldLabel: this.app.i18n.gettext('Repeat password'), 
715                     name: 'passwordRepeat',
716                     validator: this.onPasswordConfirm.createDelegate(this),
717                     listeners: {
718                         scope: this,
719                         specialkey: function (field, event) {
720                             if (event.getKey() === event.ENTER) {
721                                 // call OK button handler
722                                 this.passwordConfirmWindow.items.first().buttons[1].handler.call(this);
723                             }
724                         }
725                     }
726                 }, {
727                     xtype: 'displayfield',
728                     hideLabel: true,
729                     id: 'passwordStatus',
730                     value: this.app.i18n.gettext('Passwords do not match!')
731                 }],
732                 buttons: [{
733                     text: _('Cancel'),
734                     iconCls: 'action_cancel',
735                     scope: this,
736                     handler: function () {
737                         this.passwordConfirmWindow.hide();
738                     }
739                 }, {
740                     text: _('Ok'),
741                     formBind: true,
742                     iconCls: 'action_saveAndClose',
743                     scope: this,
744                     handler: function () {
745                         var confirmForm = this.passwordConfirmWindow.items.first().getForm();
746                         
747                         // check if confirm form is valid (we need this if special key called button handler)
748                         if (confirmForm.isValid()) {
749                             this.passwordConfirmWindow.hide();
750                             // focus email field
751                             this.getForm().findField('accountEmailAddress').focus(true, 100);
752                         }
753                     }
754                 }]
755             }],
756             listeners: {
757                 scope: this,
758                 show: function (win) {
759                     var confirmForm = this.passwordConfirmWindow.items.first().getForm();
760                     
761                     confirmForm.reset();
762                     confirmForm.findField('passwordRepeat').focus(true, 500);
763                 }
764             }
765         });
766         this.passwordConfirmWindow.render(document.body);
767         
768         var config = {
769             xtype: 'tabpanel',
770             deferredRender: false,
771             border: false,
772             plain: true,
773             activeTab: 0,
774             items: [{
775                 title: this.app.i18n.gettext('Account'),
776                 autoScroll: true,
777                 border: false,
778                 frame: true,
779                 layout: 'hfit',
780                 items: [{
781                     xtype: 'columnform',
782                     labelAlign: 'top',
783                     formDefaults: {
784                         xtype: 'textfield',
785                         anchor: '100%',
786                         labelSeparator: '',
787                         columnWidth: 0.333
788                     },
789                     items: [[{
790                         fieldLabel: this.app.i18n.gettext('First name'),
791                         name: 'accountFirstName',
792                         columnWidth: 0.5,
793                         tabIndex: 1,
794                         listeners: {
795                             render: function (field) {
796                                 field.focus(false, 250);
797                                 field.selectText();
798                             }
799                         }
800                     }, {
801                         fieldLabel: this.app.i18n.gettext('Last name'),
802                         name: 'accountLastName',
803                         allowBlank: false,
804                         tabIndex: 2,
805                         columnWidth: 0.5
806                     }], [{
807                         fieldLabel: this.app.i18n.gettext('Login name'),
808                         name: 'accountLoginName',
809                         allowBlank: false,
810                         tabIndex: 3,
811                         columnWidth: 0.5
812                     }, {
813                         fieldLabel: this.app.i18n.gettext('Password'),
814                         id: 'accountPassword',
815                         name: 'accountPassword',
816                         inputType: 'password',
817                         columnWidth: 0.5,
818                         tabIndex: 4,
819                         passwordsMatch: true,
820                         enableKeyEvents: true,
821                         listeners: {
822                             scope: this,
823                             blur: function (field) {
824                                 var fieldValue = field.getValue();
825                                 if (fieldValue !== '') {
826                                     // show password confirmation
827                                     // NOTE: we can't use Ext.Msg.prompt because field has to be of inputType: 'password'
828                                     this.passwordConfirmWindow.show.defer(100, this.passwordConfirmWindow);
829                                 }
830                             },
831                             destroy: function () {
832                                 // destroy password confirm window
833                                 this.passwordConfirmWindow.destroy();
834                             },
835                             keydown: function (field) {
836                                 field.passwordsMatch = false;
837                             }
838                         },
839                         validateValue: function (value) {
840                             return this.passwordsMatch;
841                         }
842                     }], [{
843                         vtype: 'email',
844                         fieldLabel: this.app.i18n.gettext('Email'),
845                         tabIndex: 5,
846                         name: 'accountEmailAddress',
847                         id: 'accountEmailAddress',
848                         columnWidth: 0.5
849                     }, {
850                         //vtype: 'email',
851                         fieldLabel: this.app.i18n.gettext('OpenID'),
852                         emptyText: '(' + this.app.i18n.gettext('Login name') + ')',
853                         tabIndex: 6,
854                         name: 'openid',
855                         columnWidth: 0.5
856                     }], [{
857                         xtype: 'tinerecordpickercombobox',
858                         fieldLabel: this.app.i18n.gettext('Primary group'),
859                         tabIndex: 7,
860                         listWidth: 250,
861                         name: 'accountPrimaryGroup',
862                         blurOnSelect: true,
863                         allowBlank: false,
864                         recordClass: Tine.Admin.Model.Group,
865                         listeners: {
866                             scope: this,
867                             'select': function (combo, record, index) {
868                                 // refresh grid
869                                 if (this.pickerGridGroups) {
870                                     this.pickerGridGroups.getView().refresh();
871                                 }
872                             }
873                         }
874                     }, {
875                         xtype: 'combo',
876                         fieldLabel: this.app.i18n.gettext('Status'),
877                         name: 'accountStatus',
878                         mode: 'local',
879                         triggerAction: 'all',
880                         allowBlank: false,
881                         tabIndex: 8,
882                         editable: false,
883                         store: [
884                             ['enabled',  this.app.i18n.gettext('enabled')],
885                             ['disabled', this.app.i18n.gettext('disabled')],
886                             ['expired',  this.app.i18n.gettext('expired')],
887                             ['blocked',  this.app.i18n.gettext('blocked')]
888                         ],
889                         listeners: {
890                             scope: this,
891                             select: function (combo, record) {
892                                 switch (record.data.field1) {
893                                     case 'blocked':
894                                         Ext.Msg.alert(this.app.i18n._('Invalid Status'),
895                                             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.'));
896                                         combo.setValue(combo.startValue);
897                                         break;
898                                     case 'expired':
899                                         this.getForm().findField('accountExpires').setValue(new Date());
900                                         break;
901                                     case 'enabled':
902                                         var expiryDateField = this.getForm().findField('accountExpires'),
903                                             expiryDate = expiryDateField.getValue(),
904                                             now = new Date();
905                                             
906                                         if (expiryDate < now) {
907                                             expiryDateField.setValue('');
908                                         }
909                                         break;
910                                     default:
911                                         // do nothing
912                                 }
913                             }
914                         }
915                     }, {
916                         xtype: 'extuxclearabledatefield',
917                         fieldLabel: this.app.i18n.gettext('Expires'),
918                         name: 'accountExpires',
919                         tabIndex: 9,
920                         emptyText: this.app.i18n.gettext('never')
921                     }], [{
922                         xtype: 'combo',
923                         fieldLabel: this.app.i18n.gettext('Visibility'),
924                         name: 'visibility',
925                         mode: 'local',
926                         tabIndex: 10,
927                         triggerAction: 'all',
928                         allowBlank: false,
929                         editable: false,
930                         store: [['displayed', this.app.i18n.gettext('Display in addressbook')], ['hidden', this.app.i18n.gettext('Hide from addressbook')]],
931                         listeners: {
932                             scope: this,
933                             select: function (combo, record) {
934                                 // disable container_id combo if hidden
935                                 var addressbookContainerCombo = this.getForm().findField('container_id');
936                                 addressbookContainerCombo.setDisabled(record.data.field1 === 'hidden');
937                                 if (addressbookContainerCombo.getValue() === '') {
938                                     addressbookContainerCombo.setValue(null);
939                                 }
940                             }
941                         }
942                     }, {
943                         xtype: 'tinerecordpickercombobox',
944                         fieldLabel: this.app.i18n.gettext('Saved in Addressbook'),
945                         name: 'container_id',
946                         blurOnSelect: true,
947                         tabIndex: 11,
948                         allowBlank: false,
949                         forceSelection: true,
950                         listWidth: 250,
951                         recordClass: Tine.Tinebase.Model.Container,
952                         disabled: this.record.get('visibility') === 'hidden',
953                         recordProxy: Tine.Admin.sharedAddressbookBackend,
954                         listeners: {
955                             specialkey: function(combo, e) {
956                                 if (e.getKey() == e.TAB && ! e.shiftKey) {
957                                     // move cursor to first input field (skip display fields)
958                                     // @see 0008226: when tabbing in user edit dialog, wrong tab content is displayed
959                                     e.preventDefault();
960                                     e.stopEvent();
961                                     this.getForm().findField('accountFirstName').focus();
962                                 }
963                             },
964                             scope: this
965                         }
966                     }]] 
967                 }, {
968                     xtype: 'fieldset',
969                     title: this.app.i18n.gettext('Information'),
970                     autoHeight: true,
971                     checkboxToggle: false,
972                     layout: 'hfit',
973                     items: [{
974                         xtype: 'columnform',
975                         labelAlign: 'top',
976                         formDefaults: {
977                             xtype: 'displayfield',
978                             anchor: '100%',
979                             labelSeparator: '',
980                             columnWidth: 0.333,
981                             style: this.displayFieldStyle
982                         },
983                         items: [[{
984                             fieldLabel: this.app.i18n.gettext('Last login at'),
985                             name: 'accountLastLogin',
986                             emptyText: this.ldapBackend ? this.app.i18n.gettext("don't know") : this.app.i18n.gettext('never logged in')
987                         }, {
988                             fieldLabel: this.app.i18n.gettext('Last login from'),
989                             name: 'accountLastLoginfrom',
990                             emptyText: this.ldapBackend ? this.app.i18n.gettext("don't know") : this.app.i18n.gettext('never logged in')
991                         }, {
992                             fieldLabel: this.app.i18n.gettext('Password set'),
993                             name: 'accountLastPasswordChange',
994                             emptyText: this.app.i18n.gettext('never')
995                         }]]
996                     }]
997                 }]
998             }, {
999                 title: this.app.i18n.gettext('User groups'),
1000                 border: false,
1001                 frame: true,
1002                 layout: 'fit',
1003                 items: this.initUserGroups()
1004             }, {
1005                 title: this.app.i18n.gettext('User roles'),
1006                 border: false,
1007                 frame: true,
1008                 layout: 'fit',
1009                 items: this.initUserRoles()
1010             }, {
1011                 title: this.app.i18n.gettext('Fileserver'),
1012                 disabled: !this.ldapBackend,
1013                 border: false,
1014                 frame: true,
1015                 items: this.initFileserver()
1016             }, {
1017                 title: this.app.i18n.gettext('IMAP'),
1018                 disabled: ! Tine.Admin.registry.get('manageImapEmailUser'),
1019                 autoScroll: true,
1020                 border: false,
1021                 frame: true,
1022                 layout: 'hfit',
1023                 items: this.initImap()
1024             }, {
1025                 xtype: 'columnform',
1026                 title: this.app.i18n.gettext('SMTP'),
1027                 disabled: ! Tine.Admin.registry.get('manageSmtpEmailUser'),
1028                 border: false,
1029                 frame: true,
1030                 labelAlign: 'top',
1031                 formDefaults: {
1032                     xtype: 'textfield',
1033                     anchor: '100%',
1034                     labelSeparator: '',
1035                     columnWidth: 0.5,
1036                     readOnly: true
1037                 },
1038                 items: this.initSmtp()
1039             }]
1040         };
1041         return config;
1042     }
1043 });
1044
1045 /**
1046  * User Edit Popup
1047  * 
1048  * @param   {Object} config
1049  * @return  {Ext.ux.Window}
1050  */
1051 Tine.Admin.UserEditDialog.openWindow = function (config) {
1052     var id = (config.record && config.record.id) ? config.record.id : 0;
1053     var window = Tine.WindowFactory.getWindow({
1054         width: 600,
1055         height: 400,
1056         name: Tine.Admin.UserEditDialog.prototype.windowNamePrefix + id,
1057         contentPanelConstructor: 'Tine.Admin.UserEditDialog',
1058         contentPanelConstructorConfig: config
1059     });
1060     return window;
1061 };