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
7 changes: 4 additions & 3 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
<?php
$potentialDirs = ['/lib', '/src', '/tests'];
$potentialDirs = ['/lib', '/src', '/test', '/tests'];

$finder = (new PhpCsFixer\Finder());

foreach ($potentialDirs as $dir) {
$full = __DIR__ . $dir;
if (is_dir($full)) {
$finder->in($full);
}
}

$finder->exclude(['fixtures']);

return (new PhpCsFixer\Config())
->setRules([
'@PER-CS' => true,
'@PHP82Migration' => true,
'@PHP83Migration' => true,
'php_unit_test_class_requires_covers' => true,
'nullable_type_declaration_for_default_null_value' => true,
])
->setFinder($finder)
;
92 changes: 89 additions & 3 deletions src/ApplicationLinker.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
$this->filesystem->ensureDirectoryExists($webDir);
// Ensure we have a static dir for ephemeral, generated files ...
$this->filesystem->ensureDirectoryExists($webDir . '/static');

// TODO: Move implementations to separate classes
foreach ($this->appPackages as $app) {
if ($app === 'horde/components') {
continue;
Expand All @@ -72,12 +72,15 @@
'files' => [
'LICENSE', 'composer.json', 'composer.lock', '.gitattributes',
'.horde.yml', '.travis.yml', 'package.xml', 'phpunit.xml.dist',
'.gitignore', 'README.rst',
'.gitignore', 'README.rst', 'README.md', 'README', 'CHANGELOG.md',
'.php-cs-fixer.dist.php', '.php-cs-fixer.cache', 'phpunit.xml',
],
'dirs' => [
'doc',
'test',
'bin',
'lib',
'src',
'script',
'scripts',
'static', // static should be ensured to exist in webdir.
Expand Down Expand Up @@ -116,10 +119,93 @@
$appWebDir . '/' . $name
);
}
} elseif ($this->mode == 'proxy') {
// This list is different from the one for the linker
$filterList = [

'files' => [
'.gitignore',
'.gitattributes',
'README.rst',
'README',
'LICENSE',
'phpunit.xml',
'phpunit.xml.dist',
'composer.json',
],
'dirs' => [
'bin',
'lib',
'src',
'.git',
'.github',
'doc',
'js',
'script',
'scripts',
'static',
'config',
'locale',
'themes',
'templates',
'vendor',
],
];
$this->filesystem->emptyDirectory($appWebDir, true);
// We are already per-app
// appWebDir and appVendorDir are already set
$r = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($appVendorDir));
foreach ($r as $f => $entry) {
// Ignore directories as such - they are created if they have relevant files
if ($entry->isDir()) {

Check failure on line 160 in src/ApplicationLinker.php

View workflow job for this annotation

GitHub Actions / CI

Cannot call method isDir() on mixed.
continue;
}
$name = $entry->getFilename();

Check failure on line 163 in src/ApplicationLinker.php

View workflow job for this annotation

GitHub Actions / CI

Cannot call method getFilename() on mixed.
$relativePathName = $r->getSubPathname();
$relativePath = $r->getSubPath();
$split = explode(DIRECTORY_SEPARATOR, $relativePathName, 3);
// Skip subpaths of the filtered dirs
if (in_array(
$split[0],
$filterList['dirs']
)) {
continue;
}
if (in_array(
$name,
$filterList['files']
)) {
continue;
}
$this->filesystem->ensureDirectoryExists($appWebDir . DIRECTORY_SEPARATOR . $relativePath);
$pathProxyToAutoloader = $this->filesystem->findShortestPath(
$appWebDir . DIRECTORY_SEPARATOR . $relativePathName,
$vendorDir . DIRECTORY_SEPARATOR . 'autoload.php',
preferRelative: true
);
$pathProxyToFile = $this->filesystem->findShortestPath(
$appWebDir . DIRECTORY_SEPARATOR . $relativePathName,
$appVendorDir . DIRECTORY_SEPARATOR . $relativePathName,
preferRelative: true
);

$originalContent = file_get_contents($appVendorDir . DIRECTORY_SEPARATOR . $relativePathName);
if (str_contains((string) $originalContent, '<?php')) {
// PHP files get a proxy
$content = "<?php\nrequire_once(__DIR__ . '/$pathProxyToAutoloader');\nrequire_once(__DIR__ . '/$pathProxyToFile');";
} else {
// Non-PHP files get copied as is
$content = $originalContent;
}
$this->filesystem->filePutContentsIfModified(
$appWebDir . DIRECTORY_SEPARATOR . $relativePathName,
$content

Check failure on line 202 in src/ApplicationLinker.php

View workflow job for this annotation

GitHub Actions / CI

Parameter #2 $content of method Composer\Util\Filesystem::filePutContentsIfModified() expects string, string|false given.
);
} // EndForEach File
} else {
$copy = new RecursiveCopy($appVendorDir, $appWebDir, array_merge($filterList['files'], $filterList['dirs']));
$copy->copy();
}
}
} // EndForEach App
}
}
13 changes: 10 additions & 3 deletions src/HordeLocalFileWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class HordeLocalFileWriter
private string $webDir;

private Filesystem $filesystem;
private string $vendorHordeDir;

/**
* Undocumented function
Expand All @@ -27,11 +28,12 @@ class HordeLocalFileWriter
* @param string $baseDir
* @param string[] $apps
*/
public function __construct(Filesystem $filesystem, string $baseDir, array $apps)
public function __construct(Filesystem $filesystem, string $baseDir, array $apps, private string $mode = 'symlink')
{
$this->filesystem = $filesystem;
$this->configDir = $baseDir . '/var/config';
$this->vendorDir = $baseDir . '/vendor';
$this->vendorHordeDir = $this->vendorDir . DIRECTORY_SEPARATOR . 'horde' . DIRECTORY_SEPARATOR . 'horde';
$this->webDir = $baseDir . '/web';
$this->apps = $apps;
}
Expand All @@ -49,9 +51,14 @@ private function processApp(string $app): void
[$vendor, $name] = explode('/', $app, 2);
$this->filesystem->ensureDirectoryExists($this->configDir . "/$name");
$path = $this->configDir . "/$name/horde.local.php";
$hordeBaseDir = $hordeWebDir;
if ($this->mode === 'proxy') {
$hordeBaseDir = $this->vendorHordeDir;
}
$hordeLocalFileContent = sprintf(
"<?php if (!defined('HORDE_BASE')) define('HORDE_BASE', '%s');\n",
$hordeWebDir
"<?php if (!defined('HORDE_BASE')) define('HORDE_BASE', '%s');\nif (!defined('HORDE_CONFIG_BASE')) define('HORDE_CONFIG_BASE', '%s');\n",
$hordeBaseDir,
$this->configDir
);
// special case horde/horde needs to require the composer autoloader
if ($app == 'horde/horde') {
Expand Down
11 changes: 9 additions & 2 deletions src/HordeReconfigureCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Composer\Command\BaseCommand;
use Composer\Factory as ComposerFactory;
use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -17,6 +18,7 @@
protected function configure(): void
{
$this->setName('horde:reconfigure')->setAliases(['horde-reconfigure']);
$this->addOption('mode', 'm', InputOption::VALUE_OPTIONAL, 'Should we "symlink" or "proxy" to the webdir?', 'symlink');
$this->setDescription('Rewrite autogenerated configuration');
$this->setHelp(
<<<EOT
Expand All @@ -37,12 +39,17 @@
if (!$composer) {
die('Error: Command was run without a relation to composer itself');
}
$mode = $input->getOption('mode');
if (!in_array($mode, ['symlink', 'proxy', 'copy'])) {
$output->writeln('<error>Invalid mode. Must be "symlink", "proxy" or "copy". "symlink" is the current default.</error>');
return 1;
}
// This is needed to support PHP 7.4 (no union types) for both Composer 2.2 / 2.3
// Cannot use instanceof here as the class will not exist in 2.2.
if (get_class($composer) === 'Composer\PartialComposer') {
$flow = HordeReconfigureFlow::fromPartialComposer($composer, new SymphonyOutputAdapter($output));
$flow = HordeReconfigureFlow::fromPartialComposer($composer, new SymphonyOutputAdapter($output), $mode);

Check failure on line 50 in src/HordeReconfigureCommand.php

View workflow job for this annotation

GitHub Actions / CI

Parameter #3 $mode of static method Horde\Composer\HordeReconfigureFlow::fromPartialComposer() expects string, mixed given.
} else {
$flow = HordeReconfigureFlow::fromComposer($composer, new SymphonyOutputAdapter($output));
$flow = HordeReconfigureFlow::fromComposer($composer, new SymphonyOutputAdapter($output), $mode);

Check failure on line 52 in src/HordeReconfigureCommand.php

View workflow job for this annotation

GitHub Actions / CI

Parameter #3 $mode of static method Horde\Composer\HordeReconfigureFlow::fromComposer() expects string, mixed given.
}
return $flow->run();
}
Expand Down
22 changes: 14 additions & 8 deletions src/HordeReconfigureFlow.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\Console\Output\OutputInterface;
use Horde\Composer\IOAdapter\SymphonyOutputAdapter;
use RuntimeException;
use strncasecmp;

class HordeReconfigureFlow
{
Expand All @@ -42,14 +43,14 @@ public function __construct(DirectoryTree $tree, FlowIoInterface $io, string $mo
* @param FlowIoInterface|null $output
* @return self
*/
public static function fromComposer(Composer $composer, ?FlowIoInterface $output = null): self
public static function fromComposer(Composer $composer, ?FlowIoInterface $output = null, string $mode = 'symlink'): self
{
return self::fromAnyComposer($composer, $output);
return self::fromAnyComposer($composer, $output, $mode);
}

public static function fromPartialComposer(PartialComposer $composer, ?FlowIoInterface $output = null): self
public static function fromPartialComposer(PartialComposer $composer, ?FlowIoInterface $output = null, string $mode = 'symlink'): self
{
return self::fromAnyComposer($composer, $output);
return self::fromAnyComposer($composer, $output, $mode);
}

/**
Expand All @@ -63,9 +64,12 @@ public static function fromPartialComposer(PartialComposer $composer, ?FlowIoInt
*
* @TODO Refactor this once we require PHP 8.0 or higher
*/
private static function fromAnyComposer($composer, ?FlowIoInterface $output = null): self
private static function fromAnyComposer($composer, ?FlowIoInterface $output = null, string $mode = 'symlink'): self
{
$mode = \strncasecmp(\PHP_OS, 'WIN', 3) === 0 ? 'copy' : 'symlink';
// Symlink mode does not work on Windows
if ($mode == 'symlink') {
$mode = strncasecmp(\PHP_OS, 'WIN', 3) === 0 ? 'copy' : 'symlink';
}
$tree = DirectoryTree::fromComposerJsonPath(ComposerFactory::getComposerFile());
$vendorDir = $composer->getConfig()->get('vendor-dir');
if (!is_string($vendorDir)) {
Expand Down Expand Up @@ -100,18 +104,20 @@ public function run(): int
$filesystem
);
$snippetHandler->handle();

$this->io->writeln('Configuration mode: ' . $this->mode);
$this->io->writeln('Writing app configs to /var/config dir');
$registrySnippetFileWriter = new RegistrySnippetFileWriter(
$filesystem,
$rootPackageDir,
$hordeApps
$hordeApps,
$this->mode
);
$registrySnippetFileWriter->run();
$hordeLocalWriter = new HordeLocalFileWriter(
$filesystem,
$rootPackageDir,
$hordeApps,
$this->mode
);
$hordeLocalWriter->run();
$this->io->writeln('Linking app configs to /web Dir');
Expand Down
4 changes: 2 additions & 2 deletions src/JsTreeLinker.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function run(): void
// app javascript dirs are exposed under js/$app
foreach ($this->apps as $app) {
[$vendor, $name] = explode('/', $app, 2);
$appPath = $this->webDir . '/' . $name;
$appPath = $this->vendorDir . '/' . $vendor . '/' . $name;
$jsSourcePath = $appPath . '/js';
if (!$this->filesystem->isReadable($jsSourcePath)) {
continue;
Expand Down Expand Up @@ -115,7 +115,7 @@ public function linkDir(string $sourceDir, string $targetDir): void
}
$sourceFile = $sourceDir . '/' . $sourceItem;
$targetFile = $targetDir . '/' . $sourceItem;
if ($this->mode === 'symlink') {
if (in_array($this->mode, ['symlink', 'proxy'])) {
$this->filesystem->relativeSymlink($sourceFile, $targetFile);
} else {
if (is_file($sourceFile)) {
Expand Down
25 changes: 17 additions & 8 deletions src/RegistrySnippetFileWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,25 @@ class RegistrySnippetFileWriter
private string $configRegistryDir;
private string $webDir;

private Filesystem $filesystem;

/**
* Undocumented function
*
* @param Filesystem $filesystem
* @param string $baseDir
* @param string[] $apps
*/
public function __construct(Filesystem $filesystem, string $baseDir, array $apps)
{
$this->filesystem = $filesystem;
public function __construct(
private Filesystem $filesystem,
/**
* The config dir for the registry
*/
private string $baseDir,
array $apps,
private string $mode = 'symlink'
) {
/**
* The config dir for the registry
*/
$this->configDir = $baseDir . '/var/config';
/**
* The config dir for the registry snippets
Expand All @@ -48,7 +55,7 @@ public function run(): void
'/**' . PHP_EOL .
' * AUTOGENERATED ONLY IF ABSENT' . PHP_EOL .
' * Edit this file to match your needs' . PHP_EOL .
' * To redo, delete file and run `composer horde-reconfigure`' . PHP_EOL .
' * To redo, delete file and run `composer horde-reconfigure --mode=' . $this->mode . '`' . PHP_EOL .
' */' . PHP_EOL;
$registry00FileContent .= sprintf(
'$deployment_webroot = \'%s\';
Expand Down Expand Up @@ -91,10 +98,12 @@ public function run(): void
} else {
// A registry snippet should ensure the install dir is known
$registryAppFilename = $this->configRegistryDir . '/02-location-' . $appName . '.php';
$appInVendorDir = $this->baseDir . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $appVendor . DIRECTORY_SEPARATOR . $appName . DIRECTORY_SEPARATOR;
$registryAppSnippet .=
'$this->applications[\'' . $appName . '\'][\'fileroot\'] = "$deployment_fileroot/' . $appName . '";' . PHP_EOL .
'$this->applications[\'' . $appName . '\'][\'fileroot\'] = \'' . $appInVendorDir . '\';' . PHP_EOL .
'$this->applications[\'' . $appName . '\'][\'templates\'] = \'' . $appInVendorDir . 'templates' . DIRECTORY_SEPARATOR . '\';' . PHP_EOL .
'$this->applications[\'' . $appName . '\'][\'webroot\'] = $this->applications[\'horde\'][\'webroot\'] . \'/../' . $appName . "';" . PHP_EOL .
'$this->applications[\'' . $appName . '\'][\'themesfs\'] = $this->applications[\'horde\'][\'fileroot\'] . \'/../themes/' . $appName . '/\';' . PHP_EOL .
'$this->applications[\'' . $appName . '\'][\'themesfs\'] = \'' . $this->webDir . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR . '\';' . PHP_EOL .
'$this->applications[\'' . $appName . '\'][\'themesuri\'] = $this->applications[\'horde\'][\'webroot\'] . \'/../themes/' . $appName . '/\';';
}
$this->filesystem->filePutContentsIfModified($registryAppFilename, $registryAppSnippet);
Expand Down
Loading
Loading