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