diff --git a/src/Entity/MediaBundle.php b/src/Entity/MediaBundle.php index 8833f82..1a9d5f8 100644 --- a/src/Entity/MediaBundle.php +++ b/src/Entity/MediaBundle.php @@ -2,8 +2,9 @@ namespace Drupal\media_entity\Entity; -use Drupal\Core\Entity\EntityDescriptionInterface; use Drupal\Core\Config\Entity\ConfigEntityBundleBase; +use Drupal\Core\Entity\EntityDescriptionInterface; +use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityWithPluginCollectionInterface; use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection; use Drupal\media_entity\MediaBundleInterface; @@ -121,7 +122,7 @@ class MediaBundle extends ConfigEntityBundleBase implements MediaBundleInterface /** * Default status of this media bundle. * - * @var array + * @var bool */ public $status = TRUE; @@ -241,4 +242,17 @@ class MediaBundle extends ConfigEntityBundleBase implements MediaBundleInterface $this->new_revision = $new_revision; } + /** + * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + + if (!$update) { + // Inform the plugin that this bundle has just been created. + $this->getType()->onBundleSave($this->id()); + } + + } + } diff --git a/src/MediaBundleForm.php b/src/MediaBundleForm.php index 014a538..5d310f0 100644 --- a/src/MediaBundleForm.php +++ b/src/MediaBundleForm.php @@ -373,7 +373,7 @@ class MediaBundleForm extends EntityForm { // Override the "status" base field default value, for this bundle. $fields = $this->entityFieldManager->getFieldDefinitions('media', $bundle->id()); - $media = $this->entityTypeManager->getStorage('media')->create(array('bundle' => $bundle->id())); + $media = $this->entityTypeManager->getStorage('media')->create(['bundle' => $bundle->id()]); $value = (bool) $form_state->getValue(['options', 'status']); if ($media->status->value != $value) { $fields['status']->getConfig($bundle->id())->setDefaultValue($value)->save(); diff --git a/src/MediaTypeBase.php b/src/MediaTypeBase.php index a472e7d..b2da7e8 100644 --- a/src/MediaTypeBase.php +++ b/src/MediaTypeBase.php @@ -3,14 +3,14 @@ namespace Drupal\media_entity; use Drupal\Component\Plugin\PluginBase; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Config\Config; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\Component\Utility\NestedArray; +use Drupal\Core\StringTranslation\StringTranslationTrait; use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\Core\Form\FormStateInterface; /** * Base implementation of media type plugin. @@ -158,4 +158,9 @@ abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, C return 'media:' . $media->bundle() . ':' . $media->uuid(); } + /** + * {@inheritdoc} + */ + public function onBundleSave($bundle_name) {} + } diff --git a/src/MediaTypeInterface.php b/src/MediaTypeInterface.php index 2a171c2..09ca4c4 100644 --- a/src/MediaTypeInterface.php +++ b/src/MediaTypeInterface.php @@ -2,8 +2,8 @@ namespace Drupal\media_entity; -use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Component\Plugin\ConfigurablePluginInterface; +use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Plugin\PluginFormInterface; /** @@ -85,4 +85,12 @@ interface MediaTypeInterface extends PluginInspectionInterface, ConfigurablePlug */ public function getDefaultName(MediaInterface $media); + /** + * Take actions when saving a bundle. + * + * @param string $bundle_name + * The machine-name of the bundle saved. + */ + public function onBundleSave($bundle_name); + } diff --git a/src/MediaTypeSourceFieldTrait.php b/src/MediaTypeSourceFieldTrait.php new file mode 100644 index 0000000..7067882 --- /dev/null +++ b/src/MediaTypeSourceFieldTrait.php @@ -0,0 +1,233 @@ +sourceFieldInfo(); + $options = []; + + $allowed_field_types = $source_field_info['allowed_types']; + /** @var \Drupal\media_entity\MediaBundleInterface $bundle */ + $bundle = $form_state->getFormObject()->getEntity(); + foreach ($this->entityFieldManager->getFieldDefinitions('media', $bundle->id()) as $field_name => $field) { + $allowed_type = in_array($field->getType(), $allowed_field_types); + $is_base_field = $field->getFieldStorageDefinition()->isBaseField(); + if ($allowed_type && !$is_base_field) { + $options[$field_name] = $field->getLabel(); + } + } + + // Add a checkbox to allow the field being created automatically on save. + // Only include it when the bundle is new, so we can use its presence to + // know we can safely override the source field configuration. + if ($bundle->isNew()) { + $form['create_source_field'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Create a source field automatically when saving this form.'), + '#description' => $this->t('If checked, a default field will be created and used as a source field. If you uncheck the field, you will need to create a field and revisit this form later to select it.'), + '#default_value' => TRUE, + ]; + } + + // Select the source field. Only show it when the bundle is not new, so + // there will potentially be fields to select. + $form['source_field'] = [ + '#type' => 'select', + '#title' => $this->t('Field with source information'), + '#default_value' => isset($this->configuration['source_field']) ? $this->configuration['source_field'] : '', + '#empty_option' => $this->t('- None -'), + '#empty_value' => '', + '#options' => $options, + '#access' => !$bundle->isNew(), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function onBundleSave($bundle_name) { + // If we are creating a new bundle and the user elected to have the default + // field created, defaultSourceFieldName will have been set from + // setConfiguration() and determineDefaultSourceFieldName(). + // @see determineDefaultSourceFieldName() + if (isset($this->defaultSourceFieldName)) { + $this->createDefaultSourceField($bundle_name); + } + } + + /** + * {@inheritdoc} + */ + protected function createDefaultSourceField($bundle_name) { + $source_field_info = $this->sourceFieldInfo(); + + if (!in_array($source_field_info['default_type'], $source_field_info['allowed_types'])) { + throw new \LogicException('Default source field type is not within the allowed field types.'); + } + + $field_name = $this->defaultSourceFieldName; + + $storage = FieldStorageConfig::create([ + 'field_name' => $field_name, + 'entity_type' => 'media', + 'type' => $source_field_info['default_type'], + ]); + $storage->save(); + + // Create the field instance. + FieldConfig::create([ + 'entity_type' => 'media', + 'field_name' => $field_name, + 'label' => $source_field_info['default_label'], + 'required' => TRUE, + 'bundle' => $bundle_name, + ])->save(); + + // Make the field visible on the form display. + /** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */ + $form_display = EntityFormDisplay::create([ + 'targetEntityType' => 'media', + 'bundle' => $bundle_name, + 'mode' => 'default', + 'status' => TRUE, + ]); + $form_display->setComponent($field_name, [ + 'type' => $source_field_info['default_widget'], + ])->save(); + + // Make the field visible on the media entity itself. + /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */ + $display = EntityViewDisplay::create([ + 'targetEntityType' => 'media', + 'bundle' => $bundle_name, + 'mode' => 'default', + 'status' => TRUE, + ]); + $display->setComponent($field_name, [ + 'type' => $source_field_info['default_formatter'], + ])->save(); + } + + /** + * Determine a free field name to use as the default field. + * + * @return string + * An appropriate field name that was determined to be available. + */ + protected function determineDefaultSourceFieldName() { + // Determine a unique field name. + if (isset($this->defaultSourceFieldName)) { + return $this->defaultSourceFieldName; + } + + $base_field_name = 'field_media_' . $this->getPluginId(); + $field_name_candidate = $base_field_name; + $postfix = ''; + while (FieldStorageConfig::loadByName('media', $field_name_candidate)) { + if (empty($postfix)) { + $postfix = 1; + } + else { + $postfix++; + } + + $field_name_candidate = $base_field_name . '_' . $postfix; + } + + $this->defaultSourceFieldName = $field_name_candidate; + + return $this->defaultSourceFieldName; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + // Take out the create_source_field key when present so it doesn't end up + // in the configuration. Record it was set so we can later decide to + // actually create the field. Determine the field name here so it correctly + // is entered into the configuration. + if (isset($configuration['create_source_field'])) { + $create_source_field = $configuration['create_source_field']; + unset($configuration['create_source_field']); + + if ($create_source_field) { + $configuration['source_field'] = $this->determineDefaultSourceFieldName(); + } + } + + parent::setConfiguration($configuration); + } + +} diff --git a/src/Plugin/Action/DeleteMedia.php b/src/Plugin/Action/DeleteMedia.php index b2a98d2..4a5a48e 100644 --- a/src/Plugin/Action/DeleteMedia.php +++ b/src/Plugin/Action/DeleteMedia.php @@ -84,7 +84,7 @@ class DeleteMedia extends ActionBase implements ContainerFactoryPluginInterface * {@inheritdoc} */ public function execute($object = NULL) { - $this->executeMultiple(array($object)); + $this->executeMultiple([$object]); } /** diff --git a/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml b/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml index 6ff7e14..257c840 100644 --- a/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml +++ b/tests/modules/media_entity_test_type/config/schema/media_entity_test_type.schema.yml @@ -5,3 +5,6 @@ media_entity.bundle.type.test_type: test_config_value: type: string label: 'Test config value' + source_field: + type: string + label: 'Source field' diff --git a/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php index 60d68a0..d34114d 100644 --- a/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php +++ b/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php @@ -2,7 +2,12 @@ namespace Drupal\media_entity_test_type\Plugin\MediaEntity\Type; +use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\Core\Entity\Entity\EntityViewDisplay; use Drupal\Core\Form\FormStateInterface; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\media_entity\MediaTypeSourceFieldTrait; use Drupal\media_entity\Plugin\MediaEntity\Type\Generic; /** @@ -15,6 +20,20 @@ use Drupal\media_entity\Plugin\MediaEntity\Type\Generic; * ) */ class TestType extends Generic { + use MediaTypeSourceFieldTrait; + + /** + * {@inheritdoc} + */ + public function sourceFieldInfo() { + return [ + 'allowed_types' => ['string'], + 'default_label' => $this->t('Test source'), + 'default_type' => 'string', + 'default_widget' => 'string_textfield', + 'default_formatter' => 'string', + ]; + } /** * {@inheritdoc} @@ -45,6 +64,10 @@ class TestType extends Generic { '#default_value' => empty($this->configuration['test_config_value']) ? NULL : $this->configuration['test_config_value'], ]; + $form = $this->sourceFieldConfigurationForm($form, $form_state); + + $form['source_field']['#description'] = $this->t('Field on media entity that stores the source information.'); + return $form; } diff --git a/tests/src/FunctionalJavascript/BundleCreationTest.php b/tests/src/FunctionalJavascript/BundleCreationTest.php new file mode 100644 index 0000000..385e79d --- /dev/null +++ b/tests/src/FunctionalJavascript/BundleCreationTest.php @@ -0,0 +1,100 @@ +drupalGet('admin/structure/media/add'); + $page = $this->getSession()->getPage(); + + // Fill in a label to the bundle. + $page->fillField('label', $label); + // Wait for machine name generation. Default: waitUntilVisible(), does not + // work properly. + $this->getSession() + ->wait(5000, "jQuery('.machine-name-value').text() === '{$bundleMachineName}'"); + + // Select our test bundle type. + $this->assertSession()->fieldExists('Type provider'); + $this->assertSession()->optionExists('Type provider', 'test_type'); + $page->selectFieldOption('Type provider', 'test_type'); + $this->assertSession()->assertWaitOnAjaxRequest(); + + // Make sure the checkbox for creating the source field is there and save. + $this->assertSession() + ->checkboxChecked('type_configuration[test_type][create_source_field]'); + $page->pressButton('Save media bundle'); + + // Check whether the source field was correctly created. + $this->drupalGet("admin/structure/media/manage/{$bundleMachineName}/fields"); + + // Check 2nd column of first data row, to be machine name for field name. + $this->assertSession() + ->elementContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[2]', 'field_media_test_type'); + // Check 3rd column of first data row, to be correct field type. + $this->assertSession() + ->elementTextContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[3]', 'Text (plain)'); + + // Check that the source field is correctly assigned to media bundle. + $this->drupalGet("admin/structure/media/manage/{$bundleMachineName}"); + + $this->assertSession() + ->fieldValueEquals('type_configuration[test_type][source_field]', 'field_media_test_type'); + } + + /** + * Test creation of media bundle without default field. + */ + public function testBundleCreationWithoutDefaultField() { + $label = 'Bundle without Default Field'; + $bundleMachineName = str_replace(' ', '_', strtolower($label)); + + $this->drupalGet('admin/structure/media/add'); + $page = $this->getSession()->getPage(); + + // Fill in a label to the bundle. + $page->fillField('label', 'Bundle without Default Field'); + + // Wait for machine name generation. Default: waitUntilVisible(), does not + // work properly. + $this->getSession() + ->wait(5000, "jQuery('.machine-name-value').text() === '{$bundleMachineName}'"); + + // Select our test bundle type. + $this->assertSession()->fieldExists('Type provider'); + $this->assertSession()->optionExists('Type provider', 'test_type'); + $page->selectFieldOption('Type provider', 'test_type'); + $this->assertSession()->assertWaitOnAjaxRequest(); + + // Un-check creation of default source field. + $page->uncheckField('type_configuration[test_type][create_source_field]'); + $page->pressButton('Save media bundle'); + + // Check that there are not fields created. + $this->drupalGet("admin/structure/media/manage/{$bundleMachineName}/fields"); + $this->assertSession() + ->elementsCount('xpath', '//*[text()="No fields are present yet."]', 1); + } + +}