2026-03-12 11:34:33 -04:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
namespace OCA\Testing\Command;
|
|
|
|
|
|
|
2026-03-13 04:09:28 -04:00
|
|
|
|
use OCP\App\IAppManager;
|
2026-03-12 11:34:33 -04:00
|
|
|
|
use Symfony\Component\Console\Command\Command;
|
|
|
|
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
|
|
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
|
|
|
|
|
|
|
|
|
|
class StaticHunt extends Command {
|
2026-03-13 04:09:28 -04:00
|
|
|
|
private const SKIP_REGEX = [
|
|
|
|
|
|
'@/templates/.+.php$@',
|
|
|
|
|
|
'@/ajax/.+.php$@',
|
|
|
|
|
|
'@/register_command.php$@',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-03-12 11:34:33 -04:00
|
|
|
|
public function __construct(
|
2026-03-13 04:09:28 -04:00
|
|
|
|
private IAppManager $appManager,
|
2026-03-12 11:34:33 -04:00
|
|
|
|
) {
|
|
|
|
|
|
parent::__construct();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 04:09:28 -04:00
|
|
|
|
#[\Override]
|
|
|
|
|
|
protected function configure(): void {
|
2026-03-12 11:34:33 -04:00
|
|
|
|
$this
|
|
|
|
|
|
->setName('testing:static-hunt')
|
|
|
|
|
|
->setDescription('Hunt for static properties in classes');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 04:09:28 -04:00
|
|
|
|
#[\Override]
|
2026-03-12 11:34:33 -04:00
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
|
|
|
|
|
$folders = [
|
|
|
|
|
|
'' => __DIR__ . '/../../../../lib/private/legacy',
|
2026-03-13 04:09:28 -04:00
|
|
|
|
'\\OC' => __DIR__ . '/../../../../lib/private',
|
|
|
|
|
|
'\\OC\\Core' => __DIR__ . '/../../../../core',
|
|
|
|
|
|
];
|
|
|
|
|
|
$apps = $this->appManager->getAllAppsInAppsFolders();
|
|
|
|
|
|
foreach ($apps as $app) {
|
|
|
|
|
|
$info = $this->appManager->getAppInfo($app);
|
|
|
|
|
|
if (!isset($info['namespace'])) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$folders['\\OCA\\' . $info['namespace']] = $this->appManager->getAppPath($app) . '/lib';
|
|
|
|
|
|
}
|
|
|
|
|
|
$stats = [
|
|
|
|
|
|
'classes' => 0,
|
|
|
|
|
|
'properties' => 0,
|
2026-03-12 11:34:33 -04:00
|
|
|
|
];
|
|
|
|
|
|
foreach ($folders as $namespace => $folder) {
|
2026-03-13 04:09:28 -04:00
|
|
|
|
$this->scanFolder($folder, $namespace, $output, $stats);
|
2026-03-12 11:34:33 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 04:09:28 -04:00
|
|
|
|
$output->writeln('<info>Found ' . $stats['properties'] . ' static properties spread among ' . $stats['classes'] . ' classes</info>');
|
|
|
|
|
|
|
2026-03-12 11:34:33 -04:00
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 04:09:28 -04:00
|
|
|
|
private function scanFolder(string $folder, string $namespace, OutputInterface $output, array &$stats): void {
|
2026-03-12 11:34:33 -04:00
|
|
|
|
$folder = realpath($folder);
|
2026-03-13 04:09:28 -04:00
|
|
|
|
$output->writeln('Folder ' . $folder, OutputInterface::VERBOSITY_VERBOSE);
|
|
|
|
|
|
foreach ($this->recursiveGlob($folder) as $filename) {
|
2026-03-12 11:34:33 -04:00
|
|
|
|
try {
|
|
|
|
|
|
$filename = realpath($filename);
|
2026-03-13 04:09:28 -04:00
|
|
|
|
if (($namespace === '\\OC') && str_contains($filename, 'lib/private/legacy')) {
|
|
|
|
|
|
// Skip legacy in OC as it’s scanned with an empty namespace separately
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (self::SKIP_REGEX as $skipRegex) {
|
|
|
|
|
|
if (preg_match($skipRegex, $filename)) {
|
|
|
|
|
|
continue 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
$classname = $namespace . substr(str_replace('/', '\\', substr($filename, strlen($folder))), 0, -4);
|
|
|
|
|
|
$output->writeln('Class ' . $classname, OutputInterface::VERBOSITY_VERBOSE);
|
2026-03-12 11:34:33 -04:00
|
|
|
|
if (!class_exists($classname)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$rClass = new \ReflectionClass($classname);
|
|
|
|
|
|
$staticProperties = $rClass->getStaticProperties();
|
|
|
|
|
|
if (empty($staticProperties)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-03-13 04:09:28 -04:00
|
|
|
|
$stats['classes']++;
|
2026-03-12 11:34:33 -04:00
|
|
|
|
$output->writeln('<info># ' . str_replace(\OC::$SERVERROOT, '', $filename) . " $classname</info>");
|
|
|
|
|
|
foreach ($staticProperties as $property => $value) {
|
|
|
|
|
|
$propertyObject = $rClass->getProperty($property);
|
2026-03-13 04:09:28 -04:00
|
|
|
|
$stats['properties']++;
|
2026-03-12 11:34:33 -04:00
|
|
|
|
$output->write("$propertyObject");
|
|
|
|
|
|
}
|
2026-03-13 04:09:28 -04:00
|
|
|
|
$output->writeln('');
|
2026-03-12 11:34:33 -04:00
|
|
|
|
} catch (\Throwable $t) {
|
|
|
|
|
|
$output->writeln("$t");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-13 04:09:28 -04:00
|
|
|
|
|
|
|
|
|
|
private function recursiveGlob(string $path, int $depth = 1): \Generator {
|
|
|
|
|
|
$pattern = $path . str_repeat('/*', $depth);
|
|
|
|
|
|
yield from glob($pattern . '.php');
|
|
|
|
|
|
if (!empty(glob($pattern, GLOB_ONLYDIR))) {
|
|
|
|
|
|
yield from $this->recursiveGlob($path, $depth + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-12 11:34:33 -04:00
|
|
|
|
}
|