summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreffulgentsia2016-09-21 01:09:07 -0700
committereffulgentsia2016-09-21 01:09:07 -0700
commit6a4cfa29a1edc7ffdbba364d37703ad31c4c9294 (patch)
tree8f5f2c272fd501cfe93073624e524b6fc023f2b8
parent58f67f617eff209c6dfc649bfc92093332ef1013 (diff)
Issue #2796953 by Wim Leers, mondrake, effulgentsia, catch, mikeryan, ayalon, chx, Fabianx, tim.plunkett, jibran, xjm, slashrsm, phenaproxima, borisson_: [regression] Plugins extending from classes of uninstalled modules lead to fatal error
-rw-r--r--core/lib/Drupal/Core/Block/BlockBase.php18
-rw-r--r--core/lib/Drupal/Core/Plugin/DefaultPluginManager.php6
-rw-r--r--core/lib/Drupal/Core/Plugin/PluginWithFormsTrait.php29
-rw-r--r--core/modules/system/system.install16
-rw-r--r--core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/ExtendingNonInstalledClass.php14
-rw-r--r--core/tests/Drupal/KernelTests/Core/Plugin/Discovery/AnnotatedClassDiscoveryTest.php7
-rw-r--r--core/tests/Drupal/KernelTests/Core/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php7
-rw-r--r--core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php20
-rw-r--r--core/tests/Drupal/Tests/Core/Plugin/PluginWithFormsTraitTest.php67
9 files changed, 161 insertions, 23 deletions
diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php
index bcc3954..02e5def 100644
--- a/core/lib/Drupal/Core/Block/BlockBase.php
+++ b/core/lib/Drupal/Core/Block/BlockBase.php
@@ -11,6 +11,7 @@ use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
+use Drupal\Core\Plugin\PluginWithFormsTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\Component\Transliteration\TransliterationInterface;
@@ -26,6 +27,7 @@ use Drupal\Component\Transliteration\TransliterationInterface;
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface {
use ContextAwarePluginAssignmentTrait;
+ use PluginWithFormsTrait;
/**
* The transliteration service.
@@ -272,20 +274,4 @@ abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginIn
$this->transliteration = $transliteration;
}
- /**
- * {@inheritdoc}
- */
- public function getFormClass($operation) {
- if ($this->hasFormClass($operation)) {
- return $this->getPluginDefinition()['forms'][$operation];
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function hasFormClass($operation) {
- return isset($this->getPluginDefinition()['forms'][$operation]);
- }
-
}
diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
index fa706a4..62decb2 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -247,12 +247,6 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt
if (!empty($this->defaults) && is_array($this->defaults)) {
$definition = NestedArray::mergeDeep($this->defaults, $definition);
}
-
- // If no default form is defined and this plugin implements
- // \Drupal\Core\Plugin\PluginFormInterface, use that for the default form.
- if (!isset($definition['forms']['configure']) && isset($definition['class']) && is_subclass_of($definition['class'], PluginFormInterface::class)) {
- $definition['forms']['configure'] = $definition['class'];
- }
}
/**
diff --git a/core/lib/Drupal/Core/Plugin/PluginWithFormsTrait.php b/core/lib/Drupal/Core/Plugin/PluginWithFormsTrait.php
new file mode 100644
index 0000000..ec71720
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/PluginWithFormsTrait.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\Core\Plugin;
+
+/**
+ * Provides a trait with typical behavior for plugins which have forms.
+ */
+trait PluginWithFormsTrait {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormClass($operation) {
+ if (isset($this->getPluginDefinition()['forms'][$operation])) {
+ return $this->getPluginDefinition()['forms'][$operation];
+ }
+ elseif ($operation === 'configure' && $this instanceof PluginFormInterface) {
+ return static::class;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasFormClass($operation) {
+ return !empty($this->getFormClass($operation));
+ }
+
+}
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index d705b5c..8ab78ff 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1660,6 +1660,11 @@ function system_update_8014() {
*/
/**
+ * @addtogroup updates-8.2.0
+ * @{
+ */
+
+/**
* Fix configuration overrides to not override non existing keys.
*/
function system_update_8200(&$sandbox) {
@@ -1681,3 +1686,14 @@ function system_update_8200(&$sandbox) {
$sandbox['config_names'] = array_diff($sandbox['config_names'], $config_names_to_process);
$sandbox['#finished'] = empty($sandbox['config_names']) ? 1 : ($sandbox['max'] - count($sandbox['config_names'])) / $sandbox['max'];
}
+
+/**
+ * Clear caches due to behavior change in DefaultPluginManager.
+ */
+function system_update_8201() {
+ // Empty update to cause a cache rebuild.
+}
+
+/**
+ * @} End of "addtogroup updates-8.2.0".
+ */
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/ExtendingNonInstalledClass.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/ExtendingNonInstalledClass.php
new file mode 100644
index 0000000..a42a474
--- /dev/null
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/ExtendingNonInstalledClass.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\plugin_test\Plugin\plugin_test\fruit;
+
+use Drupal\non_installed_module\Plugin\plugin_test\fruit\YummyFruit;
+
+/**
+ * @Plugin(
+ * id = "extending_non_installed_class",
+ * label = "A plugin whose class is extending from a non-installed module class",
+ * color = "pink",
+ * )
+ */
+class ExtendingNonInstalledClass extends YummyFruit { }
diff --git a/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/AnnotatedClassDiscoveryTest.php b/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
index bc68dc1..9a714c4 100644
--- a/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
@@ -64,6 +64,13 @@ class AnnotatedClassDiscoveryTest extends DiscoveryTestBase {
'class' => 'Drupal\plugin_test_extended\Plugin\plugin_test\fruit\BigApple',
'provider' => 'plugin_test_extended',
),
+ 'extending_non_installed_class' => array(
+ 'id' => 'extending_non_installed_class',
+ 'label' => 'A plugin whose class is extending from a non-installed module class',
+ 'color' => 'pink',
+ 'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\ExtendingNonInstalledClass',
+ 'provider' => 'plugin_test',
+ ),
);
$base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
diff --git a/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php b/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
index 3d5d8c2..a9fbf79 100644
--- a/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
@@ -71,6 +71,13 @@ class CustomDirectoryAnnotatedClassDiscoveryTest extends DiscoveryTestBase {
'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Orange',
'provider' => 'plugin_test',
),
+ 'extending_non_installed_class' => array(
+ 'id' => 'extending_non_installed_class',
+ 'label' => 'A plugin whose class is extending from a non-installed module class',
+ 'color' => 'pink',
+ 'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\ExtendingNonInstalledClass',
+ 'provider' => 'plugin_test',
+ ),
);
$base_directory = \Drupal::root() . '/core/modules/system/tests/modules/plugin_test/src';
diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
index feac004..ffdac5a 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
@@ -61,6 +61,25 @@ class DefaultPluginManagerTest extends UnitTestCase {
}
/**
+ * Tests the plugin manager with a plugin that extends a non-installed class.
+ */
+ public function testDefaultPluginManagerWithPluginExtendingNonInstalledClass() {
+ $definitions = array();
+ $definitions['extending_non_installed_class'] = array(
+ 'id' => 'extending_non_installed_class',
+ 'label' => 'A plugin whose class is extending from a non-installed module class',
+ 'color' => 'pink',
+ 'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\ExtendingNonInstalledClass',
+ 'provider' => 'plugin_test',
+ );
+
+ $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+ $plugin_manager = new TestPluginManager($this->namespaces, $definitions, $module_handler, 'test_alter_hook', '\Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface');
+ $plugin_manager->getDefinition('plugin_test', FALSE);
+ $this->assertTrue(TRUE, 'No PHP fatal error occurred when retrieving the definitions of a module with plugins that depend on a non-installed module class should not cause a PHP fatal.');
+ }
+
+ /**
* Tests the plugin manager with a disabled module.
*/
public function testDefaultPluginManagerWithDisabledModule() {
@@ -383,7 +402,6 @@ class DefaultPluginManagerTest extends UnitTestCase {
$data['no_form'][] = ['class' => TestPluginForm::class];
$data['no_form'][] = [
'class' => TestPluginForm::class,
- 'forms' => ['configure' => TestPluginForm::class],
'foo' => ['bar' => ['baz']],
];
diff --git a/core/tests/Drupal/Tests/Core/Plugin/PluginWithFormsTraitTest.php b/core/tests/Drupal/Tests/Core/Plugin/PluginWithFormsTraitTest.php
new file mode 100644
index 0000000..7066f78
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/PluginWithFormsTraitTest.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Drupal\Tests\Core\Plugin;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\Plugin\PluginWithFormsInterface;
+use Drupal\Core\Plugin\PluginWithFormsTrait;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\PluginWithFormsTrait
+ * @group Plugin
+ */
+class PluginWithFormsTraitTest extends UnitTestCase {
+
+ /**
+ * @covers ::getFormClass
+ * @covers ::hasFormClass
+ * @dataProvider providerGetFormClass
+ */
+ public function testGetFormClass(PluginWithFormsInterface $block_plugin, $operation, $expected_class) {
+ $this->assertSame($expected_class, $block_plugin->getFormClass($operation));
+ $this->assertSame($expected_class !== NULL, $block_plugin->hasFormClass($operation));
+ }
+
+ /**
+ * @return array
+ */
+ public function providerGetFormClass() {
+ $block_plugin_without_forms = new TestClass([], 'block_plugin_without_forms', [
+ 'provider' => 'block_test',
+ ]);
+ // A block plugin that has a form defined for the 'poke' operation.
+ $block_plugin_with_forms = new TestClass([], 'block_plugin_with_forms', [
+ 'provider' => 'block_test',
+ 'forms' => [
+ 'poke' => static::class,
+ ],
+ ]);
+ return [
+ 'block plugin without forms, "configure" operation' => [$block_plugin_without_forms, 'configure', TestClass::class],
+ 'block plugin without forms, "tickle" operation' => [$block_plugin_without_forms, 'tickle', NULL],
+ 'block plugin withut forms, "poke" operation' => [$block_plugin_without_forms, 'poke', NULL],
+ 'block plugin with forms, "configure" operation' => [$block_plugin_with_forms, 'configure', TestClass::class],
+ 'block plugin with forms, "tickle" operation' => [$block_plugin_with_forms, 'tickle', NULL],
+ 'block plugin with forms, "poke" operation' => [$block_plugin_with_forms, 'poke', static::class],
+ ];
+ }
+
+}
+
+class TestClass extends PluginBase implements PluginWithFormsInterface, PluginFormInterface {
+ use PluginWithFormsTrait;
+
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ return [];
+ }
+
+ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
+ }
+
+ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+ }
+
+}