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