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