diff --git a/core/modules/outside_in/outside_in.module b/core/modules/outside_in/outside_in.module index adf13c1..90bbba2 100644 --- a/core/modules/outside_in/outside_in.module +++ b/core/modules/outside_in/outside_in.module @@ -5,9 +5,9 @@ * Allows configuring blocks and other configuration from the front-end of the site. */ +use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Component\Utility\Html; -use Drupal\Component\Utility\Xss; + /** * Implements hook_help(). */ @@ -66,3 +66,16 @@ function outside_in_page_bottom(array &$page_top) { '#weight' => -1000 ]; } + +/** + * Implements hook_block_alter(). + */ +function outside_in_block_alter(&$definitions) { + foreach ($definitions as &$definition) { + // If no default form is defined and this plugin implements + // \Drupal\Core\Plugin\PluginFormInterface, use that for the default form. + if (!isset($definition['form']['default']) && is_subclass_of($definition['class'], PluginFormInterface::class)) { + $definition['form']['default'] = $definition['class']; + } + } +} diff --git a/core/modules/outside_in/outside_in.services.yml b/core/modules/outside_in/outside_in.services.yml index 00b78cf..0b7f6b2 100644 --- a/core/modules/outside_in/outside_in.services.yml +++ b/core/modules/outside_in/outside_in.services.yml @@ -4,3 +4,7 @@ services: arguments: ['@title_resolver', '@renderer'] tags: - { name: render.main_content_renderer, format: drupal_offcanvas } + + outside_in.block.manager: + class: Drupal\outside_in\OutsideInBlockManager + arguments: ['@class_resolver'] diff --git a/core/modules/outside_in/src/OperationAwareFormInterface.php b/core/modules/outside_in/src/OperationAwareFormInterface.php new file mode 100644 index 0000000..cf31c58 --- /dev/null +++ b/core/modules/outside_in/src/OperationAwareFormInterface.php @@ -0,0 +1,20 @@ +classResolver = $class_resolver; + } + + /** + * {@inheritdoc} + */ + public function getFormObject(PluginInspectionInterface $plugin, $operation) { + $definition = $plugin->getPluginDefinition(); + if (!isset($definition['form'][$operation])) { + throw new InvalidPluginDefinitionException($plugin->getPluginId(), sprintf('The "%s" plugin did not specify a "%s" form class', $plugin->getPluginId(), $operation)); + } + + // If the form specified is the plugin itself, use it directly. + if (get_class($plugin) === $definition['form'][$operation]) { + $form_object = $plugin; + } + else { + $form_object = $this->classResolver->getInstanceFromDefinition($definition['form'][$operation]); + } + + // Ensure the resulting object is a plugin form. + if (!$form_object instanceof PluginFormInterface) { + throw new InvalidPluginDefinitionException($plugin->getPluginId(), sprintf('The "%s" plugin did not specify a valid "%s" form class, must implement \Drupal\Core\Plugin\PluginFormInterface', $plugin->getPluginId(), $operation)); + } + + if ($form_object instanceof OperationAwareFormInterface) { + $form_object->setOperation($operation); + } + + return $form_object; + } + +} diff --git a/core/modules/outside_in/src/OutsideInBlockManagerInterface.php b/core/modules/outside_in/src/OutsideInBlockManagerInterface.php new file mode 100644 index 0000000..17c9c15 --- /dev/null +++ b/core/modules/outside_in/src/OutsideInBlockManagerInterface.php @@ -0,0 +1,27 @@ +operation = $operation; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + // Intentionally empty. + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + // Intentionally empty. + } + +} diff --git a/core/modules/outside_in/tests/modules/offcanvas_test/src/Plugin/Block/TestBlock.php b/core/modules/outside_in/tests/modules/offcanvas_test/src/Plugin/Block/TestBlock.php index a271cd8..629a902 100644 --- a/core/modules/outside_in/tests/modules/offcanvas_test/src/Plugin/Block/TestBlock.php +++ b/core/modules/outside_in/tests/modules/offcanvas_test/src/Plugin/Block/TestBlock.php @@ -10,6 +10,9 @@ * * @Block( * id = "offcanvas_links_block", + * form = { + * "sidebar" = "\Drupal\offcanvas_test\Form\SidebarForm" + * }, * admin_label = @Translation("Offcanvas test block") * ) */ diff --git a/core/modules/outside_in/tests/src/Kernel/MultipleBlockFormTest.php b/core/modules/outside_in/tests/src/Kernel/MultipleBlockFormTest.php new file mode 100644 index 0000000..302a145 --- /dev/null +++ b/core/modules/outside_in/tests/src/Kernel/MultipleBlockFormTest.php @@ -0,0 +1,37 @@ +createInstance('offcanvas_links_block'); + + $form_object1 = \Drupal::service('outside_in.block.manager')->getFormObject($block, 'default'); + $form_object2 = \Drupal::service('outside_in.block.manager')->getFormObject($block, 'sidebar'); + + // Assert that the block itself is used for the default form. + $this->assertSame($block, $form_object1); + + $expected_sidebar = new SidebarForm(); + $expected_sidebar->setOperation('sidebar'); + $this->assertEquals($expected_sidebar, $form_object2); + } + +} diff --git a/core/modules/outside_in/tests/src/Unit/OutsideInBlockManagerTest.php b/core/modules/outside_in/tests/src/Unit/OutsideInBlockManagerTest.php new file mode 100644 index 0000000..a3d7b58 --- /dev/null +++ b/core/modules/outside_in/tests/src/Unit/OutsideInBlockManagerTest.php @@ -0,0 +1,138 @@ +classResolver = $this->prophesize(ClassResolverInterface::class); + $this->manager = new OutsideInBlockManager($this->classResolver->reveal()); + } + + /** + * @covers ::getFormObject + */ + public function testGetFormObject() { + $plugin_form = $this->prophesize(PluginFormInterface::class); + $expected = $plugin_form->reveal(); + + $this->classResolver->getInstanceFromDefinition(get_class($expected))->willReturn($expected); + + $plugin = $this->prophesize(PluginInspectionInterface::class); + $plugin->getPluginDefinition()->willReturn([ + 'form' => [ + 'standard_class' => get_class($expected), + ], + ]); + + $form_object = $this->manager->getFormObject($plugin->reveal(), 'standard_class'); + $this->assertSame($expected, $form_object); + } + + /** + * @covers ::getFormObject + */ + public function testGetFormObjectUsingPlugin() { + $this->classResolver->getInstanceFromDefinition(Argument::cetera())->shouldNotBeCalled(); + + $plugin = $this->prophesize(PluginInspectionInterface::class)->willImplement(PluginFormInterface::class); + $plugin->getPluginDefinition()->willReturn([ + 'form' => [ + 'default' => get_class($plugin->reveal()), + ], + ]); + + $form_object = $this->manager->getFormObject($plugin->reveal(), 'default'); + $this->assertSame($plugin->reveal(), $form_object); + } + + /** + * @covers ::getFormObject + */ + public function testGetFormObjectOperationAware() { + $plugin_form = $this->prophesize(PluginFormInterface::class)->willImplement(OperationAwareFormInterface::class); + $plugin_form->setOperation('operation_aware')->shouldBeCalled(); + + $expected = $plugin_form->reveal(); + + $this->classResolver->getInstanceFromDefinition(get_class($expected))->willReturn($expected); + + $plugin = $this->prophesize(PluginInspectionInterface::class); + $plugin->getPluginDefinition()->willReturn([ + 'form' => [ + 'operation_aware' => get_class($expected), + ], + ]); + + $form_object = $this->manager->getFormObject($plugin->reveal(), 'operation_aware'); + $this->assertSame($expected, $form_object); + } + + /** + * @covers ::getFormObject + */ + public function testGetFormObjectDefinitionException() { + $this->setExpectedException(InvalidPluginDefinitionException::class, 'The "the_plugin_id" plugin did not specify a "anything" form class'); + + $plugin = $this->prophesize(PluginInspectionInterface::class); + $plugin->getPluginId()->willReturn('the_plugin_id'); + $plugin->getPluginDefinition()->willReturn([]); + + $form_object = $this->manager->getFormObject($plugin->reveal(), 'anything'); + $this->assertSame(NULL, $form_object); + } + + /** + * @covers ::getFormObject + */ + public function testGetFormObjectInvalidException() { + $this->setExpectedException(InvalidPluginDefinitionException::class, 'The "the_plugin_id" plugin did not specify a valid "invalid" form class, must implement \Drupal\Core\Plugin\PluginFormInterface'); + + $expected = new \stdClass(); + $this->classResolver->getInstanceFromDefinition(get_class($expected))->willReturn($expected); + + $plugin = $this->prophesize(PluginInspectionInterface::class); + $plugin->getPluginId()->willReturn('the_plugin_id'); + $plugin->getPluginDefinition()->willReturn([ + 'form' => [ + 'invalid' => get_class($expected), + ], + ]); + + $form_object = $this->manager->getFormObject($plugin->reveal(), 'invalid'); + $this->assertSame(NULL, $form_object); + } + +}