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