ececdf65f848c5a0a11569fb437c483f7b764dcc
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Resources / Public / Javascript / Install.js
1 /***************************************************************
2 *
3 * Various JavaScript functions for the Install Tool
4 *
5 * Copyright notice
6 *
7 * (c) 2009-2010 Marcus Krause, Helmut Hummel, Lars Houmark
8 * (c) 2013 Wouter Wolters <typo3@wouterwolters.nl>
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 backend provided by
12 * Kasper Skaarhoj <kasper@typo3.com> together with TYPO3
13 *
14 * Released under GNU/GPL (see license file in /typo3/)
15 *
16 * This script is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 *
20 * This copyright notice MUST APPEAR in all copies of this script
21 *
22 ***************************************************************/
23
24 /**
25 * Handle core update
26 */
27 var TYPO3 = {};
28 TYPO3.Install = {};
29 TYPO3.Install.coreUpdate = {
30 /**
31 * The action queue defines what actions are called in which order
32 */
33 actionQueue: {
34 coreUpdateUpdateVersionMatrix: {
35 loadingMessage: 'Fetching list of released versions from typo3.org',
36 finishMessage: 'Fetched list of released versions',
37 nextActionName: 'coreUpdateIsUpdateAvailable'
38 },
39 coreUpdateIsUpdateAvailable: {
40 loadingMessage: 'Checking for possible regular or security update',
41 finishMessage: undefined,
42 nextActionName: undefined
43 },
44 coreUpdateCheckPreConditions: {
45 loadingMessage: 'Checking if update is possible',
46 finishMessage: 'System can be updated',
47 nextActionName: 'coreUpdateDownload'
48 },
49 coreUpdateDownload: {
50 loadingMessage: 'Downloading new core',
51 finishMessage: 'Core download finished',
52 nextActionName: 'coreUpdateVerifyChecksum'
53 },
54 coreUpdateVerifyChecksum: {
55 loadingMessage: 'Verifying checksum of downloaded core',
56 finishMessage: 'Checksum verified',
57 nextActionName: 'coreUpdateUnpack'
58 },
59 coreUpdateUnpack: {
60 loadingMessage: 'Unpacking core',
61 finishMessage: 'Unpacking core successful',
62 nextActionName: 'coreUpdateMove'
63 },
64 coreUpdateMove: {
65 loadingMessage: 'Moving core',
66 finishMessage: 'Moved core to final location',
67 nextActionName: 'clearCache'
68 },
69 clearCache: {
70 loadingMessage: 'Clearing caches',
71 finishMessage: 'Caches cleared',
72 nextActionName: 'coreUpdateActivate'
73 },
74 coreUpdateActivate: {
75 loadingMessage: 'Activating core',
76 finishMessage: 'Core updated - please reload your browser',
77 nextActionName: undefined
78 }
79 },
80
81 /**
82 * Clone of a DOM object acts as message template
83 */
84 messageTemplate: null,
85
86 /**
87 * Clone of a DOM object acts as button template
88 */
89 buttonTemplate: null,
90
91 /**
92 * Fetching the templates out of the DOM
93 */
94 initialize: function() {
95 var messageTemplateSection = $('#messageTemplate');
96 var buttonTemplateSection = $('#buttonTemplate');
97 this.messageTemplate = messageTemplateSection.children().clone();
98 this.buttonTemplate = buttonTemplateSection.children().clone();
99 messageTemplateSection.remove();
100 },
101
102 /**
103 * Public method checkForUpdate
104 */
105 checkForUpdate: function() {
106 this.callAction('coreUpdateUpdateVersionMatrix');
107 },
108
109 /**
110 * Public method updateDevelopment
111 */
112 updateDevelopment: function() {
113 this.update('development');
114 },
115
116 /**
117 * Public method updateRegular
118 */
119 updateRegular: function() {
120 this.update('regular');
121 },
122
123 /**
124 * Execute core update.
125 *
126 * @param type Either 'development' or 'regular'
127 */
128 update: function(type) {
129 if (type !== "development") {
130 type = 'regular';
131 }
132 this.callAction('coreUpdateCheckPreConditions', type);
133 },
134
135 /**
136 * Generic method to call actions from the queue
137 *
138 * @param actionName Name of the action to be called
139 * @param type Update type (optional)
140 */
141 callAction: function(actionName, type) {
142 var self = this;
143 var arguments = {
144 install: {
145 controller: 'ajax',
146 action: actionName
147 }
148 };
149 if (type !== undefined) {
150 arguments.install["type"] = type;
151 }
152 this.addLoadingMessage(this.actionQueue[actionName].loadingMessage);
153 $.ajax({
154 url: location.href,
155 data: arguments,
156 cache: false,
157 success: function(result) {
158 canContinue = self.handleResult(result, self.actionQueue[actionName].finishMessage);
159 if (canContinue === true && (self.actionQueue[actionName].nextActionName !== undefined)) {
160 self.callAction(self.actionQueue[actionName].nextActionName, type);
161 }
162 },
163 error: function(result) {
164 self.handleResult(result);
165 }
166 });
167 },
168
169 /**
170 * Handle ajax result of core update step.
171 *
172 * @param data
173 * @param successMessage Optional success message
174 */
175 handleResult: function(data, successMessage) {
176 var canContinue = false;
177 this.removeLoadingMessage();
178 if (data.success === true) {
179 canContinue = true;
180 if (data.status && typeof(data.status) === 'object') {
181 this.showStatusMessages(data.status);
182 }
183 if (data.action && typeof(data.action) === 'object') {
184 this.showActionButton(data.action);
185 }
186 if (successMessage) {
187 this.addMessage('ok', successMessage);
188 }
189 } else {
190 // Handle clearcache until it uses the new view object
191 if (data === "OK") {
192 canContinue = true;
193 if (successMessage) {
194 this.addMessage('ok', successMessage);
195 }
196 } else {
197 canContinue = false;
198 if (data.status && typeof(data.status) === 'object') {
199 this.showStatusMessages(data.status);
200 } else {
201 this.addMessage('error', 'General error');
202 }
203 }
204 }
205 return canContinue;
206 },
207
208 /**
209 * Add a loading message with some text.
210 *
211 * @param messageTitle
212 */
213 addLoadingMessage: function(messageTitle) {
214 var domMessage = this.messageTemplate.clone();
215 domMessage.find('.message-header strong').html(messageTitle);
216 domMessage.addClass('message-loading');
217 $('#coreUpdate').append(domMessage);
218 },
219
220 /**
221 * Remove an enabled loading message
222 */
223 removeLoadingMessage: function() {
224 $('#coreUpdate .message-loading').closest('.typo3-message').remove();
225 },
226
227 /**
228 * Show a list of status messages
229 *
230 * @param messages
231 */
232 showStatusMessages: function(messages) {
233 var self = this;
234 $.each(messages, function(index, element) {
235 var title = false;
236 var severity = false;
237 var message = false;
238 if (element.severity) {
239 severity = element.severity;
240 }
241 if (element.title) {
242 title = element.title;
243 }
244 if (element.message) {
245 message = element.message;
246 }
247 self.addMessage(severity, title, message);
248 });
249 },
250
251 /**
252 * Show an action button
253 *
254 * @param button
255 */
256 showActionButton: function(button) {
257 var title = false;
258 var action = false;
259 if (button.title) {
260 title = button.title;
261 }
262 if (button.action) {
263 action = button.action;
264 }
265 var domButton = this.buttonTemplate;
266 if (action) {
267 domButton.find('button').data('action', action);
268 }
269 if (title) {
270 domButton.find('button').html(title);
271 }
272 $('#coreUpdate').append(domButton);
273 },
274
275 /**
276 * Show a status message
277 *
278 * @param severity
279 * @param title
280 * @param message
281 */
282 addMessage: function(severity, title, message) {
283 var domMessage = this.messageTemplate.clone();
284 if (severity) {
285 domMessage.addClass('message-' + severity);
286 }
287 if (title) {
288 domMessage.find('.message-header strong').html(title);
289 }
290 if (message) {
291 domMessage.find('.message-body').html(message);
292 }
293 $('#coreUpdate').append(domMessage);
294 }
295 };
296
297 $(document).ready(function() {
298 // Used in database compare section to select/deselect checkboxes
299 $('.checkall').on('click', function() {
300 $(this).closest('fieldset').find(':checkbox').prop('checked', this.checked);
301 });
302
303 // Toggle open/close
304 $('.toggleButton').on('click', function() {
305 $toggleGroup = $(this).closest('.toggleGroup');
306 $toggleGroup.toggleClass('expanded');
307 $toggleGroup.find('.toggleData').toggle();
308 handleButtonScrolling();
309 });
310
311 $('.toggleAll').on('click', function() {
312 $toggleAll = $('.toggleGroup');
313 if ($toggleAll.not('.expanded').length == 0) {
314 // all elements are open, close them
315 $toggleAll.removeClass('expanded');
316 $toggleAll.find('.toggleData').hide();
317 } else {
318 $toggleAll.addClass('expanded');
319 $toggleAll.find('.toggleData').show();
320 }
321 handleButtonScrolling();
322 });
323
324 // Simple password strength indicator
325 $('.t3-install-form-password-strength').on('keyup', function() {
326 var value = $(this).val();
327 var strongRegex = new RegExp('^(?=.{8,})(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*\\W).*$', 'g');
328 var mediumRegex = new RegExp('^(?=.{8,})(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[0-9]))).*$', 'g');
329 var enoughRegex = new RegExp('(?=.{8,}).*', 'g');
330
331 if (value.length == 0) {
332 $(this).attr('style', 'background-color:#FBB19B; border:1px solid #DC4C42');
333 } else if (false == enoughRegex.test(value)) {
334 $(this).attr('style', 'background-color:#FBB19B; border:1px solid #DC4C42');
335 } else if (strongRegex.test(value)) {
336 $(this).attr('style', 'background-color:#CDEACA; border:1px solid #58B548');
337 } else if (mediumRegex.test(value)) {
338 $(this).attr('style', 'background-color:#FBFFB3; border:1px solid #C4B70D');
339 } else {
340 $(this).attr('style', 'background-color:#FBFFB3; border:1px solid #C4B70D');
341 }
342 });
343
344 // Install step database settings
345 $('#t3-install-step-type').change(function() {
346 var connectionType = $(this).val(),
347 hostField = $('#t3-install-step-host'),
348 portField = $('#t3-install-step-port'),
349 socketField = $('#t3-install-step-socket');
350
351 if (connectionType === 'socket') {
352 hostField.parent().fadeOut();
353 hostField.val('localhost');
354 portField.parent().fadeOut();
355 socketField.parent().fadeIn();
356 } else {
357 hostField.parent().fadeIn();
358 if (hostField.val() === 'localhost') {
359 hostField.val('127.0.0.1');
360 }
361 portField.parent().fadeIn();
362 socketField.parent().fadeOut();
363 }
364 }).trigger('change');
365
366 // Extension compatibility check
367 $('.typo3-message', '#checkExtensions').hide();
368 $('button', '#checkExtensions').click(function(e) {
369 $('button', '#checkExtensions').hide();
370 $('.typo3-message', '#checkExtensions').hide();
371 $('.message-loading', '#checkExtensions').show();
372 checkExtensionsCompatibility(true);
373 e.preventDefault();
374 return false;
375 });
376
377 // Focus input field on click on item-div around it
378 $('.toggleGroup .item').on('click', function() {
379 $(this).find('input').focus();
380 });
381
382 // Footer scrolling and visibility
383 if ($('#fixed-footer-fieldset').length > 0) {
384 $(window).scroll(handleButtonScrolling);
385 $('body.backend #typo3-docbody').scroll(handleButtonScrolling);
386 }
387
388 // Handle core update
389 var $coreUpdateSection = $('#coreUpdate');
390 if ($coreUpdateSection) {
391 TYPO3.Install.coreUpdate.initialize();
392 $coreUpdateSection.on('click', 'button', (function(e) {
393 e.preventDefault();
394 var action = $(e.target).data('action');
395 TYPO3.Install.coreUpdate[action]();
396 $(e.target).closest('.t3-install-form-submit').remove();
397 }));
398 }
399 });
400
401 function handleButtonScrolling() {
402 if (!isScrolledIntoView($('#fixed-footer-fieldset'))) {
403 $('#fixed-footer-fieldset fieldset').addClass('fixed');
404 } else {
405 $('#fixed-footer-fieldset fieldset').removeClass('fixed');
406 }
407 }
408 function isScrolledIntoView(elem) {
409 var docViewTop = $(window).scrollTop();
410 var docViewBottom = docViewTop + $(window).height();
411 var elemTop = $(elem).offset().top;
412 var elemBottom = elemTop + $(elem).height();
413
414 return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
415 }
416
417 /**
418 * Checks extension compatibility by trying to load ext_tables and ext_localconf
419 * via ajax.
420 *
421 * @param force
422 */
423 function checkExtensionsCompatibility(force) {
424 var url = location.href + '&install[controller]=ajax&install[action]=extensionCompatibilityTester';
425 if (force) {
426 clearCache();
427 url += '&install[extensionCompatibilityTester][forceCheck]=1';
428 } else {
429 url += '&install[extensionCompatibilityTester][forceCheck]=0';
430 }
431 $.ajax({
432 url: url,
433 cache: false,
434 success: function(data) {
435 if (data === 'OK') {
436 handleCheckExtensionsSuccess();
437 } else {
438 if(data === 'unauthorized') {
439 location.reload();
440 }
441 // workaround for xdebug returning 200 OK on fatal errors
442 if (data.substring(data.length - 2) === 'OK') {
443 handleCheckExtensionsSuccess();
444 } else {
445 handleCheckExtensionsError();
446 }
447 }
448 },
449 error: function(data) {
450 handleCheckExtensionsError();
451 }
452 });
453 }
454
455 /**
456 * Handles result of extension compatibility check.
457 * Displays uninstall buttons for non-compatible extensions.
458 */
459 function handleCheckExtensionsSuccess() {
460 $.ajax({
461 url: $('#checkExtensions').data('protocolurl'),
462 cache: false,
463 success: function(data) {
464 if (data) {
465 $('.message-error .message-body', '#checkExtensions').html(
466 'The following extensions are not compatible. Please uninstall them and try again. '
467 );
468 var extensions = data.split(',');
469 var unloadButtonWrapper = $('<fieldset class="t3-install-form-submit"></fieldset>');
470 for(var i=0; i<extensions.length; i++) {
471 var extension = extensions[i];
472 var unloadButton = $('<button />', {
473 text: 'Uninstall '+ $.trim(extension),
474 "class": $.trim(extension),
475 click: function(e) {
476 uninstallExtension($(this).attr('class'));
477 e.preventDefault();
478 return false;
479 }
480 });
481 var fullButton = unloadButtonWrapper.append(unloadButton);
482 $('.message-error .message-body', '#checkExtensions').append(fullButton);
483 }
484 var unloadAllButton = $('<button />', {
485 text: 'Uninstall all incompatible extensions: '+ data,
486 click: function(e) {
487 $('.message-loading', '#checkExtensions').show();
488 uninstallExtension(data);
489 e.preventDefault();
490 return false;
491 }
492 });
493 unloadButtonWrapper.append('<hr />');
494 var fullUnloadAllButton = unloadButtonWrapper.append(unloadAllButton);
495 $('.message-error .message-body', '#checkExtensions').append(fullUnloadAllButton);
496
497 $('.message-loading', '#checkExtensions').hide();
498 $('button', '#checkExtensions').show();
499 $('.message-error', '#checkExtensions').show();
500 } else {
501 $('.typo3-message', '#checkExtensions').hide();
502 $('.message-ok', '#checkExtensions').show();
503
504 }
505 },
506 error: function(data) {
507 $('.typo3-message', '#checkExtensions').hide();
508 $('.message-ok', '#checkExtensions').show();
509 }
510 })
511 }
512
513 /**
514 * Call checkExtensionsCompatibility recursively on error
515 * so we can find all incompatible extensions
516 */
517 function handleCheckExtensionsError() {
518 checkExtensionsCompatibility(false);
519 }
520
521 /**
522 * Send an ajax request to uninstall an extension (or multiple extensions)
523 *
524 * @param extension string of extension(s) - may be comma separated
525 */
526 function uninstallExtension(extension) {
527 var url = location.href + '&install[controller]=ajax&install[action]=uninstallExtension' +
528 '&install[uninstallExtension][extensions]=' + extension;
529 $.ajax({
530 url: url,
531 cache: false,
532 success: function(data) {
533 if (data === 'OK') {
534 checkExtensionsCompatibility(true);
535 } else {
536 if(data === 'unauthorized') {
537 location.reload();
538 }
539 // workaround for xdebug returning 200 OK on fatal errors
540 if (data.substring(data.length - 2) === 'OK') {
541 checkExtensionsCompatibility(true);
542 } else {
543 $('.message-loading', '#checkExtensions').hide();
544 $('.message-error .message-body', '#checkExtensions').html(
545 'Something went wrong. Check failed.'
546 );
547 }
548 }
549 },
550 error: function(data) {
551 handleCheckExtensionsError();
552 }
553 });
554 }
555
556 /**
557 * Ajax call to clear all caches.
558 */
559 function clearCache() {
560 $.ajax({
561 url: location.href + '&install[controller]=ajax&install[action]=clearCache',
562 cache: false
563 });
564 }