Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 98 additions & 31 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,20 @@ public function preAutoloadDump(Event $event): void

$root = dirname(realpath($event->getComposer()->getConfig()->get('vendor-dir'))) . '/';
foreach ($extra['plugin-paths'] as $pluginsPath) {
if (!is_dir($root . $pluginsPath)) {
$pluginPath = $root . $pluginsPath;
if (!is_dir($pluginPath)) {
continue;
}
foreach (new DirectoryIterator($root . $pluginsPath) as $fileInfo) {
if (!$fileInfo->isDir() || $fileInfo->isDot()) {
continue;
}
foreach ($this->findAppPlugins($pluginPath) as $pluginName => $pluginPath) {
$namespace = str_replace('/', '\\', $pluginName) . '\\';
$testNamespace = $namespace . 'Test\\';
$path = $this->getRelativePath($pluginPath, $root);

$folderName = $fileInfo->getFilename();
if ($folderName[0] === '.') {
continue;
if (!isset($autoload['psr-4'][$namespace])) {
$autoload['psr-4'][$namespace] = $path . '/src';
}

$pluginNamespace = $folderName . '\\';
$pluginTestNamespace = $folderName . '\\Test\\';
$path = $pluginsPath . '/' . $folderName . '/';
if (!isset($autoload['psr-4'][$pluginNamespace]) && is_dir($root . $path . '/src')) {
$autoload['psr-4'][$pluginNamespace] = $path . 'src';
}
if (!isset($devAutoload['psr-4'][$pluginTestNamespace]) && is_dir($root . $path . '/tests')) {
$devAutoload['psr-4'][$pluginTestNamespace] = $path . 'tests';
if (!isset($devAutoload['psr-4'][$testNamespace]) && is_dir($pluginPath . '/tests')) {
$devAutoload['psr-4'][$testNamespace] = $path . '/tests';
}
}
}
Expand Down Expand Up @@ -154,28 +147,102 @@ public function findPlugins(

foreach ($pluginDirs as $path) {
$path = $this->getFullPath($path, $vendorDir);
if (is_dir($path)) {
$dir = new DirectoryIterator($path);
foreach ($dir as $info) {
if (!$info->isDir() || $info->isDot()) {
continue;
}

$name = $info->getFilename();
if ($name[0] === '.') {
continue;
}

$plugins[$name] = $path . DIRECTORY_SEPARATOR . $name;
}
if (!is_dir($path)) {
continue;
}
$plugins += $this->findAppPlugins($path, true);
}

ksort($plugins);

return $plugins;
}

/**
* Find application plugins in a plugin path.
*
* Supports both `plugins/MyPlugin/src` and `plugins/Vendor/Plugin/src`.
* When requested, top-level directories with no plugin children are kept
* for backward compatibility.
*
* @param string $path The absolute plugin path.
* @param bool $keepLegacyDirectories Whether to keep legacy top-level entries.
* @return array<string, string>
*/
protected function findAppPlugins(string $path, bool $keepLegacyDirectories = false): array
{
$plugins = [];

foreach (new DirectoryIterator($path) as $info) {
if ($this->shouldSkipDirectory($info)) {
continue;
}

$name = $info->getFilename();
$pluginPath = $info->getPathname();
if ($this->isPluginDirectory($pluginPath)) {
$plugins[$name] = $pluginPath;

continue;
}

$vendorPlugins = [];
foreach (new DirectoryIterator($pluginPath) as $subInfo) {
if ($this->shouldSkipDirectory($subInfo)) {
continue;
}

$subName = $subInfo->getFilename();
$subPluginPath = $subInfo->getPathname();
if ($this->isPluginDirectory($subPluginPath)) {
$vendorPlugins[$name . '/' . $subName] = $subPluginPath;
}
}

if ($vendorPlugins) {
$plugins += $vendorPlugins;

continue;
}
if ($keepLegacyDirectories) {
$plugins[$name] = $pluginPath;
}
}

return $plugins;
}

/**
* @param \DirectoryIterator $info Directory iterator entry.
* @return bool
*/
protected function shouldSkipDirectory(DirectoryIterator $info): bool
{
return !$info->isDir() || $info->isDot() || $info->getFilename()[0] === '.';
}

/**
* @param string $path Directory path.
* @return bool
*/
protected function isPluginDirectory(string $path): bool
{
return is_dir($path . DIRECTORY_SEPARATOR . 'src');
}

/**
* @param string $path Absolute plugin path.
* @param string $root Absolute application root path.
* @return string
*/
protected function getRelativePath(string $path, string $root): string
{
$path = str_replace('\\', '/', $path);
$root = str_replace('\\', '/', $root);

return trim(substr($path, strlen($root)), '/');
}

/**
* Turns relative paths in full paths.
*
Expand Down
9 changes: 9 additions & 0 deletions tests/TestCase/PluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class PluginTest extends TestCase
'plugins/Fee/tests',
'plugins/Foe/src',
'plugins/Fum',
'plugins/LegacyVendor/Childless',
'plugins/YourVendor/YourPlugin/src',
'plugins/YourVendor/YourPlugin/tests',
'app_plugins/Bar/src',
'app_plugins/Bar/tests',
];
Expand Down Expand Up @@ -148,6 +151,7 @@ public function testPreAutoloadDump()
'Foo\\' => 'xyz/Foo/src',
'Fee\\' => 'plugins/Fee/src',
'Foe\\' => 'plugins/Foe/src',
'YourVendor\\YourPlugin\\' => 'plugins/YourVendor/YourPlugin/src',
'Bar\\' => 'app_plugins/Bar/src',
],
];
Expand All @@ -157,6 +161,7 @@ public function testPreAutoloadDump()
'psr-4' => [
'Foo\Test\\' => 'xyz/Foo/tests',
'Fee\Test\\' => 'plugins/Fee/tests',
'YourVendor\\YourPlugin\\Test\\' => 'plugins/YourVendor/YourPlugin/tests',
'Bar\Test\\' => 'app_plugins/Bar/tests',
],
];
Expand Down Expand Up @@ -322,8 +327,10 @@ public function testFindPlugins()
'Foe' => $this->path . '/plugins/Foe',
'Foo' => $this->path . '/plugins/Foo',
'Fum' => $this->path . '/plugins/Fum',
'LegacyVendor' => $this->path . '/plugins/LegacyVendor',
'Princess' => $this->path . '/vendor/cakephp/princess',
'TheThing' => $this->path . '/vendor/cakephp/the-thing',
'YourVendor/YourPlugin' => $this->path . '/plugins/YourVendor/YourPlugin',
];
$this->assertSame($expected, $return, 'Composer and application plugins should be listed');

Expand All @@ -339,8 +346,10 @@ public function testFindPlugins()
'Foe' => $this->path . '/plugins/Foe',
'Foo' => $this->path . '/plugins/Foo',
'Fum' => $this->path . '/plugins/Fum',
'LegacyVendor' => $this->path . '/plugins/LegacyVendor',
'Princess' => $this->path . '/vendor/cakephp/princess',
'TheThing' => $this->path . '/vendor/cakephp/the-thing',
'YourVendor/YourPlugin' => $this->path . '/plugins/YourVendor/YourPlugin',
];
$this->assertSame($expected, $return, 'Composer and application plugins should be listed');
}
Expand Down
Loading