diff --git a/core/lib/Drupal/Core/Plugin/PluginFormBase.php b/core/lib/Drupal/Core/Plugin/PluginFormBase.php index 6277ce5..d0fe1b4 100644 --- a/core/lib/Drupal/Core/Plugin/PluginFormBase.php +++ b/core/lib/Drupal/Core/Plugin/PluginFormBase.php @@ -8,6 +8,9 @@ /** * Provides a base class for plugin forms. + * + * Classes extending this can be in any namespace, but are commonly placed in + * the 'PluginForm' namespace, such as \Drupal\module_name\PluginForm\ClassName. */ abstract class PluginFormBase implements PluginFormInterface, PluginAwareInterface { diff --git a/core/lib/Drupal/Core/Plugin/PluginFormFactory.php b/core/lib/Drupal/Core/Plugin/PluginFormFactory.php index 9c6e88e..4abb7ca 100644 --- a/core/lib/Drupal/Core/Plugin/PluginFormFactory.php +++ b/core/lib/Drupal/Core/Plugin/PluginFormFactory.php @@ -33,10 +33,9 @@ public function __construct(ClassResolverInterface $class_resolver) { * {@inheritdoc} */ public function createInstance(PluginInspectionInterface $plugin, $operation, $fallback_operation = NULL) { - $definition = $plugin->getPluginDefinition(); - if (!isset($definition['form'][$operation])) { + if (!$this->hasFormClass($plugin, $operation)) { // Use the default form class if no form is specified for this operation. - if ($fallback_operation && isset($definition['form'][$fallback_operation])) { + if ($fallback_operation && $this->hasFormClass($plugin, $fallback_operation)) { $operation = $fallback_operation; } else { @@ -44,12 +43,14 @@ public function createInstance(PluginInspectionInterface $plugin, $operation, $f } } + $form_class = $this->getFormClass($plugin, $operation); + // If the form specified is the plugin itself, use it directly. - if (get_class($plugin) === $definition['form'][$operation]) { + if (ltrim(get_class($plugin), '\\') === ltrim($form_class, '\\')) { $form_object = $plugin; } else { - $form_object = $this->classResolver->getInstanceFromDefinition($definition['form'][$operation]); + $form_object = $this->classResolver->getInstanceFromDefinition($form_class); } // Ensure the resulting object is a plugin form. @@ -64,4 +65,27 @@ public function createInstance(PluginInspectionInterface $plugin, $operation, $f return $form_object; } + /** + * {@inheritdoc} + */ + public function hasFormClass(PluginInspectionInterface $plugin, $operation) { + return isset($plugin->getPluginDefinition()['form'][$operation]); + } + + /** + * Gets the plugin form class for a specific operation. + * + * @param \Drupal\Component\Plugin\PluginInspectionInterface $plugin + * The plugin the form is for. + * @param string $operation + * The name of the operation to use, e.g., 'add' or 'edit'. + * + * @return null|string + */ + protected function getFormClass(PluginInspectionInterface $plugin, $operation) { + if ($this->hasFormClass($plugin, $operation)) { + return $plugin->getPluginDefinition()['form'][$operation]; + } + } + } diff --git a/core/lib/Drupal/Core/Plugin/PluginFormFactoryInterface.php b/core/lib/Drupal/Core/Plugin/PluginFormFactoryInterface.php index d5c6818..a37fea8 100644 --- a/core/lib/Drupal/Core/Plugin/PluginFormFactoryInterface.php +++ b/core/lib/Drupal/Core/Plugin/PluginFormFactoryInterface.php @@ -32,4 +32,17 @@ */ public function createInstance(PluginInspectionInterface $plugin, $operation, $fallback_operation = NULL); + /** + * Determines if a given plugin has a form for the specified operation. + * + * @param \Drupal\Component\Plugin\PluginInspectionInterface $plugin + * The plugin the form is for. + * @param string $operation + * The name of the operation to use, e.g., 'add' or 'edit'. + * + * @return bool + * Returns TRUE if the plugin has a form for the operation. + */ + public function hasFormClass(PluginInspectionInterface $plugin, $operation); + } diff --git a/core/modules/block/src/BlockForm.php b/core/modules/block/src/BlockForm.php index d4b4c42..d6d3014 100644 --- a/core/modules/block/src/BlockForm.php +++ b/core/modules/block/src/BlockForm.php @@ -133,7 +133,7 @@ public function form(array $form, FormStateInterface $form_state) { $form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts()); $form['#tree'] = TRUE; - $form['settings'] = $this->getPluginForm($this->entity->getPlugin())->buildConfigurationForm(array(), $form_state); + $form['settings'] = $this->getPluginForm($entity->getPlugin())->buildConfigurationForm(array(), $form_state); $form['visibility'] = $this->buildVisibilityInterface([], $form_state); // If creating a new block, calculate a safe default machine name. @@ -335,13 +335,14 @@ protected function validateVisibility(array $form, FormStateInterface $form_stat public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); + $entity = $this->entity; // The Block Entity form puts all block plugin form elements in the // settings form element, so just pass that to the block for submission. // @todo Find a way to avoid this manipulation. $settings = (new FormState())->setValues($form_state->getValue('settings')); // Call the plugin submit handler. - $block = $this->entity->getPlugin(); + $block = $entity->getPlugin(); $this->getPluginForm($block)->submitConfigurationForm($form, $settings); // If this block is context-aware, set the context mapping. if ($block instanceof ContextAwarePluginInterface && $block->getContextDefinitions()) { @@ -351,30 +352,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) { // Update the original form values. $form_state->setValue('settings', $settings->getValues()); - $this->submitVisibility($form, $form_state); - - // Save the settings of the plugin. - $this->entity->save(); - - drupal_set_message($this->t('The block configuration has been saved.')); - $form_state->setRedirect( - 'block.admin_display_theme', - array( - 'theme' => $form_state->getValue('theme'), - ), - array('query' => array('block-placement' => Html::getClass($this->entity->id()))) - ); - } - - /** - * Helper function to independently submit the visibility UI. - * - * @param array $form - * A nested array form elements comprising the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function submitVisibility(array $form, FormStateInterface $form_state) { + // Submit visibility condition settings. foreach ($form_state->getValue('visibility') as $condition_id => $values) { // Allow the condition to submit the form. $condition = $form_state->get(['conditions', $condition_id]); @@ -389,8 +367,20 @@ protected function submitVisibility(array $form, FormStateInterface $form_state) $condition_configuration = $condition->getConfiguration(); $form_state->setValue(['visibility', $condition_id], $condition_configuration); // Update the visibility conditions on the block. - $this->entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration); + $entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration); } + + // Save the settings of the plugin. + $entity->save(); + + drupal_set_message($this->t('The block configuration has been saved.')); + $form_state->setRedirect( + 'block.admin_display_theme', + array( + 'theme' => $form_state->getValue('theme'), + ), + array('query' => array('block-placement' => Html::getClass($this->entity->id()))) + ); } /** diff --git a/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestMultipleFormsBlock.php b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestMultipleFormsBlock.php index 70412fb..87708c2 100644 --- a/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestMultipleFormsBlock.php +++ b/core/modules/block/tests/modules/block_test/src/Plugin/Block/TestMultipleFormsBlock.php @@ -10,7 +10,7 @@ * @Block( * id = "test_multiple_forms_block", * form = { - * "secondary" = "\Drupal\block_test\Form\EmptyBlockForm" + * "secondary" = "\Drupal\block_test\PluginForm\EmptyBlockForm" * }, * admin_label = @Translation("Multiple forms test block") * ) diff --git a/core/modules/block/tests/modules/block_test/src/Form/EmptyBlockForm.php b/core/modules/block/tests/modules/block_test/src/PluginForm/EmptyBlockForm.php similarity index 92% rename from core/modules/block/tests/modules/block_test/src/Form/EmptyBlockForm.php rename to core/modules/block/tests/modules/block_test/src/PluginForm/EmptyBlockForm.php index 98c6fc5..6a654cb 100644 --- a/core/modules/block/tests/modules/block_test/src/Form/EmptyBlockForm.php +++ b/core/modules/block/tests/modules/block_test/src/PluginForm/EmptyBlockForm.php @@ -1,6 +1,6 @@ prophesize(PluginFormInterface::class); + $expected = $plugin_form->reveal(); + + $plugin = $this->prophesize(PluginInspectionInterface::class); + $plugin->getPluginDefinition()->willReturn([ + 'form' => [ + 'standard_class' => get_class($expected), + ], + ]); + + $result = $this->manager->hasFormClass($plugin->reveal(), 'standard_class'); + $this->assertTrue($result); + } + + /** * @covers ::createInstance */ public function testCreateInstance() { @@ -81,6 +99,23 @@ public function testCreateInstanceUsingPlugin() { /** * @covers ::createInstance */ + public function testCreateInstanceUsingPluginWithSlashes() { + $this->classResolver->getInstanceFromDefinition(Argument::cetera())->shouldNotBeCalled(); + + $plugin = $this->prophesize(PluginInspectionInterface::class)->willImplement(PluginFormInterface::class); + $plugin->getPluginDefinition()->willReturn([ + 'form' => [ + 'configuration' => '\\' . get_class($plugin->reveal()), + ], + ]); + + $form_object = $this->manager->createInstance($plugin->reveal(), 'configuration'); + $this->assertSame($plugin->reveal(), $form_object); + } + + /** + * @covers ::createInstance + */ public function testCreateInstanceDefaultFallback() { $this->classResolver->getInstanceFromDefinition(Argument::cetera())->shouldNotBeCalled();