Commit 6a4cfa29 authored by effulgentsia's avatar effulgentsia

Issue #2796953 by Wim Leers, mondrake, effulgentsia, catch, mikeryan, ayalon,...

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
parent 58f67f61
......@@ -11,6 +11,7 @@
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 @@
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface {
use ContextAwarePluginAssignmentTrait;
use PluginWithFormsTrait;
/**
* The transliteration service.
......@@ -272,20 +274,4 @@ public function setTransliteration(TransliterationInterface $transliteration) {
$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]);
}
}
......@@ -247,12 +247,6 @@ public function processDefinition(&$definition, $plugin_id) {
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'];
}
}
/**
......
<?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));
}
}
......@@ -1659,6 +1659,11 @@ function system_update_8014() {
* @} End of "addtogroup updates-8.0.0-rc".
*/
/**
* @addtogroup updates-8.2.0
* @{
*/
/**
* Fix configuration overrides to not override non existing keys.
*/
......@@ -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".
*/
<?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 { }
......@@ -64,6 +64,13 @@ protected function setUp() {
'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';
......
......@@ -71,6 +71,13 @@ protected function setUp() {
'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';
......
......@@ -60,6 +60,25 @@ protected function setUp() {
$this->namespaces['Drupal\plugin_test'] = $this->root . '/core/modules/system/tests/modules/plugin_test/src';
}
/**
* 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.
*/
......@@ -383,7 +402,6 @@ public function providerTestProcessDefinition() {
$data['no_form'][] = ['class' => TestPluginForm::class];
$data['no_form'][] = [
'class' => TestPluginForm::class,
'forms' => ['configure' => TestPluginForm::class],
'foo' => ['bar' => ['baz']],
];
......
<?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) {
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment