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