Skip to content

Commit

Permalink
Feature cli (#8)
Browse files Browse the repository at this point in the history
* version constraint syntax

* Add CI job for satis repo update

* tried to make postinstall work with a event handlers

* Initial draft of writing config to var/config and symlinking to old locations

* Add trailing semicolon (syntax)

* Add quotation marks for app fileroot

* Move static dir out of horde dir

* Stub of a horde-specific command

* Add missing getCapabilities

* Flesh out stub of HordeReconfigureCommand

* Add phpstan action

* Fix issues found by phpstan

* Raise to phpstan level 3

* Fix issues found by phpstan

* Move to phpstan level 5

* horde-reconfigure command can now rebuild js and config symlinks

* Fix phpstan detected issues

* Write horde.local.php files during horde-reconfigure command

* Handle copying presets and linking themes

* Add type hints suggested by phpstan level 6

* Ensure always returning a string, not false

* Handle doc/registry.d snippets

Co-authored-by: Koch, Corbinian <koch@b1-systems.de>
Co-authored-by: Midah Pasche <midahannep@gmail.com>
  • Loading branch information
3 people committed Nov 3, 2021
1 parent 21127f6 commit afd3e4a
Show file tree
Hide file tree
Showing 13 changed files with 561 additions and 24 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/phpstan.yml
@@ -0,0 +1,18 @@
name: PHPStan
on:
push:
workflow_dispatch:
pull_request:

jobs:
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: php-actions/composer@v5

- name: PHPStan Static Analysis
uses: php-actions/phpstan@v3
with:
path: src/
level: 5
16 changes: 16 additions & 0 deletions src/CommandProvider.php
@@ -0,0 +1,16 @@
<?php
declare(strict_types = 1);
namespace Horde\Composer;

use Composer\Plugin\Capability\CommandProvider as CommandProviderCapability;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Command\BaseCommand;

class CommandProvider implements CommandProviderCapability
{
public function getCommands()
{
return [new HordeReconfigureCommand];
}
}
69 changes: 69 additions & 0 deletions src/ConfigLinker.php
@@ -0,0 +1,69 @@
<?php
declare(strict_types = 1);
namespace Horde\Composer;
use \DirectoryIterator;
use \RecursiveDirectoryIterator;
use \RecursiveIteratorIterator;

class ConfigLinker
{
private string $baseDir;
private string $configDir;
private string $vendorDir;
private string $webDir;

public function __construct(string $baseDir)
{
$this->baseDir = $baseDir;
$this->vendorDir = $baseDir . '/vendor';
$this->webDir= $baseDir . '/web';
$this->configDir = $this->baseDir . '/var/config';
}
/**
* Symlink contents of var/config
*
* We always check the whole tree even though this may happen
* multiple times in installations with many apps
*
* @return void
*/
public function run(): void
{
// Abort unless var/config exists and is readable
if (!is_dir($this->configDir) || !is_readable($this->configDir)) {
return;
}
// Iterate through subdirs
foreach (new DirectoryIterator($this->configDir) as $appFileInfo) {
if (!$appFileInfo->isDir()) {
continue;
}
if ($appFileInfo->isDot()) {
continue;
}
$app = $appFileInfo->getFilename();
// Next if no corresponding web/$app/config dir exists
$appConfigDir = $appFileInfo->getPathname();
$targetDir = $this->webDir . '/' . $app . '/config/';
if (!is_dir($targetDir)) {
continue;
}
// Iterate recursively
$contentInfo = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($appConfigDir));
foreach ($contentInfo as $contentItem) {
// Don't symlink dirs
if ($contentItem->isDir()) {
continue;
}
$relativeName = $contentInfo->getSubPathname();
$linkName = $targetDir . '/' . $relativeName;
$sourceName = $appConfigDir . '/' . $relativeName;
if (file_exists($linkName)) {
continue;
}
symlink($sourceName, $linkName);
}
// Do not overwrite existing files or links
}
}
}
19 changes: 12 additions & 7 deletions src/HordeInstaller.php
Expand Up @@ -82,11 +82,11 @@ class HordeInstaller extends LibraryInstaller
protected function setupDirs(PackageInterface $package): void
{
[$this->vendorName, $this->packageName] = explode('/', $package->getName(), 2);
$this->projectRoot = realpath(dirname(Factory::getComposerFile()));
$this->projectRoot = (string)realpath(dirname(Factory::getComposerFile()));
$this->webDir = $this->projectRoot . '/web';
$this->configDir = $this->projectRoot . '/var/config';
$this->hordeConfigDir = $this->projectRoot . '/var/config/horde';
$this->appConfigDir = $this->projectRoot . '/var/config/horde';
$this->appConfigDir = $this->projectRoot . '/var/config/' . $this->packageName;
$this->hordeWebDir = $this->webDir . '/horde';
$this->configRegistryDir = $this->configDir . '/horde/registry.d/';
$this->jsDir = $this->webDir . '/js';
Expand Down Expand Up @@ -158,7 +158,6 @@ public function update(InstalledRepositoryInterface $repo, PackageInterface $ini
/**
* Handle horde-specific postinstall tasks
*
* @param InstalledRepositoryInterface $repo The repository
* @param PackageInterface $package The package installed or updated
*/
public function postinstall(PackageInterface $package): void
Expand All @@ -181,6 +180,10 @@ public function postinstall(PackageInterface $package): void
if (!file_exists($this->appConfigDir)) {
mkdir($this->appConfigDir, 0750, true);
}
$staticDir = $this->webDir . '/static';
if (!file_exists($staticDir)) {
mkdir($staticDir, 0750, true);
}
// Type horde-application needs a config/horde.local.php pointing to horde dir
// If a horde-application has a registry snippet in doc-dir, fetch it and put it into config/registry.d
if (is_dir($this->packageDocRegistryDir)) {
Expand Down Expand Up @@ -234,15 +237,17 @@ public function postinstall(PackageInterface $package): void
'$this->applications[\'horde\'][\'webroot\'] = $app_webroot;' . PHP_EOL .
'$this->applications[\'horde\'][\'jsfs\'] = $deployment_fileroot . \'/js/horde/\';' . PHP_EOL .
'$this->applications[\'horde\'][\'jsuri\'] = $deployment_webroot . \'js/horde/\';' . PHP_EOL .
'$this->applications[\'horde\'][\'staticfs\'] = $deployment_fileroot . \'/static\';' . PHP_EOL .
'$this->applications[\'horde\'][\'staticuri\'] = $deployment_webroot . \'static\';' . PHP_EOL .
'$this->applications[\'horde\'][\'themesfs\'] = $deployment_fileroot . \'/themes/horde/\';' . PHP_EOL .
'$this->applications[\'horde\'][\'themesuri\'] = $deployment_webroot . \'/themes/horde/\';';
'$this->applications[\'horde\'][\'themesuri\'] = $deployment_webroot . \'themes/horde/\';';
file_put_contents($registryLocalFilePath, $registryLocalFileContent);
}
} else {
// A registry snippet should ensure the install dir is known
$registryAppFilename = $this->configRegistryDir . 'location-' . $app . '.php';
$registryAppSnippet = '<?php' . PHP_EOL .
'$this->applications[\'' . $app . '\'][\'fileroot\'] = $deployment_fileroot/' . $app . PHP_EOL .
'$this->applications[\'' . $app . '\'][\'fileroot\'] = "$deployment_fileroot/' . $app . '";' . PHP_EOL .
'$this->applications[\'' . $app . '\'][\'webroot\'] = $this->applications[\'horde\'][\'webroot\'] . \'/../' . $app . "';" . PHP_EOL .
'$this->applications[\'' . $app . '\'][\'themesfs\'] = $this->applications[\'horde\'][\'fileroot\'] . \'/../themes/' . $app . '/\';' . PHP_EOL .
'$this->applications[\'' . $app . '\'][\'themesuri\'] = $this->applications[\'horde\'][\'webroot\'] . \'/../themes/' . $app . '/\';';
Expand Down Expand Up @@ -322,7 +327,7 @@ public function linkVarConfig(): void
}
}

public function linkJavaScript($package, $app = 'horde'): void
public function linkJavaScript(PackageInterface $package, string $app = 'horde'): void
{
// TODO: Error handling
$packageJsDir = $this->getInstallPath($package) . '/js/';
Expand Down Expand Up @@ -350,7 +355,7 @@ public function linkJavaScript($package, $app = 'horde'): void
}

// Work around case inconsistencies, hard requires etc until they are resolved in code
protected function _legacyWorkaround($path): string
protected function _legacyWorkaround(string $path): string
{
return sprintf("ini_set('include_path', '%s/horde/autoloader/lib%s%s/horde/form/lib/%s' . ini_get('include_path'));
require_once('%s/horde/core/lib/Horde/Core/Nosql.php');
Expand Down
47 changes: 39 additions & 8 deletions src/HordeInstallerPlugin.php
Expand Up @@ -5,39 +5,52 @@
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Installer\PackageEvent;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\Plugin\Capable;

class HordeInstallerPlugin implements PluginInterface, EventSubscriberInterface
class HordeInstallerPlugin implements PluginInterface, EventSubscriberInterface, Capable
{
protected $installer;
protected HordeInstaller $installer;

public function activate(Composer $composer, IOInterface $io)
public function activate(Composer $composer, IOInterface $io): void
{
$this->installer = new HordeInstaller($io, $composer);
$composer->getInstallationManager()->addInstaller($this->installer);
}

public function deactivate(Composer $composer, IOInterface $io)
public function deactivate(Composer $composer, IOInterface $io): void
{
$composer->getInstallationManager()->removeInstaller($this->installer);
}

public function uninstall(Composer $composer, IOInterface $io)
public function uninstall(Composer $composer, IOInterface $io): void
{
return;
}

public static function getSubscribedEvents()
/**
* Exposre which events are handled by which handler
*
* @return string[]
*/
public static function getSubscribedEvents(): array
{
return [
'post-package-install' => 'postInstallHandler',
'post-package-update' => 'postUpdateHandler',
];
}

public function postInstallHandler($event)
/**
* Handler for post-package-install
*
* @param PackageEvent $event
* @return void
*/
public function postInstallHandler(PackageEvent $event): void
{
$ops = $event->getOperations();
foreach($ops as $op) {
Expand All @@ -51,7 +64,13 @@ public function postInstallHandler($event)
}
}

public function postUpdateHandler($event)
/**
* Handler for post-package-update
*
* @param PackageEvent $event
* @return void
*/
public function postUpdateHandler(PackageEvent $event): void
{
$ops = $event->getOperations();
foreach($ops as $op) {
Expand All @@ -65,6 +84,18 @@ public function postUpdateHandler($event)
}
}

/**
* Expose capabilities
*
* @return string[]
*/
public function getCapabilities(): array
{
return [
'Composer\Plugin\Capability\CommandProvider' => 'Horde\Composer\CommandProvider',
];
}

public function deactivate(Composer $composer, IOInterface $io)
{
$installer = new HordeInstaller($io, $composer);
Expand Down
83 changes: 83 additions & 0 deletions src/HordeLocalFileWriter.php
@@ -0,0 +1,83 @@
<?php
declare(strict_types = 1);
namespace Horde\Composer;
use \Composer\Util\Filesystem;

class HordeLocalFileWriter
{
/**
* List of apps
*
* @var string[]
*/
private array $apps;
private string $baseDir;
private string $configDir;
private string $vendorDir;
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;
$this->baseDir = $baseDir;
$this->configDir = $baseDir . '/var/config';
$this->vendorDir = $baseDir . '/vendor';
$this->webDir = $baseDir . '/web';
$this->apps = $apps;
}

public function run(): void
{
foreach ($this->apps as $app) {
$this->processApp($app);
}
}

private function processApp(string $app): void
{
$hordeWebDir = $this->webDir . '/horde';
list($vendor, $name) = explode('/', $app, 2);
$this->filesystem->ensureDirectoryExists($this->configDir . "/$name");
$path = $this->configDir . "/$name/horde.local.php";
$hordeLocalFileContent = sprintf(
"<?php if (!defined('HORDE_BASE')) define('HORDE_BASE', '%s');\n",
$hordeWebDir
);
// special case horde/horde needs to require the composer autoloader
if ($app == 'horde/horde') {
$hordeLocalFileContent .= $this->_legacyWorkaround(realpath($this->vendorDir));
$hordeLocalFileContent .= "require_once('" . $this->vendorDir ."/autoload.php');";
}
$this->filesystem->filePutContentsIfModified($path, $hordeLocalFileContent);
}
/**
* Legacy support
*
* Work around case inconsistencies
* hard requires etc until they are resolved in code
*
* @param string $path Path to vendor dir
* @return string
*/
protected function _legacyWorkaround(string $path): string
{
return sprintf("ini_set('include_path', '%s/horde/autoloader/lib%s%s/horde/form/lib/%s' . ini_get('include_path'));
require_once('%s/horde/core/lib/Horde/Core/Nosql.php');
",
$path,
PATH_SEPARATOR,
$path,
PATH_SEPARATOR,
$path
);
}
}

0 comments on commit afd3e4a

Please sign in to comment.