From 003a69c8189cbae3b16ad575ce649ad1c8dfbeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 3 May 2024 10:50:59 +0200 Subject: [PATCH 1/4] Fix config preserve during composer update --- .github/workflows/ci.yml | 13 +++++++++++++ src/ExtensionInstaller.php | 8 +++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b39dee6..3ccccbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,3 +116,16 @@ jobs: cd test-composer/vendor/roundcube/roundcubemail ls -lah plugins/acl/config.* if [ ! -f plugins/acl/config.inc.php ]; then echo 'Config file was not created' && exit 1; fi + + - name: Test update - install plugin + run: | + cd test-composer + echo '// xxx no config update xxx' >> vendor/roundcube/roundcubemail/plugins/carddav/config.inc.php + composer update -v --prefer-dist --no-interaction --no-progress roundcube/carddav --prefer-lowest + + - name: Test update - verify install + run: | + cd test-composer + ls -lah vendor/roundcube/roundcubemail/plugins/carddav/config.* + if [ ! -f vendor/roundcube/roundcubemail/plugins/carddav/config.inc.php ]; then echo 'Config file was deleted' && exit 1; fi + if ! grep -Fq 'xxx no config update xxx' vendor/roundcube/roundcubemail/plugins/carddav/config.inc.php; then echo 'Config file was replaced' && exit 1; fi diff --git a/src/ExtensionInstaller.php b/src/ExtensionInstaller.php index 8cfbd61..e24cd01 100644 --- a/src/ExtensionInstaller.php +++ b/src/ExtensionInstaller.php @@ -63,13 +63,19 @@ public function getInstallPath(PackageInterface $package) private function initializeRoundcubemailEnvironment(): void { - // initialize Roundcube environment if (!defined('INSTALL_PATH')) { define('INSTALL_PATH', $this->getRoundcubemailInstallPath() . '/'); } require_once INSTALL_PATH . 'program/include/iniset.php'; } + public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) + { + $this->setRoundcubemailInstallPath($repo); + + return parent::isInstalled($repo, $package); + } + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->setRoundcubemailInstallPath($repo); From de87a3c488a12f53a82d7e11d4e690c364b920e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 3 May 2024 11:25:14 +0200 Subject: [PATCH 2/4] Make sure "roundcube/roundcubemail" install path is always available --- composer.json | 4 ++-- src/ExtensionInstaller.php | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index f08d7df..54b491a 100644 --- a/composer.json +++ b/composer.json @@ -19,11 +19,11 @@ ], "require": { "php": ">=7.3 <8.4", - "composer-plugin-api": "^1.0 || ^2.0", + "composer-plugin-api": "^2.1", "roundcube/roundcubemail": "*" }, "require-dev": { - "composer/composer": "^2.0", + "composer/composer": "^2.1", "ergebnis/composer-normalize": "^2.13", "friendsofphp/php-cs-fixer": "^3.0", "phpstan/extension-installer": "^1.1", diff --git a/src/ExtensionInstaller.php b/src/ExtensionInstaller.php index e24cd01..618b3e5 100644 --- a/src/ExtensionInstaller.php +++ b/src/ExtensionInstaller.php @@ -2,6 +2,7 @@ namespace Roundcube\Composer; +use Composer\Installer\InstallationManager; use Composer\Installer\LibraryInstaller; use Composer\Package\PackageInterface; use Composer\Package\Version\VersionParser; @@ -43,15 +44,26 @@ protected function setRoundcubemailInstallPath(InstalledRepositoryInterface $ins protected function getRoundcubemailInstallPath(): string { + // install path is not set at composer download phase + // never assume any path, but for this known composer behaviour get it from backtrace instead + if ($this->roundcubemailInstallPath === null) { + $backtrace = debug_backtrace(); + foreach ($backtrace as $frame) { + // relies on https://github.com/composer/composer/blob/2.7.4/src/Composer/Installer/InstallationManager.php#L243 + if (($frame['object'] ?? null) instanceof InstallationManager + && $frame['function'] === 'downloadAndExecuteBatch' + ) { + $this->setRoundcubemailInstallPath($frame['args'][0]); + } + } + } + return $this->roundcubemailInstallPath; } public function getInstallPath(PackageInterface $package) { - if ( - !$this->supports($package->getType()) - || $this->roundcubemailInstallPath === null // install path is not known at download phase - ) { + if (!$this->supports($package->getType())) { return parent::getInstallPath($package); } From aa7ed43d07b2cd10ab76d78ba6da268c77bf92c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 3 May 2024 11:20:12 +0200 Subject: [PATCH 3/4] Make sure the "roundcube/roundcubemail" install path never changes --- src/ExtensionInstaller.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ExtensionInstaller.php b/src/ExtensionInstaller.php index 618b3e5..3ea5a77 100644 --- a/src/ExtensionInstaller.php +++ b/src/ExtensionInstaller.php @@ -36,9 +36,15 @@ protected function setRoundcubemailInstallPath(InstalledRepositoryInterface $ins if ($roundcubemailPackage === $rootPackage) { // $this->getInstallPath($package) does not work for root package $this->initializeVendorDir(); - $this->roundcubemailInstallPath = dirname($this->vendorDir); + $installPath = dirname($this->vendorDir); } else { - $this->roundcubemailInstallPath = $this->getInstallPath($roundcubemailPackage); + $installPath = $this->getInstallPath($roundcubemailPackage); + } + + if ($this->roundcubemailInstallPath === null) { + $this->roundcubemailInstallPath = $installPath; + } elseif ($this->roundcubemailInstallPath !== $installPath) { + throw new \Exception('Install path of "roundcube/roundcubemail" package has unexpectedly changed'); } } From a762400c7825906b08a19da8eda6e1e5e0257840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 12 May 2024 19:21:20 +0200 Subject: [PATCH 4/4] Add override method attribute --- phpstan.neon.dist | 1 + src/ExtensionInstaller.php | 6 ++++++ src/PluginInstaller.php | 3 +++ src/RoundcubeInstaller.php | 3 +++ src/SkinInstaller.php | 3 +++ 5 files changed, 16 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3812e72..f8f6daf 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -3,6 +3,7 @@ includes: parameters: level: 4 + checkMissingOverrideMethodAttribute: true paths: - . excludePaths: diff --git a/src/ExtensionInstaller.php b/src/ExtensionInstaller.php index 3ea5a77..b4272ac 100644 --- a/src/ExtensionInstaller.php +++ b/src/ExtensionInstaller.php @@ -67,6 +67,7 @@ protected function getRoundcubemailInstallPath(): string return $this->roundcubemailInstallPath; } + #[\Override] public function getInstallPath(PackageInterface $package) { if (!$this->supports($package->getType())) { @@ -87,6 +88,7 @@ private function initializeRoundcubemailEnvironment(): void require_once INSTALL_PATH . 'program/include/iniset.php'; } + #[\Override] public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->setRoundcubemailInstallPath($repo); @@ -94,6 +96,7 @@ public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface return parent::isInstalled($repo, $package); } + #[\Override] public function install(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->setRoundcubemailInstallPath($repo); @@ -155,6 +158,7 @@ public function install(InstalledRepositoryInterface $repo, PackageInterface $pa return null; } + #[\Override] public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) { $this->setRoundcubemailInstallPath($repo); @@ -220,6 +224,7 @@ public function update(InstalledRepositoryInterface $repo, PackageInterface $ini return null; } + #[\Override] public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) { $this->setRoundcubemailInstallPath($repo); @@ -261,6 +266,7 @@ public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $ return null; } + #[\Override] public function supports($packageType) { return $packageType === $this->composer_type; diff --git a/src/PluginInstaller.php b/src/PluginInstaller.php index e09fbe4..99356c0 100644 --- a/src/PluginInstaller.php +++ b/src/PluginInstaller.php @@ -6,11 +6,13 @@ class PluginInstaller extends ExtensionInstaller { protected $composer_type = 'roundcube-plugin'; + #[\Override] public function getVendorDir() { return $this->getRoundcubemailInstallPath() . \DIRECTORY_SEPARATOR . 'plugins'; } + #[\Override] protected function confirmInstall($package_name) { $config = $this->composer->getConfig()->get('roundcube'); @@ -24,6 +26,7 @@ protected function confirmInstall($package_name) return $answer; } + #[\Override] protected function getConfig($package_name, $config, $add) { $cur_config = !empty($config['plugins']) diff --git a/src/RoundcubeInstaller.php b/src/RoundcubeInstaller.php index a7f81fe..5f117c5 100644 --- a/src/RoundcubeInstaller.php +++ b/src/RoundcubeInstaller.php @@ -11,6 +11,7 @@ class RoundcubeInstaller implements PluginInterface private $extentions = [PluginInstaller::class, SkinInstaller::class]; private $installers = []; + #[\Override] public function activate(Composer $composer, IOInterface $io) { foreach ($this->extentions as $extension) { @@ -20,6 +21,7 @@ public function activate(Composer $composer, IOInterface $io) } } + #[\Override] public function deactivate(Composer $composer, IOInterface $io) { foreach ($this->installers as $installer) { @@ -27,5 +29,6 @@ public function deactivate(Composer $composer, IOInterface $io) } } + #[\Override] public function uninstall(Composer $composer, IOInterface $io) {} } diff --git a/src/SkinInstaller.php b/src/SkinInstaller.php index 4d9cee5..1e8c29b 100644 --- a/src/SkinInstaller.php +++ b/src/SkinInstaller.php @@ -6,11 +6,13 @@ class SkinInstaller extends ExtensionInstaller { protected $composer_type = 'roundcube-skin'; + #[\Override] public function getVendorDir() { return $this->getRoundcubemailInstallPath() . \DIRECTORY_SEPARATOR . 'skins'; } + #[\Override] protected function confirmInstall($package_name) { $config = $this->composer->getConfig()->get('roundcube'); @@ -24,6 +26,7 @@ protected function confirmInstall($package_name) return $answer; } + #[\Override] protected function getConfig($package_name, $config, $add) { $cur_config = !empty($config['skin'])