diff --git a/src/Annotation/MediaType.php b/src/Annotation/MediaType.php index 65a3d70..0d43633 100644 --- a/src/Annotation/MediaType.php +++ b/src/Annotation/MediaType.php @@ -40,4 +40,11 @@ class MediaType extends Plugin { */ public $description = ''; + /** + * The field types that can be used as a source field for this type. + * + * @var string[] + */ + public $allowed_field_types = []; + } diff --git a/src/Entity/MediaBundle.php b/src/Entity/MediaBundle.php index 8833f82..4285f0c 100644 --- a/src/Entity/MediaBundle.php +++ b/src/Entity/MediaBundle.php @@ -2,12 +2,15 @@ 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\field\FieldStorageConfigInterface; use Drupal\media_entity\MediaBundleInterface; use Drupal\media_entity\MediaInterface; +use Drupal\media_entity\SourceFieldInterface; /** * Defines the Media bundle configuration entity. @@ -121,7 +124,7 @@ class MediaBundle extends ConfigEntityBundleBase implements MediaBundleInterface /** * Default status of this media bundle. * - * @var array + * @var bool */ public $status = TRUE; @@ -241,4 +244,59 @@ 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) { + $type_plugin = $this->getType(); + + if ($type_plugin instanceof SourceFieldInterface) { + $field = $type_plugin->getSourceField($this); + + $storage = $field->getFieldStorageDefinition(); + if ($storage instanceof FieldStorageConfigInterface) { + $storage->save(); + } + + if ($field->isNew()) { + $entity_type = $field->getTargetEntityTypeId(); + $bundle = $field->getTargetBundle(); + + $plugin_definition = \Drupal::service('plugin.manager.field.field_type') + ->getDefinition($field->getType()); + + if ($field->isDisplayConfigurable('form')) { + $widget = \Drupal::service('plugin.manager.field.widget') + ->createInstance($plugin_definition['default_widget']) + ->getConfiguration(); + + entity_get_form_display($entity_type, $bundle, 'default') + ->setComponent($field->getName(), [ + 'type' => $widget->getPluginId(), + 'settings' => $widget->getConfiguration(), + ]) + ->save(); + } + if ($field->isDisplayConfigurable('view')) { + $formatter = \Drupal::service('plugin.manager.field.formatter') + ->createInstance($plugin_definition['default_formatter']); + + entity_get_display($entity_type, $bundle, 'default') + ->setComponent($field->getName(), [ + 'type' => $formatter->getPluginId(), + 'settings' => $formatter->getConfiguration(), + ]) + ->save(); + } + } + + $field->save(); + } + } + + } + } 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..1d79053 100644 --- a/src/MediaTypeBase.php +++ b/src/MediaTypeBase.php @@ -3,19 +3,20 @@ 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\Field\FieldDefinitionInterface; +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. */ -abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, ContainerFactoryPluginInterface { +abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, SourceFieldInterface, ContainerFactoryPluginInterface { use StringTranslationTrait; /** @@ -105,7 +106,9 @@ abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, C * {@inheritdoc} */ public function defaultConfiguration() { - return []; + return [ + 'source_field' => NULL, + ]; } /** @@ -138,7 +141,31 @@ abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, C * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { - return []; + $options = []; + + /** @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(), $this->pluginDefinition['allowed_field_types']); + $is_base_field = $field->getFieldStorageDefinition()->isBaseField(); + if ($allowed_type && !$is_base_field) { + $options[$field_name] = $field->getLabel(); + } + } + + // 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' => $this->configuration['source_field'], + '#empty_option' => $this->t('- create -'), + '#empty_value' => NULL, + '#options' => $options, + '#disabled' => empty($options), + ]; + + return $form; } /** @@ -158,4 +185,54 @@ abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, C return 'media:' . $media->bundle() . ':' . $media->uuid(); } + /** + * {@inheritdoc} + */ + public function getSourceField(MediaBundleInterface $bundle) { + $id = 'media.' . $bundle->id() . '.' . $this->configuration['source_field']; + + return $this->entityTypeManager + ->getStorage('field_config') + ->load($id) + ?: + $this->createSourceField($bundle); + } + + /** + * Creates the source field definition for a bundle. + * + * @param \Drupal\media_entity\MediaBundleInterface $bundle + * The bundle. + * + * @return \Drupal\field\FieldConfigInterface + * The unsaved field definition. The field storage definition, if new, + * should not be saved either. + */ + abstract protected function createSourceField(MediaBundleInterface $bundle); + + /** + * 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 getSourceFieldName() { + $base_id = 'media.field_media_' . $this->getPluginId(); + $tries = 0; + $storage = $this->entityTypeManager->getStorage('field_storage_config'); + + // Iterate at least once, until no field with the generated ID is found. + do { + $id = $base_id; + // If we've tried before, increment and append suffix. + if ($tries) { + $id .= '_' . ++$tries; + } + $field = $storage->load($id); + } + while ($field); + + return $id; + } + } diff --git a/src/MediaTypeInterface.php b/src/MediaTypeInterface.php index 2a171c2..767217f 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; /** 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/src/Plugin/MediaEntity/Type/Generic.php b/src/Plugin/MediaEntity/Type/Generic.php index b83ba9d..523abd5 100644 --- a/src/Plugin/MediaEntity/Type/Generic.php +++ b/src/Plugin/MediaEntity/Type/Generic.php @@ -3,6 +3,7 @@ namespace Drupal\media_entity\Plugin\MediaEntity\Type; use Drupal\Core\Form\FormStateInterface; +use Drupal\media_entity\MediaBundleInterface; use Drupal\media_entity\MediaInterface; use Drupal\media_entity\MediaTypeBase; @@ -50,4 +51,27 @@ class Generic extends MediaTypeBase { return $form; } + /** + * {@inheritdoc} + */ + protected function createSourceField(MediaBundleInterface $bundle) { + $field_storage = $this->entityTypeManager + ->getStorage('field_storage_config') + ->create([ + 'entity_type' => 'media', + 'field_name' => $this->getSourceFieldName(), + // Strings are harmless, inoffensive puppies: a good choice for a + // generic media type. + 'type' => 'string', + ]); + + /** @var \Drupal\field\FieldConfigInterface $field */ + return $this->entityTypeManager + ->getStorage('field_config') + ->create([ + 'field_storage' => $field_storage, + 'bundle' => $bundle->id(), + ]); + } + } diff --git a/src/SourceFieldInterface.php b/src/SourceFieldInterface.php new file mode 100644 index 0000000..9c6ad65 --- /dev/null +++ b/src/SourceFieldInterface.php @@ -0,0 +1,20 @@ + ['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); + } + +}