diff --git a/src/helpers/Api.php b/src/helpers/Api.php index ef5e68d4592..b04d0792489 100644 --- a/src/helpers/Api.php +++ b/src/helpers/Api.php @@ -204,7 +204,7 @@ public static function processResponseHeaders(array $headers): void // did we just get any new plugin license keys? $pluginsService = Craft::$app->getPlugins(); - if (isset($headers['x-craft-plugin-licenses'])) { + if (isset($headers['x-craft-plugin-licenses']) && !Craft::$app->getProjectConfig()->readOnly) { $pluginLicenseKeys = explode(',', reset($headers['x-craft-plugin-licenses'])); foreach ($pluginLicenseKeys as $key) { [$pluginHandle, $key] = explode(':', $key); diff --git a/tests/unit/helpers/ApiHelperTest.php b/tests/unit/helpers/ApiHelperTest.php index cbb184be318..d4a8764d2db 100644 --- a/tests/unit/helpers/ApiHelperTest.php +++ b/tests/unit/helpers/ApiHelperTest.php @@ -10,6 +10,7 @@ use Craft; use craft\helpers\Api; use craft\helpers\App; +use craft\services\Plugins; use craft\test\TestCase; /** @@ -79,6 +80,97 @@ public function testLicenseInfo(): void self::assertNull($licenseInfo['plugin-invalid']['id']); } + public function testPluginLicenseWriteIsSkippedWhenProjectConfigIsReadOnly(): void + { + $cache = Craft::$app->getCache(); + + $projectConfig = Craft::$app->getProjectConfig(); + $readOnly = $projectConfig->readOnly; + $plugins = Craft::$app->getPlugins(); + + try { + $projectConfig->readOnly = true; + $pluginsMock = $this->createMock(Plugins::class); + $pluginsMock->expects(self::never()) + ->method('setPluginLicenseKey'); + Craft::$app->set('plugins', $pluginsMock); + + Api::processResponseHeaders([ + 'X-Craft-Plugin-Licenses' => 'example-plugin:license-key', + 'X-Craft-License-Info' => 'plugin-example-plugin:123;standard;valid', + ]); + + $licenseInfo = $cache->get(App::CACHE_KEY_LICENSE_INFO); + self::assertSame('123', $licenseInfo['plugin-example-plugin']['id']); + self::assertSame('standard', $licenseInfo['plugin-example-plugin']['edition']); + self::assertSame('valid', $licenseInfo['plugin-example-plugin']['status']); + } finally { + Craft::$app->set('plugins', $plugins); + $projectConfig->readOnly = $readOnly; + } + } + + public function testPluginLicenseWriteOccursWhenProjectConfigIsWritable(): void + { + $projectConfig = Craft::$app->getProjectConfig(); + $readOnly = $projectConfig->readOnly; + $plugins = Craft::$app->getPlugins(); + + try { + $projectConfig->readOnly = false; + $pluginsMock = $this->createMock(Plugins::class); + $pluginsMock->expects(self::once()) + ->method('setPluginLicenseKey') + ->with('example-plugin', 'license-key') + ->willReturn(true); + Craft::$app->set('plugins', $pluginsMock); + + Api::processResponseHeaders([ + 'X-Craft-Plugin-Licenses' => 'example-plugin:license-key', + ]); + } finally { + Craft::$app->set('plugins', $plugins); + $projectConfig->readOnly = $readOnly; + } + } + + /** + * @dataProvider projectConfigReadOnlyDataProvider + */ + public function testResponseWithoutPluginLicensesDoesNotWritePluginLicense(bool $readOnly): void + { + $cache = Craft::$app->getCache(); + + $projectConfig = Craft::$app->getProjectConfig(); + $originalReadOnly = $projectConfig->readOnly; + $plugins = Craft::$app->getPlugins(); + + try { + $projectConfig->readOnly = $readOnly; + $pluginsMock = $this->createMock(Plugins::class); + $pluginsMock->expects(self::never()) + ->method('setPluginLicenseKey'); + Craft::$app->set('plugins', $pluginsMock); + + Api::processResponseHeaders([ + 'X-Craft-License-Domain' => 'example.test', + ]); + + self::assertSame('example.test', $cache->get('licensedDomain')); + } finally { + Craft::$app->set('plugins', $plugins); + $projectConfig->readOnly = $originalReadOnly; + } + } + + public static function projectConfigReadOnlyDataProvider(): array + { + return [ + [false], + [true], + ]; + } + protected function _before(): void { parent::_before();