Merge branch '2016.11-develop' into 2017.02
[tine20] / tine20 / Tinebase / js / LoginPanel.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-2010 Metaways Infosystems GmbH (http://www.metaways.de)
7  */
8
9 /*global Ext, Tine*/
10  
11 Ext.ns('Tine.Tinebase');
12
13 /**
14  * @namespace   Tine.Tinebase
15  * @class       Tine.Tinebase.LoginPanel
16  * @extends     Ext.Panel
17  * @author      Cornelius Weiss <c.weiss@metaways.de>
18  */
19 Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
20     
21     /**
22      * @cfg {String} defaultUsername prefilled username
23      */
24     defaultUsername: '',
25     
26     /**
27      * @cfg {String} defaultPassword prefilled password
28      */
29     defaultPassword: '',
30     
31     /**
32      * @cfg {String} loginMethod server side login method
33      */
34     loginMethod: 'Tinebase.login',
35     
36     /**
37      * @cfg {String} loginLogo logo to show
38      */
39     loginLogo: null,
40     
41     /**
42      * @cfg {String} onLogin callback after successfull login
43      */
44     onLogin: Ext.emptyFn,
45     
46     /**
47      * @cfg {Boolean} show infobox (survey, links, text)
48      */
49     showInfoBox: true,
50     
51     /**
52      * @cfg {String} scope scope of login callback
53      */
54     scope: null,
55     
56     layout: 'fit',
57     border: false,
58     
59     /**
60      * return loginPanel
61      * 
62      * @return {Ext.FromPanel}
63      */
64     getLoginPanel: function () {
65         //Do we have a cutom Logo for branding?
66         var modSsl = Tine.Tinebase.registry.get('modSsl'),
67             secondFactor = Tine.Tinebase.registry.get('secondFactorLogin'),
68             logo = this.loginLogo ? this.loginLogo : Tine.logo;
69         
70         if (! this.loginPanel) {
71             this.loginPanel = new Ext.FormPanel({
72                 width: 460,
73                 height: 290,
74                 frame: true,
75                 labelWidth: 90,
76                 cls: 'tb-login-panel',
77                 items: [{
78                     xtype: 'container',
79                     cls: 'tb-login-lobobox',
80                     border: false,
81                     html: '<a target="_blank" href="' + Tine.weburl + '" border="0"><img src="' + logo + '" /></a>'
82                 }, {
83                     xtype: 'label',
84                     cls: 'tb-login-big-label',
85                     text: i18n._('Login')
86                 }, {
87                     xtype: 'tinelangchooser',
88                     name: 'locale',
89                     width: 170,
90                     tabindex: 1
91                 }, {
92                     xtype: 'textfield',
93                     tabindex: 2,
94                     width: 170,
95                     fieldLabel: i18n._('Username'),
96                     name: 'username',
97
98                     allowBlank: modSsl ? false : true,
99                     validateOnBlur: false,
100                     selectOnFocus: true,
101                     value: this.defaultUsername ? this.defaultUsername : undefined,
102                     disabled: modSsl ? true : false,
103                     listeners: {
104                         scope: this,
105                         render: function (field) {
106                             field.el.dom.setAttribute('autocapitalize', 'none');
107                             field.el.dom.setAttribute('autocorrect', 'off');
108                             if (Ext.supportsUserFocus) {
109                                 field.focus(false, 250);
110                             }
111                         },
112                         focus: function(field) {
113                             if (Ext.isTouchDevice) {
114                                 Ext.getBody().dom.scrollTop = this.loginPanel.getBox()['y'] - 10;
115                             }
116                         }
117                     }
118                 }, {
119                     xtype: 'textfield',
120                     tabindex: 3,
121                     width: 170,
122                     inputType: 'password',
123                     fieldLabel: i18n._('Password'),
124                     name: 'password',
125                     selectOnFocus: true,
126                     value: this.defaultPassword,
127                     disabled: modSsl ? true : false,
128                     listeners: {
129                         render: this.setLastLoginUser.createDelegate(this) 
130                     }
131                 }, {
132                     xtype: 'textfield',
133                     tabindex: 4,
134                     width: 170,
135                     inputType: 'password',
136                     hidden: secondFactor ? false : true,
137                     fieldLabel: i18n._('Two-Factor Authentication Code'),
138                     id: 'otp',
139                     name: 'otp',
140                     selectOnFocus: true
141                 }, {
142                     xtype: 'displayfield',
143                     style: {
144                         align: 'center',
145                         marginTop: '10px'
146                     },
147                     value: i18n._('Certificate detected. Please, press Login button to proceed.'),
148                     hidden: modSsl ? false : true
149                 }, {
150                     xtype: 'container',
151                     id:'contImgCaptcha',
152                     layout: 'form',
153                     style: { visibility:'hidden' },
154                     items:[{
155                        xtype: 'textfield',
156                        width: 170,
157                        labelSeparator: '',
158                        id: 'security_code',
159                        value: null,
160                        name: 'securitycode'
161                     }, {
162                        fieldLabel:(' '),
163                        labelSeparator: '',
164                        items:[
165                            new Ext.Component({
166                                autoEl: { 
167                                    tag: 'img',
168                                    id: 'imgCaptcha'
169                                }
170                            })]
171                     }]
172                   }
173                 ],
174                 buttonAlign: 'right',
175                 buttons: [{
176                     xtype: 'button',
177                     width: 120,
178                     text: i18n._('Login'),
179                     scope: this,
180                     handler: this.onLoginPress
181                 }]
182             });
183         }
184         
185         return this.loginPanel;
186     },
187
188     setLastLoginUser: function (field) {
189         var lastUser;
190         lastUser = Ext.util.Cookies.get('TINE20LASTUSERID');
191         if (lastUser) {
192             this.loginPanel.getForm().findField('username').setValue(lastUser);
193             field.focus(false,250);
194         }
195     },
196     
197     getVersionPanel: function () {
198         if (! this.versionPanel) {
199             var version = (Tine.Tinebase.registry.get('version')) ? Tine.Tinebase.registry.get('version') : {
200                 codeName: 'unknown',
201                 packageString: 'unknown'
202             };
203             
204             var versionHtml = '<label class="tb-version-label">' + i18n._('Version') + ':</label> ' +
205                               '<label class="tb-version-codename">' + version.codeName + '</label> ' +
206                               '<label class="tb-version-packagestring">(' + version.packageString + ')</label>';
207             this.versionPanel = new Ext.Container({
208                 layout: 'fit',
209                 cls: 'tb-version-tinepanel',
210                 border: false,
211                 defaults: {xtype: 'label'},
212                 items: [{
213                     html: versionHtml
214                 }]
215             })
216         }
217
218         return this.versionPanel;
219     },
220      
221     getCommunityPanel: function () {
222         if (! this.communityPanel) {
223             var translationPanel = [],
224                 stats = Locale.translationStats,
225                 version = Tine.clientVersion.packageString.match(/\d+\.\d+\.\d+/),
226                 language = Tine.Tinebase.registry.get('locale').language,
227                 percentageCompleted = stats ? Math.floor(100 * stats.translated / stats.total) : undefined;
228                 
229             this.communityPanel = new Ext.Container({
230                 layout: 'fit',
231                 cls: 'tb-login-tinepanel',
232                 border: false,
233                 defaults: {xtype: 'label'},
234                 items: [{
235                     cls: 'tb-login-big-label',
236                     html: String.format(i18n._('{0} is made for you'), Tine.title)
237                 }, {
238                     html: '<p>' + String.format(i18n._('{0} wants to make business collaboration easier and more enjoyable - for your needs! So you are warmly welcome to discuss with us, bring in ideas and get help.'), Tine.title) + '</p>'
239                 }, {
240                     cls: 'tb-login-big-label-spacer',
241                     html: '&nbsp;'
242                 }, {
243                     html: '<ul>' + 
244                         '<li><a target="_blank" href="' + Tine.weburl + '" border="0">' + String.format(i18n._('{0} Homepage'), Tine.title) + '</a></li>' +
245                         '<li><a target="_blank" href="http://www.tine20.org/forum/" border="0">' + String.format(i18n._('{0} Forum'), Tine.title) + '</a></li>' +
246                     '</ul><br/>'
247                 }, {
248                     cls: 'tb-login-big-label',
249                     html: i18n._('Translations')
250                 }, {
251                     html: Ext.isDefined(percentageCompleted) ? ('<p>' + String.format(i18n._('Translation state of {0}: {1}%.'), language, percentageCompleted) + '</p>') : ''
252                 }, {
253                     html: '<p>' + String.format(i18n._('If the state of your language is not satisfying, or if you miss a language, please consider becoming a {0} translator.'), Tine.title) + '</p>'
254                 }, {
255                     html: '<br/><ul>' +
256                         '<li><a target="_blank" href="http://wiki.tine20.org/Contributors/Howtos/Translations" border="0">' + String.format(i18n._('{0} Translation Howto'), Tine.title) + '</a></li>' +
257                         '<li><a target="_blank" href="https://www.transifex.com/projects/p/tine20/" border="0">' + i18n._('Detailed Language Statistics') + '</a></li>'
258                     + '</ul>'
259                 }]
260             });
261         }
262         
263         return this.communityPanel;
264     },
265     
266     getPoweredByPanel: function () {
267         if (! this.poweredByPanel) {
268             this.poweredByPanel = new Ext.Container({
269                 layout: 'fit',
270                 cls: 'powered-by-panel',
271                 width: 200,
272                 height: 50,
273                 border: false,
274                 defaults: {xtype: 'label'},
275                 items: [{
276                     html: "<div class='tine-viewport-poweredby' style='position: absolute; bottom: 10px; right: 10px; font:normal 12px arial, helvetica,tahoma,sans-serif;'>" + 
277                         i18n._("Powered by:") + " <a target='_blank' href='" + Tine.weburl + "' title='" + i18n._("online open source groupware and crm") + "'>" + Tine.title + "</a>"
278                 }]
279             });
280         }
281         
282         return this.poweredByPanel;
283     },
284     
285     getSurveyData: function (cb) {
286         var ds = new Ext.data.Store({
287             proxy: new Ext.data.ScriptTagProxy({
288                 url: 'https://versioncheck.tine20.net/surveyCheck/surveyCheck.php'
289             }),
290             reader: new Ext.data.JsonReader({
291                 root: 'survey'
292             }, ['title', 'subtitle', 'duration', 'langs', 'link', 'enddate', 'htmlmessage', 'version'])
293         });
294         
295         ds.on('load', function (store, records) {
296             var survey = records[0];
297             
298             cb.call(this, survey);
299         }, this);
300         ds.load({params: {lang: Tine.Tinebase.registry.get('locale').locale}});
301     },
302     
303     getSurveyPanel: function () {
304         if (! this.surveyPanel) {
305             this.surveyPanel = new Ext.Container({
306                 layout: 'fit',
307                 cls: 'tb-login-surveypanel',
308                 border: false,
309                 defaults: {xtype: 'label'},
310                 items: []
311             });
312             
313             if (! Tine.Tinebase.registry.get('denySurveys')) {
314                 Tine.log.debug('getSurveyPanel() - fetching survey data ...');
315                 this.getSurveyData(function (survey) {
316                     Tine.log.debug(survey);
317                     if (typeof survey.get === 'function') {
318                         var enddate = Date.parseDate(survey.get('enddate'), Date.patterns.ISO8601Long);
319                         var version = survey.get('version');
320                         
321                         Tine.log.debug('Survey version: ' + version + ' / Tine version: ' + Tine.clientVersion.packageString) ;
322                         Tine.log.debug('Survey enddate: ' + enddate);
323                         
324                         if (Ext.isDate(enddate) && enddate.getTime() > new Date().getTime() && 
325                             Tine.clientVersion.packageString.indexOf(version) === 0) {
326                             Tine.log.debug('Show survey panel');
327                             survey.data.lang_duration = String.format(i18n._('about {0} minutes'), survey.data.duration);
328                             survey.data.link = 'https://versioncheck.tine20.net/surveyCheck/surveyCheck.php?participate';
329                             
330                             this.surveyPanel.add([{
331                                 cls: 'tb-login-big-label',
332                                 html: i18n._('Tine 2.0 needs your help')
333                             }, {
334                                 html: '<p>' + i18n._('We regularly need your feedback to make the next Tine 2.0 releases fit your needs even better. Help us and yourself by participating:') + '</p>'
335                             }, {
336                                 html: this.getSurveyTemplate().apply(survey.data)
337                             }, {
338                                 xtype: 'button',
339                                 width: 120,
340                                 text: i18n._('participate!'),
341                                 handler: function () {
342                                     window.open(survey.data.link);
343                                 }
344                             }]);
345                             this.surveyPanel.doLayout();
346                         }
347                     }
348                 });
349             }
350         }
351         
352         return this.surveyPanel;
353     },
354     
355     getSurveyTemplate: function () {
356         if (! this.surveyTemplate) {
357             this.surveyTemplate = new Ext.XTemplate(
358                 '<br/ >',
359                 '<p><b>{title}</b></p>',
360                 '<p><a target="_blank" href="{link}" border="0">{subtitle}</a></p>',
361                 '<br/>',
362                 '<p>', i18n._('Languages'), ': {langs}</p>',
363                 '<p>', i18n._('Duration'), ': {lang_duration}</p>',
364                 '<br/>').compile();
365         }
366         
367         return this.surveyTemplate;
368     },
369     
370     /**
371      * checks browser compatibility and show messages if unknown/incompatible
372      * 
373      * ie6, gecko2 -> bad
374      * unknown browser -> may not work
375      * 
376      * @return {Ext.Container}
377      * 
378      * TODO find icons with the correct license
379      */
380     getBrowserIncompatiblePanel: function() {
381         if (! this.browserIncompatiblePanel) {
382             this.browserIncompatiblePanel = new Ext.Container({
383                 layout: 'fit',
384                 cls: 'tb-login-surveypanel',
385                 border: false,
386                 defaults: {xtype: 'label'},
387                 items: []
388             });
389             
390             var browserSupport = 'compatible';
391             if (Ext.isIE6 || Ext.isGecko2) {
392                 browserSupport = 'incompatible';
393             } else if (
394                 ! (Ext.isWebKit || Ext.isGecko || Ext.isIE || Ext.isNewIE)
395             ) {
396                 // yepp we also mean -> Ext.isOpera
397                 browserSupport = 'unknown';
398             }
399             
400             var items = [];
401             if (browserSupport == 'incompatible') {
402                 items = [{
403                     cls: 'tb-login-big-label',
404                     html: i18n._('Browser incompatible')
405                 }, {
406                     html: '<p>' + i18n._('Your browser is not supported by Tine 2.0.') + '<br/><br/></p>'
407                 }];
408             } else if (browserSupport == 'unknown') {
409                 items = [{
410                     cls: 'tb-login-big-label',
411                     html: i18n._('Browser incompatible?')
412                 }, {
413                     html: '<p>' + i18n._('You are using an unrecognized browser. This could result in unexpected behaviour.') + '<br/><br/></p>'
414                 }];
415             }
416             
417             if (browserSupport != 'compatible') {
418                 this.browserIncompatiblePanel.add(items.concat([{
419                     html: '<p>' + i18n._('You might try one of these browsers:') + '<br/>'
420                         + '<a href="http://www.google.com/chrome" target="_blank">Google Chrome</a><br/>'
421                         + '<a href="http://www.mozilla.com/firefox/" target="_blank">Mozilla Firefox</a><br/>'
422                         + '<a href="http://www.apple.com/safari/download/" target="_blank">Apple Safari</a><br/>'    
423                         + '<a href="http://www.microsoft.com/windows/internet-explorer/default.aspx" target="_blank">Microsoft Internet Explorer</a>'
424                         + '<br/></p>'
425                 }]));
426                 this.browserIncompatiblePanel.doLayout();
427             }
428         }
429         
430         return this.browserIncompatiblePanel;
431     },
432     
433     initComponent: function () {
434         this.initLayout();
435         
436         this.supr().initComponent.call(this);
437     },
438     
439     initLayout: function () {
440         var infoPanelItems = (this.showInfoBox) ? [
441             this.getBrowserIncompatiblePanel(),
442             this.getCommunityPanel(),
443             this.getSurveyPanel()
444         ] : [];
445         
446         this.infoPanel = new Ext.Container({
447             cls: 'tb-login-infosection',
448             border: false,
449             width: 300,
450             height: 520, // bad idea to hardcode height here
451             layout: 'vbox',
452             layoutConfig: {
453                 align: 'stretch'
454             },
455             items: infoPanelItems
456         });
457         
458         this.items = [{
459             xtype: 'container',
460             layout: 'absolute',
461             border: false,
462             items: [
463                 this.getLoginPanel(),
464                 this.infoPanel,
465                 this.getPoweredByPanel(),
466                 this.getVersionPanel()
467             ]
468         }];
469     },
470     
471     /**
472      * do the actual login
473      */
474     onLoginPress: function () {
475         var form = this.getLoginPanel().getForm(),
476             values = form.getValues();
477             
478         if (form.isValid()) {
479             Ext.MessageBox.wait(i18n._('Logging you in...'), i18n._('Please wait'));
480
481             Ext.Ajax.request({
482                 scope: this,
483                 params : {
484                     method: this.loginMethod,
485                     username: values.username,
486                     password: values.password,
487                     securitycode: values.securitycode,
488                     otp: values.otp
489                 },
490                 timeout: 60000, // 1 minute
491                 success:function(response) {
492                     var responseData = Ext.util.JSON.decode(response.responseText);
493                     if (responseData.success === true) {
494                         Ext.MessageBox.wait(String.format(i18n._('Login successful. Loading {0}...'), Tine.title), i18n._('Please wait!'));
495                         window.document.title = this.originalTitle;
496                         response.responseData = responseData;
497                         this.onLogin.call(this.scope, response);
498                     } else {
499                         var modSsl = Tine.Tinebase.registry.get('modSsl');
500                         var resultMsg = modSsl ? i18n._('There was an error verifying your certificate!') :
501                             i18n._('Your username and/or your password are wrong!');
502                         Ext.MessageBox.show({
503                             title: i18n._('Login failure'),
504                             msg: resultMsg,
505                             buttons: Ext.MessageBox.OK,
506                             icon: Ext.MessageBox.ERROR,
507                             fn: function () {
508                                 this.getLoginPanel().getForm().findField('password').focus(true);
509                                 if(document.getElementById('useCaptcha')) {
510                                     if(typeof responseData.c1 != 'undefined') {
511                                         document.getElementById('imgCaptcha').src = 'data:image/png;base64,' + responseData.c1;
512                                         document.getElementById('contImgCaptcha').style.visibility = 'visible';
513                                     }
514                                 }
515                             }.createDelegate(this)
516                         });
517                     }
518                 }
519             });
520         } else {
521             Ext.MessageBox.alert(i18n._('Errors'), i18n._('Please fix the errors noted.'));
522         }
523     },
524     
525     onRender: function (ct, position) {
526         this.supr().onRender.apply(this, arguments);
527         
528         this.map = new Ext.KeyMap(this.el, [{
529             key : [10, 13],
530             scope : this,
531             fn : this.onLoginPress
532         }]);
533         
534         this.originalTitle = window.document.title;
535         var postfix = (Tine.Tinebase.registry.get('titlePostfix')) ? Tine.Tinebase.registry.get('titlePostfix') : '';
536         window.document.title = Ext.util.Format.stripTags(Tine.title + postfix + ' - ' + i18n._('Please enter your login data'));
537     },
538     
539     onResize: function () {
540         this.supr().onResize.apply(this, arguments);
541
542         var box      = this.getBox(),
543             loginBox = this.getLoginPanel().rendered ? this.getLoginPanel().getBox() : {width : this.getLoginPanel().width, height: this.getLoginPanel().height},
544             infoBox  = this.infoPanel.rendered ? this.infoPanel.getBox() : {width : this.infoPanel.width, height: this.infoPanel.height};
545
546         var top = (box.height - loginBox.height) / 2;
547         if (box.height - top < infoBox.height) {
548             top = box.height - infoBox.height;
549         }
550         
551         var loginLeft = (box.width - loginBox.width) / 2;
552         if (loginLeft + loginBox.width + infoBox.width > box.width) {
553             loginLeft = box.width - loginBox.width - infoBox.width;
554         }
555                 
556         this.getLoginPanel().setPosition(loginLeft, top);
557         this.infoPanel.setPosition(loginLeft + loginBox.width, top);
558         this.getPoweredByPanel().setPosition(box.width - this.poweredByPanel.width, box.height - this.poweredByPanel.height);
559     },
560     
561     renderSurveyPanel: function (survey) {
562         var items = [{
563             cls: 'tb-login-big-label',
564             html: i18n._('Tine 2.0 needs your help')
565         }, {
566             html: '<p>' + i18n._('We regularly need your feedback to make the next Tine 2.0 releases fit your needs even better. Help us and yourself by participating:') + '</p>'
567         }];
568     }
569 });