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