diff --git a/src/helpers/Api.php b/src/helpers/Api.php index b33de9418a1..074bf1fe3e4 100644 --- a/src/helpers/Api.php +++ b/src/helpers/Api.php @@ -201,7 +201,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 new file mode 100644 index 00000000000..567da620c93 --- /dev/null +++ b/tests/unit/helpers/ApiHelperTest.php @@ -0,0 +1,131 @@ +_craftWasWarmed = true; + } + } + + protected function tearDown(): void + { + if ($this->_craftWasWarmed) { + TestSetup::tearDownCraft(); + } + } + + public function testPluginLicenseWriteIsSkippedWhenProjectConfigIsReadOnly(): void + { + $cache = Craft::$app->getCache(); + $cache->delete(App::CACHE_KEY_LICENSE_INFO); + + $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' => 'example-plugin:123;standard;valid', + ]); + + $licenseInfo = $cache->get(App::CACHE_KEY_LICENSE_INFO); + self::assertSame('123', $licenseInfo['example-plugin']['id']); + self::assertSame('standard', $licenseInfo['example-plugin']['edition']); + self::assertSame('valid', $licenseInfo['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(); + $cache->delete('licensedDomain'); + + $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], + ]; + } +}