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