dae798c7411ab494c3faddcba563a1696ed38c9a
[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 /**
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
228 LoginRefresh.registerDefaultModalEvents(LoginRefresh.$loginForm).on('submit', LoginRefresh.submitForm);
229
230 $('body').append(LoginRefresh.$loginForm);
231 };
232
233 /**
234 * Shows the login form.
235 */
236 LoginRefresh.showLoginForm = function() {
237 // log off for sure
238 $.ajax({
239 url: TYPO3.settings.ajaxUrls['logout'],
240 method: 'GET',
241 success: function() {
242 if (TYPO3.configuration.showRefreshLoginPopup) {
243 LoginRefresh.showLoginPopup();
244 } else {
245 LoginRefresh.$loginForm.modal(LoginRefresh.options.modalConfig);
246 }
247 },
248 failure: function() {
249 alert('something went wrong');
250 }
251 });
252 };
253
254 /**
255 * Set login frameset url
256 */
257 LoginRefresh.setLoginFramesetUrl = function(loginFramesetUrl) {
258 LoginRefresh.loginFramesetUrl = loginFramesetUrl;
259 };
260
261 /**
262 * Opens the login form in a new window.
263 */
264 LoginRefresh.showLoginPopup = function() {
265 var vHWin = window.open(LoginRefresh.loginFramesetUrl, 'relogin_' + TYPO3.configuration.uniqueID, 'height=450,width=700,status=0,menubar=0,location=1');
266 if (vHWin) {
267 vHWin.focus();
268 }
269 };
270
271 /**
272 * Hides the login form.
273 */
274 LoginRefresh.hideLoginForm = function() {
275 LoginRefresh.$loginForm.modal('hide');
276 };
277
278 /**
279 * Fills the progressbar attached to the given modal.
280 */
281 LoginRefresh.fillProgressbar = function($activeModal) {
282 if (!LoginRefresh.isTimingOut) {
283 return;
284 }
285
286 var max = 100,
287 current = 0,
288 $progressBar = $activeModal.find('.progress-bar'),
289 $srText = $progressBar.children('.sr-only');
290
291 var progress = setInterval(function() {
292 var isOverdue = (current >= max);
293
294 if (!LoginRefresh.isTimingOut || isOverdue) {
295 clearInterval(progress);
296
297 if (isOverdue) {
298 // show login form
299 LoginRefresh.hideTimeoutModal();
300 LoginRefresh.showLoginForm();
301 }
302
303 // reset current
304 current = 0;
305 } else {
306 current += 1;
307 }
308
309 var percentText = (current) + '%';
310 $progressBar.css('width', percentText);
311 $srText.text(percentText);
312 }, 300);
313 };
314
315 /**
316 * Creates additional data based on the security level and "submits" the form
317 * via an AJAX request.
318 *
319 * @param {Event} event
320 */
321 LoginRefresh.submitForm = function(event) {
322 event.preventDefault();
323
324 var $form = LoginRefresh.$loginForm.find('form'),
325 $passwordField = $form.find('input[name=p_field]'),
326 $useridentField = $form.find('input[name=userident]'),
327 passwordFieldValue = $passwordField.val();
328
329 if (passwordFieldValue === '' && $useridentField.val() === '') {
330 Typo3Notification.error(TYPO3.LLL.core.refresh_login_failed, TYPO3.LLL.core.refresh_login_emptyPassword);
331 $passwordField.focus();
332 return;
333 }
334
335 if (passwordFieldValue) {
336 $useridentField.val(passwordFieldValue);
337 $passwordField.val('');
338 }
339
340 var postData = {
341 login_status: 'login'
342 };
343 $.each($form.serializeArray(), function(i, field) {
344 postData[field.name] = field.value;
345 });
346 $.ajax({
347 url: $form.attr('action'),
348 method: 'POST',
349 data: postData,
350 success: function(response) {
351 var result = response.login;
352 if (result.success) {
353 // User is logged in
354 LoginRefresh.hideLoginForm();
355 } else {
356 Typo3Notification.error(TYPO3.LLL.core.refresh_login_failed, TYPO3.LLL.core.refresh_login_failed_message);
357 $passwordField.focus();
358 }
359 }
360 });
361 };
362
363 /**
364 * Registers the (shown|hidden).bs.modal events.
365 * If a modal is shown, the interval check is stopped. If the modal hides,
366 * the interval check starts again.
367 * This method is not invoked for the backend locked modal, because we still
368 * need to check if the backend gets unlocked again.
369 *
370 * @param {Object} $modal
371 * @returns {Object}
372 */
373 LoginRefresh.registerDefaultModalEvents = function($modal) {
374 $modal.on('hidden.bs.modal', function() {
375 LoginRefresh.startTask();
376 }).on('shown.bs.modal', function() {
377 LoginRefresh.stopTask();
378 // focus the button which was configured as active button
379 LoginRefresh.$timeoutModal.find('.modal-footer .t3js-active').first().focus();
380 });
381
382 return $modal;
383 };
384
385 /**
386 * Checks if the user is in focus of the backend.
387 * Thanks to http://stackoverflow.com/a/19519701
388 */
389 LoginRefresh.isPageActive = function() {
390 var stateKey, eventKey, keys = {
391 hidden: 'visibilitychange',
392 webkitHidden: 'webkitvisibilitychange',
393 mozHidden: 'mozvisibilitychange',
394 msHidden: 'msvisibilitychange'
395 };
396
397 for (stateKey in keys) {
398 if (stateKey in document) {
399 eventKey = keys[stateKey];
400 break;
401 }
402 }
403 return function(c) {
404 if (c) {
405 document.addEventListener(eventKey, c);
406 }
407 return !document[stateKey];
408 }();
409 };
410
411 /**
412 * Periodically called task that checks if
413 *
414 * - the user's backend session is about to expire
415 * - the user's backend session has expired
416 * - the backend got locked
417 *
418 * and opens a dialog.
419 */
420 LoginRefresh.checkActiveSession = function() {
421 $.ajax({
422 url: TYPO3.settings.ajaxUrls['login_timedout'],
423 data: {
424 skipSessionUpdate: 1
425 },
426 success: function(response) {
427 if (response.login.locked) {
428 if (!LoginRefresh.backendIsLocked) {
429 LoginRefresh.backendIsLocked = true;
430 LoginRefresh.showBackendLockedModal();
431 }
432 } else {
433 if (LoginRefresh.backendIsLocked) {
434 LoginRefresh.backendIsLocked = false;
435 LoginRefresh.hideBackendLockedModal();
436 }
437 }
438
439 if (!LoginRefresh.backendIsLocked) {
440 if (response.login.timed_out || response.login.will_time_out) {
441 if (response.login.timed_out) {
442 LoginRefresh.showLoginForm();
443 } else {
444 LoginRefresh.showTimeoutModal();
445 }
446 }
447 }
448 }
449 });
450 };
451
452 // initialize and return the LoginRefresh object
453 $(function() {
454 LoginRefresh.initializeTimeoutModal();
455 LoginRefresh.initializeBackendLockedModal();
456 LoginRefresh.initializeLoginForm();
457
458 LoginRefresh.startTask();
459
460 if (typeof Notification !== 'undefined' && Notification.permission !== 'granted') {
461 Notification.requestPermission();
462 }
463 });
464
465 // expose to global
466 TYPO3.LoginRefresh = LoginRefresh;
467
468 return LoginRefresh;
469 });