setDescription('Compatibility scanner');
$this->setHelp('Scans plugins for common issues (backward compatibility and the like)');
$this->addArgument(
'version',
InputArgument::OPTIONAL,
'Version to check against (if not suppied, all checks are performed)'
);
$this->addArgument(
'folder',
InputArgument::IS_ARRAY,
'Folder to scan (will default to the plugins_packages folder)'
);
$this->addOption('filenames', 'f', InputOption::VALUE_NONE, 'Display filenames only');
$this->addOption(
'recursive',
'r',
InputOption::VALUE_NONE | InputOption::VALUE_NEGATABLE,
'Do not scan recursively into subfolders'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->getFormatter()->setStyle('issue', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold']));
$rules = $this->getCompatibilityRules($input->getArgument('version'));
$folders = $input->getArgument('folder') ?: $this->getDefaultFolders();
$recursive = $input->getOption('recursive') ?? true;
foreach ($folders as $f) {
$folder = $this->validateFolder($f);
if (!$folder) {
$output->writeln("Skipping invalid folder {$f}", OutputInterface::VERBOSITY_VERBOSE);
continue;
}
$issues = [];
foreach ($this->getFolderIterator($folder, $recursive, ['php', 'tpl', 'inc', 'js']) as $file) {
$filename = $file->getPathName();
$output->writeln("Checking {$filename}", OutputInterface::VERBOSITY_VERBOSE);
if ($errors = $this->checkFilecontentsAgainstRules($filename, $rules)) {
$issues[$filename] = $errors;
}
}
if (count($issues) === 0) {
continue;
}
if (!$input->getOption('filenames')) {
$issue_count = array_sum(array_map('count', $issues));
$message = count($issues) === 1
? '%u issue found in %s'
: '%u issues found in %s';
$output->writeln(sprintf(
"{$message}",
$issue_count,
$this->relativeFilePath($folder)
));
}
foreach ($issues as $filename => $errors) {
if ($input->getOption('filenames')) {
$output->writeln($filename);
} else {
$output->writeln(sprintf(
'> File %s>',
$this->relativeFilePath($filename)
));
foreach ($errors as $needle => $suggestion) {
$output->writeln(
sprintf('- %s> -> %s', $needle, $suggestion ?: 'No suggestion available')
);
}
}
}
}
return Command::SUCCESS;
}
private function getCompatibilityRules(?string $version): array
{
if ($version !== null) {
if (!file_exists(__DIR__ . "/compatibility-rules/studip-{$version}.php")) {
throw new \Exception("No rules defined for Stud.IP version {$version}");
}
return require __DIR__ . "/compatibility-rules/studip-{$version}.php";
}
$rules = [];
foreach (glob(__DIR__ . '/compatbility-rules/*.php') as $file) {
$version_rules = require $file;
$rules = array_merge($rules, $version_rules);
}
return $rules;
}
private function getDefaultFolders(): array
{
$folders = rtrim($GLOBALS['STUDIP_BASE_PATH'], '/') . '/public/plugins_packages';
$folders = glob($folders . '/*/*');
return $folders;
}
private function validateFolder(string $folder)
{
if (!file_exists($folder) || !is_dir($folder)) {
return false;
}
return $folder;
}
private function checkFilecontentsAgainstRules(string $filename, array $rules)
{
$errors = [];
$contents = strtolower(file_get_contents($filename));
foreach ($rules as $needle => $suggestion) {
if ($this->checkRule($contents, $needle)) {
$errors[$needle] = $suggestion;
}
}
return $errors;
}
private function checkRule(string $contents, string $rule)
{
if ($rule[0] === '/' && $rule[strlen($rule) - 1] === '/') {
return (bool) preg_match("{$rule}s", $contents);
}
return strpos($contents, strtolower($rule)) > 0;
}
}