diff --git a/app/Http/Controllers/PluginController.php b/app/Http/Controllers/PluginController.php index 6f0fc2a4..e2bcd9df 100644 --- a/app/Http/Controllers/PluginController.php +++ b/app/Http/Controllers/PluginController.php @@ -34,7 +34,7 @@ class PluginController extends Controller if ($result === true) { return json(trans('admin.plugins.operations.enabled', ['plugin' => $plugin->title]), 0); } else { - $reason = $result['unsatisfied']->map(function ($detail, $name) { + $unsatisfied = $result['unsatisfied']->map(function ($detail, $name) { $constraint = $detail['constraint']; if (! $detail['version']) { return trans('admin.plugins.operations.unsatisfied.disabled', compact('name')); @@ -43,6 +43,12 @@ class PluginController extends Controller } })->values()->all(); + $conflicts = $result['conflicts']->map(function ($detail, $name) { + return trans('admin.plugins.operations.unsatisfied.conflict', compact('name')); + })->values()->all(); + + $reason = array_merge($unsatisfied, $conflicts); + return json(trans('admin.plugins.operations.unsatisfied.notice'), 1, compact('reason')); } diff --git a/app/Services/PluginManager.php b/app/Services/PluginManager.php index 916cabe5..f8aef4be 100644 --- a/app/Services/PluginManager.php +++ b/app/Services/PluginManager.php @@ -269,8 +269,9 @@ class PluginManager $plugin = is_string($plugin) ? $this->get($plugin) : $plugin; if ($plugin && ! $plugin->isEnabled()) { $unsatisfied = $this->getUnsatisfied($plugin); - if ($unsatisfied->isNotEmpty()) { - return compact('unsatisfied'); + $conflicts = $this->getConflicts($plugin); + if ($unsatisfied->isNotEmpty() || $conflicts->isNotEmpty()) { + return compact('unsatisfied', 'conflicts'); } $this->enabled->put($plugin->name, ['version' => $plugin->version]); @@ -335,7 +336,6 @@ class PluginManager } /** - * @param Plugin $plugin * @return Collection */ public function getUnsatisfied(Plugin $plugin) @@ -366,6 +366,22 @@ class PluginManager }); } + /** + * @return Collection + */ + public function getConflicts(Plugin $plugin) + { + return collect($plugin->getManifestAttr('enchants.conflicts', [])) + ->mapWithKeys(function ($constraint, $name) { + $info = $this->enabled->get($name); + if ($info && Semver::satisfies($info['version'], $constraint)) { + return [$name => ['version' => $info['version'], 'constraint' => $constraint]]; + } else { + return []; + } + }); + } + /** * The plugins path. * diff --git a/resources/lang/en/admin.yml b/resources/lang/en/admin.yml index 3142b9ee..715e7540 100644 --- a/resources/lang/en/admin.yml +++ b/resources/lang/en/admin.yml @@ -95,9 +95,10 @@ plugins: title: Operations enabled: :plugin has been enabled. unsatisfied: - notice: There are unsatisfied dependencies in the plugin, therefore we can't enable it. Please install or update the plugins listed below. - disabled: "The `:name` plugin is not enabled" - version: "The version of `:name` does not satisfies the constraint `:constraint`" + notice: There are conflicts or unsatisfied dependencies in the plugin, therefore we can't enable it. Please install or update the plugins listed below, and disable those have conflicts. + disabled: 'The :name plugin is not enabled.' + version: 'The version of :name does not satisfies the constraint `:constraint`.' + conflict: 'The :name plugin cannot run with this plugin at the same time.' disabled: :plugin has been disabled. deleted: The plugin was deleted successfully. no-config-notice: The plugin is not installed or doesn't provide a configuration page. diff --git a/resources/lang/zh_CN/admin.yml b/resources/lang/zh_CN/admin.yml index 5c22f062..56dc73d5 100644 --- a/resources/lang/zh_CN/admin.yml +++ b/resources/lang/zh_CN/admin.yml @@ -100,9 +100,10 @@ plugins: title: 操作 enabled: :plugin 已启用 unsatisfied: - notice: 无法启用此插件,因为其仍有未满足的依赖关系。请检查以下插件的版本,更新或安装它们: - disabled: "`:name` 插件未启用" - version: "`:name` 的版本不符合要求 `:constraint`" + notice: 无法启用此插件,因为其仍有冲突或未满足的依赖关系。请检查以下插件的版本,更新或安装它们并禁用存在冲突的插件: + disabled: ':name 插件未启用' + version: ':name 的版本不符合要求 `:constraint`' + conflict: ':name 插件与此插件不能同时运行' disabled: :plugin 已禁用 deleted: 插件已被成功删除 no-config-notice: 插件未安装或未提供配置页面 diff --git a/tests/PluginControllerTest.php b/tests/PluginControllerTest.php index b1d97ff8..6e113dcb 100644 --- a/tests/PluginControllerTest.php +++ b/tests/PluginControllerTest.php @@ -97,6 +97,9 @@ class PluginControllerTest extends TestCase 'dep' => ['version' => '0.0.0', 'constraint' => '^6.6.6'], 'whatever' => ['version' => null, 'constraint' => '^1.2.3'], ]), + 'conflicts' => collect([ + 'conf' => ['version' => '1.2.3', 'constraint' => '^1.0.0'], + ]), ]); $mock->shouldReceive('get') @@ -152,9 +155,8 @@ class PluginControllerTest extends TestCase 'name' => 'dep', 'constraint' => '^6.6.6', ]), - trans('admin.plugins.operations.unsatisfied.disabled', [ - 'name' => 'whatever', - ]), + trans('admin.plugins.operations.unsatisfied.disabled', ['name' => 'whatever']), + trans('admin.plugins.operations.unsatisfied.conflict', ['name' => 'conf']), ], ], ]); diff --git a/tests/ServicesTest/PluginManagerTest.php b/tests/ServicesTest/PluginManagerTest.php index 15451bc0..1e0bfc1a 100644 --- a/tests/ServicesTest/PluginManagerTest.php +++ b/tests/ServicesTest/PluginManagerTest.php @@ -393,6 +393,26 @@ class PluginManagerTest extends TestCase $this->assertFalse($manager->getUnsatisfied($plugin)->has('another-plugin')); } + public function testGetConflicts() + { + $manager = app('plugins'); + + $plugin = new Plugin('/', ['enchants' => ['conflicts' => ['a' => '*', 'b' => '^1.2.0']]]); + $reflection = new ReflectionClass($manager); + $property = $reflection->getProperty('enabled'); + $property->setAccessible(true); + $property->setValue($manager, collect(['b' => ['version' => '1.2.3']])); + + $conflicts = $manager->getConflicts($plugin); + $this->assertNull($conflicts->get('a')); + $info = $conflicts->get('b'); + $this->assertEquals('1.2.3', $info['version']); + $this->assertEquals('^1.2.0', $info['constraint']); + + $plugin = new Plugin('/', ['enchants' => ['conflicts' => ['b' => '^0.0.0']]]); + $this->assertNull($manager->getConflicts($plugin)->get('b')); + } + public function testEnable() { Event::fake();