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