e79378974760b8d97ab923d5b1c206ff56197845
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Resources / Public / JavaScript / Modules / ExtensionScanner.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 * Module: TYPO3/CMS/Install/ExtensionScanner
16 */
17 define(['jquery',
18 'TYPO3/CMS/Install/Router',
19 'TYPO3/CMS/Backend/Notification'
20 ], function($, Router, Notification) {
21 'use strict';
22
23 return {
24 selectorModalBody: '.t3js-modal-body',
25 selectorModuleContent: '.t3js-module-content',
26 listOfAffectedRestFileHashes: [],
27 selectorExtensionContainer: '.t3js-extensionScanner-extension',
28 selectorNumberOfFiles: '.t3js-extensionScanner-number-of-files',
29 selectorScanSingleTrigger: '.t3js-extensionScanner-scan-single',
30
31 initialize: function(currentModal) {
32 var self = this;
33 this.currentModal = currentModal;
34 self.getData();
35
36 currentModal.on('click', this.selectorScanSingleTrigger, function(e) {
37 // Scan a single extension
38 var extension = $(e.target).data('extension');
39 e.preventDefault();
40 self.scanSingleExtension(extension);
41 return false;
42 });
43
44 currentModal.on('click', '.t3js-extensionScanner-scan-all', function(e) {
45 // Scan all button
46 e.preventDefault();
47 var $extensions = currentModal.find(self.selectorExtensionContainer);
48 self.scanAll($extensions);
49 return false;
50 });
51 },
52
53 getData: function() {
54 var self = this;
55 var modalContent = this.currentModal.find(self.selectorModalBody);
56 $.ajax({
57 url: Router.getUrl('extensionScannerGetData'),
58 cache: false,
59 success: function(data) {
60 if (data.success === true) {
61 modalContent.empty().append(data.html);
62 } else {
63 Notification.error('Something went wrong');
64 }
65 },
66 error: function(xhr) {
67 Router.handleAjaxError(xhr);
68 }
69 });
70 },
71
72 /**
73 * @param {string} extension
74 * @returns {string}
75 */
76 getExtensionSelector: function(extension) {
77 return this.selectorExtensionContainer + '-' + extension;
78 },
79
80 /**
81 * @param {JQuery} $extensions
82 */
83 scanAll: function($extensions) {
84 var self = this;
85 self.currentModal.find(this.selectorExtensionContainer)
86 .removeClass('panel-danger panel-warning panel-success')
87 .find('.panel-progress-bar')
88 .css('width', 0)
89 .attr('aria-valuenow', 0)
90 .find('span')
91 .text('0%');
92 self.setProgressForAll();
93 $extensions.each(function() {
94 var extension = $(this).data('extension');
95 self.scanSingleExtension(extension);
96 });
97 },
98
99 /**
100 * @param {string} extension
101 * @param {number} doneFiles
102 * @param {number} numberOfFiles
103 */
104 setStatusMessageForScan: function(extension, doneFiles, numberOfFiles) {
105 this.currentModal.find(this.getExtensionSelector(extension))
106 .find(this.selectorNumberOfFiles)
107 .text('Checked ' + doneFiles + ' of ' + numberOfFiles + ' files');
108 },
109
110 /**
111 * @param {string} extension
112 * @param {number} doneFiles
113 * @param {number} numberOfFiles
114 */
115 setProgressForScan: function(extension, doneFiles, numberOfFiles) {
116 var percent = (doneFiles / numberOfFiles) * 100;
117 this.currentModal.find(this.getExtensionSelector(extension))
118 .find('.panel-progress-bar')
119 .css('width', percent + '%')
120 .attr('aria-valuenow', percent)
121 .find('span')
122 .text(percent + '%');
123 },
124
125 /**
126 * Update main progress bar
127 */
128 setProgressForAll: function() {
129 var self = this;
130 // var numberOfExtensions = $(this.selectorExtensionContainer).length;
131 var numberOfExtensions = self.currentModal.find(this.selectorExtensionContainer).length;
132 var numberOfSuccess = self.currentModal.find(this.selectorExtensionContainer + '.t3js-extensionscan-finished.panel-success').length;
133 var numberOfWarning = self.currentModal.find(this.selectorExtensionContainer + '.t3js-extensionscan-finished.panel-warning').length;
134 var numberOfError = self.currentModal.find(this.selectorExtensionContainer + '.t3js-extensionscan-finished.panel-danger').length;
135 var numberOfScannedExtensions = numberOfSuccess + numberOfWarning + numberOfError;
136 var percent = (numberOfScannedExtensions / numberOfExtensions) * 100;
137 self.currentModal.find('.t3js-extensionScanner-progress-all-extension .progress-bar')
138 .css('width', percent + '%')
139 .attr('aria-valuenow', percent)
140 .find('span')
141 .text(numberOfScannedExtensions + ' of ' + numberOfExtensions + ' scanned');
142
143 if (numberOfScannedExtensions === numberOfExtensions) {
144 Notification.success('Scan finished', 'All extensions have been scanned');
145 $.ajax({
146 url: Router.getUrl(),
147 method: 'POST',
148 data: {
149 'install': {
150 'action': 'extensionScannerMarkFullyScannedRestFiles',
151 'token': self.currentModal.find(self.selectorModuleContent).data('extension-scanner-mark-fully-scanned-rest-files-token'),
152 'hashes': self.uniqueArray(this.listOfAffectedRestFileHashes)
153 }
154 },
155 cache: false,
156 success: function(data) {
157 if (data.success === true) {
158 Notification.success('Marked not affected files', 'Marked ' + data.markedAsNotAffected + ' ReST files as not affected.');
159 }
160 },
161 error: function(xhr) {
162 Router.handleAjaxError(xhr);
163 }
164 });
165 }
166 },
167
168 /**
169 * Helper method removing duplicate entries from an array
170 *
171 * @param {Array} anArray
172 * @returns {Array}
173 */
174 uniqueArray: function(anArray) {
175 return anArray.filter(function(value, index, self) {
176 return self.indexOf(value) === index;
177 });
178 },
179
180 /**
181 * Handle a single extension scan
182 *
183 * @param {string} extension
184 */
185 scanSingleExtension: function(extension) {
186 var self = this;
187 var executeToken = self.currentModal.find(this.selectorModuleContent).data('extension-scanner-files-token');
188 var modalContent = this.currentModal.find(self.selectorModalBody);
189 var $extensionContainer = this.currentModal.find(this.getExtensionSelector(extension));
190 var hitTemplate = '#t3js-extensionScanner-file-hit-template';
191 var restTemplate = '#t3js-extensionScanner-file-hit-rest-template';
192 var hitFound = false;
193 $extensionContainer.removeClass('panel-danger panel-warning panel-success t3js-extensionscan-finished');
194 $extensionContainer.data('hasRun', 'true');
195 $extensionContainer.find('.t3js-extensionScanner-scan-single').text('Scanning...').attr('disabled', 'disabled');
196 $extensionContainer.find('.t3js-extensionScanner-extension-body-loc').empty().text('0');
197 $extensionContainer.find('.t3js-extensionScanner-extension-body-ignored-files').empty().text('0');
198 $extensionContainer.find('.t3js-extensionScanner-extension-body-ignored-lines').empty().text('0');
199 this.setProgressForAll();
200 $.ajax({
201 url: Router.getUrl(),
202 method: 'POST',
203 data: {
204 'install': {
205 'action': 'extensionScannerFiles',
206 'token': executeToken,
207 'extension': extension
208 }
209 },
210 cache: false,
211 success: function(data) {
212 if (data.success === true && Array.isArray(data.files)) {
213 var numberOfFiles = data.files.length;
214 if (numberOfFiles > 0) {
215 self.setStatusMessageForScan(extension, 0, numberOfFiles);
216 $extensionContainer.find('.t3js-extensionScanner-extension-body').text('');
217 var doneFiles = 0;
218 data.files.forEach(function(file) {
219 $.ajax({
220 method: 'POST',
221 data: {
222 'install': {
223 'action': 'extensionScannerScanFile',
224 'token': self.currentModal.find(self.selectorModuleContent).data('extension-scanner-scan-file-token'),
225 'extension': extension,
226 'file': file
227 }
228 },
229 url: Router.getUrl(),
230 cache: false,
231 success: function(fileData) {
232 doneFiles = doneFiles + 1;
233 self.setStatusMessageForScan(extension, doneFiles, numberOfFiles);
234 self.setProgressForScan(extension, doneFiles, numberOfFiles);
235 if (fileData.success && $.isArray(fileData.matches)) {
236 $(fileData.matches).each(function() {
237 hitFound = true;
238 var match = this;
239 var aMatch = modalContent.find(hitTemplate).clone();
240 aMatch.find('.t3js-extensionScanner-hit-file-panel-head').attr('href', '#collapse' + match.uniqueId);
241 aMatch.find('.t3js-extensionScanner-hit-file-panel-body').attr('id', 'collapse' + match.uniqueId);
242 aMatch.find('.t3js-extensionScanner-hit-filename').text(file);
243 aMatch.find('.t3js-extensionScanner-hit-message').text(match.message);
244 if (match.indicator === 'strong') {
245 aMatch.find('.t3js-extensionScanner-hit-file-panel-head .badges')
246 .append('<span class="badge" title="Reliable match, false positive unlikely">strong</span>');
247 } else {
248 aMatch.find('.t3js-extensionScanner-hit-file-panel-head .badges')
249 .append('<span class="badge" title="Probable match, but can be a false positive">weak</span>');
250 }
251 if (match.silenced === true) {
252 aMatch.find('.t3js-extensionScanner-hit-file-panel-head .badges')
253 .append('<span class="badge" title="Match has been annotated by extension author as false positive match">silenced</span>');
254 }
255 aMatch.find('.t3js-extensionScanner-hit-file-lineContent').empty().text(match.lineContent);
256 aMatch.find('.t3js-extensionScanner-hit-file-line').empty().text(match.line + ': ');
257 if ($.isArray(match.restFiles)) {
258 $(match.restFiles).each(function() {
259 var restFile = this;
260 var aRest = modalContent.find(restTemplate).clone();
261 aRest.find('.t3js-extensionScanner-hit-rest-panel-head').attr('href', '#collapse' + restFile.uniqueId);
262 aRest.find('.t3js-extensionScanner-hit-rest-panel-head .badge').empty().text(restFile.version);
263 aRest.find('.t3js-extensionScanner-hit-rest-panel-body').attr('id', 'collapse' + restFile.uniqueId);
264 aRest.find('.t3js-extensionScanner-hit-rest-headline').text(restFile.headline);
265 aRest.find('.t3js-extensionScanner-hit-rest-body').text(restFile.content);
266 aRest.addClass('panel-' + restFile.class);
267 aMatch.find('.t3js-extensionScanner-hit-file-rest-container').append(aRest);
268 self.listOfAffectedRestFileHashes.push(restFile.file_hash);
269 });
270 }
271 var panelClass =
272 aMatch.find('.panel-breaking', '.t3js-extensionScanner-hit-file-rest-container').length > 0
273 ? 'panel-danger'
274 : 'panel-warning';
275 aMatch.addClass(panelClass);
276 $extensionContainer.find('.t3js-extensionScanner-extension-body').removeClass('hide').append(aMatch);
277 if (panelClass === 'panel-danger') {
278 $extensionContainer.removeClass('panel-warning').addClass(panelClass);
279 }
280 if (panelClass === 'panel-warning' && !$extensionContainer.hasClass('panel-danger')) {
281 $extensionContainer.addClass(panelClass);
282 }
283 });
284 }
285 if (fileData.success) {
286 var currentLinesOfCode = parseInt($extensionContainer.find('.t3js-extensionScanner-extension-body-loc').text());
287 $extensionContainer.find('.t3js-extensionScanner-extension-body-loc').empty().text(currentLinesOfCode + parseInt(fileData.effectiveCodeLines));
288 if (fileData.isFileIgnored) {
289 var currentIgnoredFiles = parseInt($extensionContainer.find('.t3js-extensionScanner-extension-body-ignored-files').text());
290 $extensionContainer.find('.t3js-extensionScanner-extension-body-ignored-files').empty().text(currentIgnoredFiles + 1);
291 }
292 var currentIgnoredLines = parseInt($extensionContainer.find('.t3js-extensionScanner-extension-body-ignored-lines').text());
293 $extensionContainer.find('.t3js-extensionScanner-extension-body-ignored-lines').empty().text(currentIgnoredLines + parseInt(fileData.ignoredLines));
294 }
295 if (doneFiles === numberOfFiles) {
296 if (!hitFound) {
297 $extensionContainer.addClass('panel-success');
298 }
299 $extensionContainer.addClass('t3js-extensionscan-finished');
300 self.setProgressForAll();
301 $extensionContainer.find('.t3js-extensionScanner-scan-single').text('Rescan').attr('disabled', null);
302 }
303 },
304 error: function(data) {
305 doneFiles = doneFiles + 1;
306 self.setStatusMessageForScan(extension, doneFiles, numberOfFiles);
307 self.setProgressForScan(extension, doneFiles, numberOfFiles);
308 self.setProgressForAll();
309 Notification.error('Oops, an error occurred', 'Please look at the console output for details');
310 console.error(data);
311 }
312 });
313 });
314 } else {
315 Notification.warning('No files found', 'The extension EXT:' + extension + ' contains no files we can scan');
316 }
317 } else {
318 Notification.error('Oops, an error occurred', 'Please look at the console output for details');
319 console.error(data);
320 }
321 },
322 error: function(xhr) {
323 Router.handleAjaxError(xhr);
324 }
325 });
326 }
327 };
328 });