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