e41e82b38c1b8173a978dc51a20b2418e8c0e7f6
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / LoginRefresh.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * Module: TYPO3/CMS/Backend/LoginRefresh
16 * Task that periodically checks if a blocking event in the backend occurred and
17 * displays a proper dialog to the user.
18 */
19 define(['jquery', 'TYPO3/CMS/Backend/Notification', 'bootstrap'], function($, Typo3Notification) {
20 'use strict';
21
22 /**
23 *
24 * @type {{identifier: {loginrefresh: string, lockedModal: string, loginFormModal: string}, options: {modalConfig: {backdrop: string}}, webNotification: null, intervalTime: integer, intervalId: null, backendIsLocked: boolean, isTimingOut: boolean, $timeoutModal: string, $backendLockedModal: string, $loginForm: string, loginFramesetUrl: string, logoutUrl: string}}
25 * @exports TYPO3/CMS/Backend/LoginRefresh
26 */
27 var LoginRefresh = {
28 identifier: {
29 loginrefresh: 't3js-modal-loginrefresh',
30 lockedModal: 't3js-modal-backendlocked',
31 loginFormModal: 't3js-modal-backendloginform'
32 },
33 options: {
34 modalConfig: {
35 backdrop: 'static'
36 }
37 },
38 webNotification: null,
39 intervalTime: 60,
40 intervalId: null,
41 backendIsLocked: false,
42 isTimingOut: false,
43 $timeoutModal: '',
44 $backendLockedModal: '',
45 $loginForm: '',
46 loginFramesetUrl: '',
47 logoutUrl: ''
48 };
49
50 /**
51 * Starts the session check task (if not running already)
52 */
53 LoginRefresh.startTask = function() {
54 if (LoginRefresh.intervalId !== null) {
55 return;
56 }
57
58 // set interval to 60 seconds
59 var interval = 1000 * LoginRefresh.intervalTime;
60 LoginRefresh.intervalId = setInterval(LoginRefresh.checkActiveSession, interval);
61 };
62
63 /**
64 * Stops the session check task
65 */
66 LoginRefresh.stopTask = function() {
67 clearInterval(LoginRefresh.intervalId);
68 LoginRefresh.intervalId = null;
69 };
70
71 /**
72 * Generates a modal dialog as template.
73 *
74 * @param {String} identifier
75 * @returns {Object}
76 */
77 LoginRefresh.generateModal = function(identifier) {
78 return $('<div />', {
79 id: identifier,
80 class: 't3js-modal ' + identifier + ' modal modal-type-default modal-severity-notice modal-style-light modal-size-small fade'
81 }).append(
82 $('<div />', {class: 'modal-dialog'}).append(
83 $('<div />', {class: 'modal-content'}).append(
84 $('<div />', {class: 'modal-header'}).append(
85 $('<h4 />', {class: 'modal-title'})
86 ),
87 $('<div />', {class: 'modal-body'}),
88 $('<div />', {class: 'modal-footer'})
89 )
90 )
91 );
92 };
93
94 /**
95 * Set interval time
96 *
97 * @param {integer} intervalTime
98 */
99 LoginRefresh.setIntervalTime = function(intervalTime) {
100 // To avoid the integer overflow in setInterval, we limit the interval time to be one request per day
101 LoginRefresh.intervalTime = Math.min(intervalTime, 86400);
102 };
103
104 /**
105 * Set logout url
106 *
107 * @param {String} logoutUrl
108 */
109 LoginRefresh.setLogoutUrl = function(logoutUrl) {
110 LoginRefresh.logoutUrl = logoutUrl;
111 };
112
113 /**
114 * Generates the modal displayed on near session time outs
115 */
116 LoginRefresh.initializeTimeoutModal = function() {
117 LoginRefresh.$timeoutModal = LoginRefresh.generateModal(LoginRefresh.identifier.loginrefresh);
118 LoginRefresh.$timeoutModal.addClass('modal-severity-notice');
119 LoginRefresh.$timeoutModal.find('.modal-header h4').text(TYPO3.LLL.core.login_about_to_expire_title);
120 LoginRefresh.$timeoutModal.find('.modal-body').append(
121 $('<p />').text(TYPO3.LLL.core.login_about_to_expire),
122 $('<div />', {class: 'progress'}).append(
123 $('<div />', {
124 class: 'progress-bar progress-bar-warning progress-bar-striped active',
125 role: 'progressbar',
126 'aria-valuemin': '0',
127 'aria-valuemax': '100'
128 }).append(
129 $('<span />', {class: 'sr-only'})
130 )
131 )
132 );
133 LoginRefresh.$timeoutModal.find('.modal-footer').append(
134 $('<button />', {
135 class: 'btn btn-default',
136 'data-action': 'logout'
137 }).text(TYPO3.LLL.core.refresh_login_logout_button).on('click', function() {
138 top.location.href = LoginRefresh.logoutUrl;
139 }),
140 $('<button />', {
141 class: 'btn btn-primary t3js-active',
142 'data-action': 'refreshSession'
143 }).text(TYPO3.LLL.core.refresh_login_refresh_button).on('click', function() {
144 $.ajax({
145 url: TYPO3.settings.ajaxUrls['login_timedout'],
146 method: 'GET',
147 success: function() {
148 LoginRefresh.hideTimeoutModal();
149 }
150 });
151 })
152 );
153 LoginRefresh.registerDefaultModalEvents(LoginRefresh.$timeoutModal);
154
155 $('body').append(LoginRefresh.$timeoutModal);
156 };
157
158 /**
159 * Shows the timeout dialog. If the backend is not focused, a Web Notification
160 * is displayed, too.
161 */
162 LoginRefresh.showTimeoutModal = function() {
163 LoginRefresh.isTimingOut = true;
164 LoginRefresh.$timeoutModal.modal(LoginRefresh.options.modalConfig);
165 LoginRefresh.fillProgressbar(LoginRefresh.$timeoutModal);
166
167 if (typeof Notification !== 'undefined' && Notification.permission === 'granted' && !LoginRefresh.isPageActive()) {
168 LoginRefresh.webNotification = new Notification(TYPO3.LLL.core.login_about_to_expire_title, {
169 body: TYPO3.LLL.core.login_about_to_expire,
170 icon: '/typo3/sysext/backend/Resources/Public/Images/Logo.png'
171 });
172 LoginRefresh.webNotification.onclick = function() {
173 window.focus();
174 };
175 }
176 };
177
178 /**
179 * Hides the timeout dialog. If a Web Notification is displayed, close it too.
180 */
181 LoginRefresh.hideTimeoutModal = function() {
182 LoginRefresh.isTimingOut = false;
183 LoginRefresh.$timeoutModal.modal('hide');
184
185 if (typeof Notification !== 'undefined' && LoginRefresh.webNotification !== null) {
186 LoginRefresh.webNotification.close();
187 }
188 };
189
190 /**
191 * Generates the modal displayed if the backend is locked.
192 */
193 LoginRefresh.initializeBackendLockedModal = function() {
194 LoginRefresh.$backendLockedModal = LoginRefresh.generateModal(LoginRefresh.identifier.lockedModal);
195 LoginRefresh.$backendLockedModal.find('.modal-header h4').text(TYPO3.LLL.core.please_wait);
196 LoginRefresh.$backendLockedModal.find('.modal-body').append(
197 $('<p />').text(TYPO3.LLL.core.be_locked)
198 );
199 LoginRefresh.$backendLockedModal.find('.modal-footer').remove();
200
201 $('body').append(LoginRefresh.$backendLockedModal);
202 };
203
204 /**
205 * Shows the "backend locked" dialog.
206 */
207 LoginRefresh.showBackendLockedModal = function() {
208 LoginRefresh.$backendLockedModal.modal(LoginRefresh.options.modalConfig);
209 };
210
211 /**
212 * Hides the "backend locked" dialog.
213 */
214 LoginRefresh.hideBackendLockedModal = function() {
215 LoginRefresh.$backendLockedModal.modal('hide');
216 };
217
218 /**
219 * Generates the login form displayed if the session has timed out.
220 */
221 LoginRefresh.initializeLoginForm = function() {
222 if (TYPO3.configuration.showRefreshLoginPopup) {
223 // dialog is not required if "showRefreshLoginPopup" is enabled
224 return;
225 }
226
227 LoginRefresh.$loginForm = LoginRefresh.generateModal(LoginRefresh.identifier.loginFormModal);
228 LoginRefresh.$loginForm.addClass('modal-notice');
229 var refresh_login_title = String(TYPO3.LLL.core.refresh_login_title).replace('%s', TYPO3.configuration.username);
230 LoginRefresh.$loginForm.find('.modal-header h4').text(refresh_login_title);
231 LoginRefresh.$loginForm.find('.modal-body').append(
232 $('<p />').text(TYPO3.LLL.core.login_expired),
233 $('<form />', {
234 id: 'beLoginRefresh',
235 method: 'POST',
236 action: TYPO3.settings.ajaxUrls['login']
237 }).append(
238 $('<div />', {class: 'form-group'}).append(
239 $('<input />', {
240 type: 'password',
241 name: 'p_field',
242 autofocus: 'autofocus',
243 class: 'form-control',
244 placeholder: TYPO3.LLL.core.refresh_login_password,
245 'data-rsa-encryption': 't3-loginrefres-userident'
246 })
247 ),
248 $('<input />', {type: 'hidden', name: 'username', value: TYPO3.configuration.username}),
249 $('<input />', {type: 'hidden', name: 'userident', id: 't3-loginrefres-userident'})
250 )
251 );
252 LoginRefresh.$loginForm.find('.modal-footer').append(
253 $('<a />', {
254 href: LoginRefresh.logoutUrl,
255 class: 'btn btn-default'
256 }).text(TYPO3.LLL.core.refresh_exit_button),
257 $('<button />', {type: 'button', class: 'btn btn-primary', 'data-action': 'refreshSession'})
258 .text(TYPO3.LLL.core.refresh_login_button)
259 .on('click', function(e) {
260 LoginRefresh.$loginForm.find('form').submit();
261 })
262 );
263 LoginRefresh.registerDefaultModalEvents(LoginRefresh.$loginForm).on('submit', LoginRefresh.submitForm);
264 $('body').append(LoginRefresh.$loginForm);
265 if (require.specified('TYPO3/CMS/Rsaauth/RsaEncryptionModule')) {
266 require(['TYPO3/CMS/Rsaauth/RsaEncryptionModule'], function(RsaEncryption) {
267 RsaEncryption.registerForm($('#beLoginRefresh').get(0));
268 });
269 }
270 };
271
272 /**
273 * Shows the login form.
274 */
275 LoginRefresh.showLoginForm = function() {
276 // log off for sure
277 $.ajax({
278 url: TYPO3.settings.ajaxUrls['logout'],
279 method: 'GET',
280 success: function() {
281 if (TYPO3.configuration.showRefreshLoginPopup) {
282 LoginRefresh.showLoginPopup();
283 } else {
284 LoginRefresh.$loginForm.modal(LoginRefresh.options.modalConfig);
285 }
286 },
287 failure: function() {
288 alert('something went wrong');
289 }
290 });
291 };
292
293 /**
294 * Set login frameset url
295 */
296 LoginRefresh.setLoginFramesetUrl = function(loginFramesetUrl) {
297 LoginRefresh.loginFramesetUrl = loginFramesetUrl;
298 };
299
300 /**
301 * Opens the login form in a new window.
302 */
303 LoginRefresh.showLoginPopup = function() {
304 var vHWin = window.open(LoginRefresh.loginFramesetUrl, 'relogin_' + TYPO3.configuration.uniqueID, 'height=450,width=700,status=0,menubar=0,location=1');
305 if (vHWin) {
306 vHWin.focus();
307 }
308 };
309
310 /**
311 * Hides the login form.
312 */
313 LoginRefresh.hideLoginForm = function() {
314 LoginRefresh.$loginForm.modal('hide');
315 };
316
317 /**
318 * Fills the progressbar attached to the given modal.
319 */
320 LoginRefresh.fillProgressbar = function($activeModal) {
321 if (!LoginRefresh.isTimingOut) {
322 return;
323 }
324
325 var max = 100,
326 current = 0,
327 $progressBar = $activeModal.find('.progress-bar'),
328 $srText = $progressBar.children('.sr-only');
329
330 var progress = setInterval(function() {
331 var isOverdue = (current >= max);
332
333 if (!LoginRefresh.isTimingOut || isOverdue) {
334 clearInterval(progress);
335
336 if (isOverdue) {
337 // show login form
338 LoginRefresh.hideTimeoutModal();
339 LoginRefresh.showLoginForm();
340 }
341
342 // reset current
343 current = 0;
344 } else {
345 current += 1;
346 }
347
348 var percentText = (current) + '%';
349 $progressBar.css('width', percentText);
350 $srText.text(percentText);
351 }, 300);
352 };
353
354 /**
355 * Creates additional data based on the security level and "submits" the form
356 * via an AJAX request.
357 *
358 * @param {Event} event
359 */
360 LoginRefresh.submitForm = function(event) {
361 event.preventDefault();
362
363 var $form = LoginRefresh.$loginForm.find('form'),
364 $passwordField = $form.find('input[name=p_field]'),
365 $useridentField = $form.find('input[name=userident]'),
366 passwordFieldValue = $passwordField.val();
367
368 if (passwordFieldValue === '' && $useridentField.val() === '') {
369 Typo3Notification.error(TYPO3.LLL.core.refresh_login_failed, TYPO3.LLL.core.refresh_login_emptyPassword);
370 $passwordField.focus();
371 return;
372 }
373
374 if (passwordFieldValue) {
375 $useridentField.val(passwordFieldValue);
376 $passwordField.val('');
377 }
378
379 var postData = {
380 login_status: 'login'
381 };
382 $.each($form.serializeArray(), function(i, field) {
383 postData[field.name] = field.value;
384 });
385 $.ajax({
386 url: $form.attr('action'),
387 method: 'POST',
388 data: postData,
389 success: function(response) {
390 var result = response.login;
391 if (result.success) {
392 // User is logged in
393 LoginRefresh.hideLoginForm();
394 } else {
395 Typo3Notification.error(TYPO3.LLL.core.refresh_login_failed, TYPO3.LLL.core.refresh_login_failed_message);
396 $passwordField.focus();
397 }
398 }
399 });
400 };
401
402 /**
403 * Registers the (shown|hidden).bs.modal events.
404 * If a modal is shown, the interval check is stopped. If the modal hides,
405 * the interval check starts again.
406 * This method is not invoked for the backend locked modal, because we still
407 * need to check if the backend gets unlocked again.
408 *
409 * @param {Object} $modal
410 * @returns {Object}
411 */
412 LoginRefresh.registerDefaultModalEvents = function($modal) {
413 $modal.on('hidden.bs.modal', function() {
414 LoginRefresh.startTask();
415 }).on('shown.bs.modal', function() {
416 LoginRefresh.stopTask();
417 // focus the button which was configured as active button
418 LoginRefresh.$timeoutModal.find('.modal-footer .t3js-active').first().focus();
419 });
420
421 return $modal;
422 };
423
424 /**
425 * Checks if the user is in focus of the backend.
426 * Thanks to http://stackoverflow.com/a/19519701
427 */
428 LoginRefresh.isPageActive = function() {
429 var stateKey, eventKey, keys = {
430 hidden: 'visibilitychange',
431 webkitHidden: 'webkitvisibilitychange',
432 mozHidden: 'mozvisibilitychange',
433 msHidden: 'msvisibilitychange'
434 };
435
436 for (stateKey in keys) {
437 if (stateKey in document) {
438 eventKey = keys[stateKey];
439 break;
440 }
441 }
442 return function(c) {
443 if (c) {
444 document.addEventListener(eventKey, c);
445 }
446 return !document[stateKey];
447 }();
448 };
449
450 /**
451 * Periodically called task that checks if
452 *
453 * - the user's backend session is about to expire
454 * - the user's backend session has expired
455 * - the backend got locked
456 *
457 * and opens a dialog.
458 */
459 LoginRefresh.checkActiveSession = function() {
460 $.ajax({
461 url: TYPO3.settings.ajaxUrls['login_timedout'],
462 data: {
463 skipSessionUpdate: 1
464 },
465 success: function(response) {
466 if (response.login.locked) {
467 if (!LoginRefresh.backendIsLocked) {
468 LoginRefresh.backendIsLocked = true;
469 LoginRefresh.showBackendLockedModal();
470 }
471 } else {
472 if (LoginRefresh.backendIsLocked) {
473 LoginRefresh.backendIsLocked = false;
474 LoginRefresh.hideBackendLockedModal();
475 }
476 }
477
478 if (!LoginRefresh.backendIsLocked) {
479 if (response.login.timed_out || response.login.will_time_out) {
480 if (response.login.timed_out) {
481 LoginRefresh.showLoginForm();
482 } else {
483 LoginRefresh.showTimeoutModal();
484 }
485 }
486 }
487 }
488 });
489 };
490
491 LoginRefresh.initialize = function() {
492 LoginRefresh.initializeTimeoutModal();
493 LoginRefresh.initializeBackendLockedModal();
494 LoginRefresh.initializeLoginForm();
495
496 LoginRefresh.startTask();
497
498 if (typeof Notification !== 'undefined' && Notification.permission !== 'granted') {
499 Notification.requestPermission();
500 }
501 };
502
503 // expose to global
504 TYPO3.LoginRefresh = LoginRefresh;
505
506 return LoginRefresh;
507 });