Add credit for this issue to the following contributors just prior to commit: https://www.drupal.org/files/issues/contributors.txt
(Not yet since we are trying to keep this issue on one page. There are 66 names, so this probably does not require Infrastructure intervention.)

Problem/Motivation

In past few months (since Dublin) we reached the agreement to bring the contributed Media Entity module to core (under the name Media), which will allow us to implement better and more powerful media handling.

For more info see #2801277: [META] Support remote media assets as first-class citizens by adding Media Entity module in core, #2825215: Media initiative: Essentials - first round of improvements in core and #2786785: Media in Drupal 8 Initiative.

This is blocker for all issues that we'll be focusing on at Media sprint in Berlin (starts on December 12th 2016). Marking as major for that reason.

Proposed resolution

Bring Media entity module in core mostly as it is. Any changes made will need upgrade paths for the existing module users.

Remove integrations with few contrib modules: Inline entity form, Devel Generate and Plugin. We will open follow-up issues in those modules after this lands.

Media entity is stable and extensively used and there are no urgent changes planned for it. Other steps, which will happen after and not directly change Media entity are explained in #2801277: [META] Support remote media assets as first-class citizens by adding Media Entity module in core.

Remaining tasks

  • Reviewers and patch contributors: The Media entity issue reviews spreadsheet is being used to track the hundreds of points of feedback on the issue. Enter new comments at the top. See #333 for instructions.
  • MOST IMPORTANT: reviewers please identify any must have issues that should happen in this issue or followups to ensure the module gets stable before 8.3.0 either as the first commit or eventually.
  • The change record draft is intended to document how contrib modules should update from Media Entity in contrib to Media in core. It needs to be reviewed, completed, and confirmed by someone knowledgeable about Media in contrib.
  • File followup issues still missing in the Media entity issue reviews spreadsheet ("1" in column H but not in column I; note that there are 4 tabs).
  • Add test coverage for #361 and #363 (see #364).
  • Issue summary update for #2836153: Improve metadata mapping UI on Media type form. Notes from the DDD meeting notes:

    There are two mapping things for followups:
    The UI. kind of confusing right now. Roy proposed a few solutions and there is a solution for it. https://www.drupal.org/node/2836153 Needs IS update
    https://www.drupal.org/node/2862467

  • Address issue (possibly as a followup) of boilerplate code for icons and media_entity_copy_icons() (see #366 and #367).
  • File followup about icon file types?

Data model changes

Adds new fieldable and revisionable entity type.

Follow ups

Moved to the summary of #2825215: Media initiative: Essentials - first round of improvements in core.

CommentFileSizeAuthor
#391 interdiff-2831274-389-381.txt27.83 KBphenaproxima
#391 2831274-391.patch234.57 KBphenaproxima
#389 Screen Shot 2017-04-24 at 3.52.44 PM.png45.98 KBnaveenvalecha
#389 interdiff-2831274-388-389.txt2.62 KBnaveenvalecha
#389 2831274-389.patch255.64 KBnaveenvalecha
#388 interdiff-2831274-386-388.txt367 bytesphenaproxima
#388 2831274-388.patch255.64 KBphenaproxima
#386 interdiff-384-386.txt1.01 KBseanB
#386 2831274-386.patch255.64 KBseanB
#384 interdiff-383-384.txt9.76 KBseanB
#384 2831274-384.patch255.67 KBseanB
#383 interdiff-2831274-379-383.txt3.49 KBchr.fritsch
#383 2831274-383.patch254.61 KBchr.fritsch
#379 interdiff-2831274-376-379.txt1.54 KBnaveenvalecha
#379 2831274-379.patch253.87 KBnaveenvalecha
#376 interdiff-368-376.txt10.5 KBseanB
#376 2831274-376.patch252.42 KBseanB
#368 2831274-363-368.txt1.41 KBBoobaa
#368 2831274-368-do-not-test.patch247.43 KBBoobaa
#363 contributors.txt621 bytesslashrsm
#363 interdiff_354_363.txt983 bytesslashrsm
#363 2831274_363.patch247.42 KBslashrsm
#354 2831274_353_TEST_ONLY.patch247.38 KBslashrsm
#354 2831274_353.patch247.35 KBslashrsm
#354 interdiff_352_353.txt1.75 KBslashrsm
#352 interdiff_315_352.txt2.64 KBslashrsm
#352 2831274_352_TEST_ONLY.patch246.61 KBslashrsm
#352 2831274_352.patch246.58 KBslashrsm
#351 2831274-351.patch245.06 KBWim Leers
#351 interdiff.txt3.65 KBWim Leers
#349 interdiff-345-349.txt1.3 KBiMiksu
#349 2831274-349.patch244.71 KBiMiksu
#345 2831274-345.patch244.7 KBwebflo
#343 interdiff.txt625 bytesamateescu
#343 2831274-343.patch244.7 KBamateescu
#336 interdiff-332-336.txt4.51 KBseanB
#336 2831274-336.patch244.51 KBseanB
#332 interdiff-318-332.txt103.69 KBseanB
#332 interdiff-325-332.txt40.75 KBseanB
#332 interdiff-330-332.txt7.39 KBseanB
#332 2831274-332.patch244.69 KBseanB
#330 interdiff-325-330.txt40.06 KBseanB
#330 interdiff-318-330.txt103.4 KBseanB
#330 2831274-330.patch244.29 KBseanB
#325 2831274-325.patch244.79 KBmarcoscano
#325 interdiff_318_325.txt76.41 KBmarcoscano
interdiff_318_323.txt75.37 KBmarcoscano
2831274-323.patch244.79 KBmarcoscano
2831274-320.patch245.28 KBmarcoscano
interdiff_318_320.txt71.08 KBmarcoscano
#318 interdiff_312_318.txt3.67 KBslashrsm
#318 2831274_318.patch245.17 KBslashrsm
#312 interdiff_308_312.txt14.08 KBslashrsm
#312 2831274_312.patch244.57 KBslashrsm
#308 interdiff_307_308.txt17.73 KBslashrsm
#308 2831274_308.patch242.6 KBslashrsm
#307 interdiff_305_307.txt25.31 KBslashrsm
#307 2831274_307.patch228.11 KBslashrsm
#305 interdiff_302_304.txt987 bytesslashrsm
#305 2831274_304.patch215.37 KBslashrsm
#302 interdiff-299-302.txt7.44 KBseanB
#302 2831274-302.patch214.89 KBseanB
#299 interdiff-295-299.txt425 bytesseanB
#299 2831274-299.patch215.11 KBseanB
#295 interdiff-288-295.txt6.47 KBseanB
#295 2831274-295.patch215.08 KBseanB
#288 interdiff.txt1.18 KBGábor Hojtsy
#288 2831274-288.patch213.81 KBGábor Hojtsy
#286 interdiff-285-286.txt13.85 KBseanB
#286 2831274_286.patch213.1 KBseanB
#285 interdiff-283-285.txt14.96 KBseanB
#285 2831274_285.patch216.27 KBseanB
#283 2831274-283.patch215.14 KBwebflo
#283 2831274-283.interdiff.txt2.03 KBwebflo
#281 2831274-281.patch215.24 KBwebflo
#281 2831274-281.interdiff.txt618 byteswebflo
#277 interdiff-229-277.txt84.37 KBseanB
#277 2831274_277.patch215.29 KBseanB
#275 interdiff-2831274-259-274.txt22.52 KBphenaproxima
#269 Media entity architecture high level.png119.31 KBGábor Hojtsy
#262 attempt_to_make_this_use_handlers_and_weep-interdiff.txt9.56 KBWim Leers
#259 media_entity-2831274-259.patch224.94 KBWim Leers
#259 interdiff.txt857 bytesWim Leers
#256 media_entity-2831274-256.patch224.94 KBWim Leers
#256 interdiff.txt6.49 KBWim Leers
#254 media_entity-2831274-254.patch223.87 KBWim Leers
#254 interdiff.txt10.68 KBWim Leers
#253 media_entity-2831274-253.patch223.29 KBWim Leers
#253 interdiff.txt3.32 KBWim Leers
#252 media_entity-2831274-252.patch222.95 KBWim Leers
#252 interdiff.txt5.27 KBWim Leers
#248 media_entity-2831274-248.patch221.89 KBWim Leers
#248 interdiff.txt11.09 KBWim Leers
#246 media_entity-2831274-246.patch220.14 KBWim Leers
#246 interdiff.txt15.96 KBWim Leers
#245 interdiff.txt869 bytesWim Leers
#245 media_entity-2831274-245.patch219.64 KBWim Leers
#243 interdiff.txt7.02 KBWim Leers
#243 media_entity-2831274-243.patch219.43 KBWim Leers
#232 Screen Shot 2017-01-18 at 10.35.10.png29.8 KBWim Leers
#232 Screen Shot 2017-01-18 at 10.35.04.png24.12 KBWim Leers
#232 Screen Shot 2017-01-18 at 10.34.53.png15.95 KBWim Leers
#229 interdiff_226_228.txt24.29 KBslashrsm
#229 2831274_228.patch221.64 KBslashrsm
#226 interdiff.txt5.28 KBslashrsm
#226 2831274_226.patch197.68 KBslashrsm
#224 interdiff_205_224.txt2.33 KBslashrsm
#224 2831274_224.patch195.3 KBslashrsm
#205 interdiff-2831274-204-205.txt322 bytesnaveenvalecha
#205 2831274-205.patch194.01 KBnaveenvalecha
#204 interdiff-2831274-199-204.txt5.03 KBnaveenvalecha
#204 2831274-204.patch194 KBnaveenvalecha
#200 media-view.png45.81 KBeffulgentsia
#200 media-select.png35.05 KBeffulgentsia
#200 media-add-link.png25.81 KBeffulgentsia
#200 media-listing.png29.77 KBeffulgentsia
#200 media-er-field.png59.58 KBeffulgentsia
#200 media-add-field.png57.51 KBeffulgentsia
#200 media-structure.png37.52 KBeffulgentsia
#200 media-type.png58.16 KBeffulgentsia
#199 interdiff-198-199.txt8.92 KBseanB
#199 2831274_199.patch192.66 KBseanB
#198 interdiff_194_198.txt73.71 KBseanB
#198 2831274-198.patch189.1 KBseanB
#194 interdiff_192_194.txt19.66 KBseanB
#194 2831274-194.patch188.3 KBseanB
#192 interdiff_189_192.txt53.02 KBseanB
#192 2831274-192.patch187.21 KBseanB
#189 interdiff_187_189.txt783 bytesseanB
#189 2831274_189.patch188.19 KBseanB
#187 interdiff_179_186.txt4.82 KBseanB
#187 2831274_186.patch188.37 KBseanB
#180 interdiff_179_180.txt4.56 KBseanB
#180 2831274_180.patch188.21 KBseanB
#179 interdiff_176_179.txt2.61 KBseanB
#179 2831274_179.patch187.01 KBseanB
#176 2831274_176-test-for-174.patch2.82 KBseanB
#176 interdiff_170_176.txt3.68 KBseanB
#176 2831274_176.patch185.58 KBseanB
#170 interdiff_162_170.txt5.88 KBslashrsm
#170 2831274_170.patch183.95 KBslashrsm
#162 interdiff_161_162.txt11.89 KBslashrsm
#162 2831274_162.patch184.41 KBslashrsm
#161 interdiff-2831274-159-161.txt2.53 KBchr.fritsch
#161 2831274_161.patch181.48 KBchr.fritsch
#159 2831274_159.patch180.07 KBchr.fritsch
#159 interdiff-2831274-151-159.txt14.06 KBchr.fritsch
#151 internet_148_151.txt3.06 KBslashrsm
#151 2831274_151.patch179.26 KBslashrsm
#148 interdiff_147_148.txt1.15 KBslashrsm
#148 2831274_148.patch178.79 KBslashrsm
#147 interdiff_144_147.txt3.52 KBslashrsm
#147 2831274_147.patch178.7 KBslashrsm
#144 interdiff_136_144.txt37.12 KBslashrsm
#144 2831274_144.patch178.96 KBslashrsm
#136 interdiff-2831274-128-136.txt9.9 KBphenaproxima
#136 2831274-136.patch179.36 KBphenaproxima
#128 interdiff-2831274-112-128.txt43.95 KBphenaproxima
#128 2831274-128.patch179.24 KBphenaproxima
#120 2831274_120.patch179.09 KBmtodor
#120 2831274_120_interdiff-120-112.txt3.68 KBmtodor
#112 interdiff_109_112.txt8.42 KBslashrsm
#112 2831274_112.patch179.08 KBslashrsm
#109 interdiff_107_109.txt17.37 KBslashrsm
#109 2831274_109.patch177.86 KBslashrsm
#107 interdiff-99-107.txt3.65 KBGábor Hojtsy
#107 2831274-107.patch177.7 KBGábor Hojtsy
#102 Add media | media_entity 8.x-1.6 2016-12-14 14-10-59.png267.07 KBGábor Hojtsy
#101 Add media | media_entity 8.x-1.6 2016-12-14 14-10-59.png267.07 KBGábor Hojtsy
#99 interdiff-2831274-94-99.txt118.32 KBphenaproxima
#99 2831274-99.patch176.23 KBphenaproxima
#94 interdiff_92_94.txt9.52 KBslashrsm
#94 2831274_94.patch181.53 KBslashrsm
#92 interdiff_89_92.txt25.1 KBslashrsm
#92 2831274_92.patch184.32 KBslashrsm
#89 interdiff_85_89.txt4.24 KBslashrsm
#89 2831274_89.patch169.52 KBslashrsm
#85 interdiff_75_85.txt41.92 KBslashrsm
#85 2831274_85.patch168.57 KBslashrsm
#75 interdiff_72_75.patch675 bytesslashrsm
#75 2831274_75.patch168.88 KBslashrsm
#72 interdiff_70_72.txt630 bytesslashrsm
#72 2831274_72.patch168.87 KBslashrsm
#70 interdiff_61_70.txt18.27 KBslashrsm
#70 2831274_70.patch168.9 KBslashrsm
#61 interdiff_59_61.txt654 bytesslashrsm
#61 2831274_61.patch169.49 KBslashrsm
#59 2831274_59.patch169.26 KBslashrsm
#59 interdiff_58.txt14.17 KBslashrsm
#58 interdiff-2831274-55-58.txt2.66 KBchr.fritsch
#58 2831274-58.patch170.54 KBchr.fritsch
#55 interdiff.txt13.19 KBmarcoscano
#55 2831274-55.patch170.63 KBmarcoscano
#52 interdiff.txt6.74 KBamateescu
#52 2831274-52.patch170.36 KBamateescu
#47 interdiff-2831274-36-44.txt10.21 KBchr.fritsch
#47 2831274_44.patch171.36 KBchr.fritsch
#37 2831274_36.patch171.35 KBchr.fritsch
#37 interdiff-2831274-32-36.txt3.72 KBchr.fritsch
#34 2831274_34.patch170 KBchr.fritsch
#34 interdiff-2831274-32-34.txt5.76 KBchr.fritsch
#33 interdiff_25_32.txt1.57 KBslashrsm
#33 283127_32.patch171.23 KBslashrsm
#25 interdiff-2831274-21-25.txt33.56 KBchr.fritsch
#25 2831274_25.patch171.16 KBchr.fritsch
#21 interdiff_19_21.patch78.55 KBslashrsm
#21 2831274_21.patch196.65 KBslashrsm
#19 2831274_19.patch196 KBslashrsm
#19 interdiff_14_19.txt3.08 KBslashrsm
#14 interdiff_2_14.txt34.69 KBslashrsm
#14 2831274_14.patch193.89 KBslashrsm
#2 2831274_2.patch199.89 KBslashrsm
Members fund testing for the Drupal project. Drupal Association Learn more

Comments

slashrsm created an issue. See original summary.

slashrsm’s picture

Issue summary: View changes
Status: Active » Needs review
FileSize
199.89 KB

Attached patch adds Media entity 8.x-1.6 with the changes described in the issue summary. It currently re-implements own revision form logic.

Berdir’s picture

  1. +++ b/core/modules/media_entity/config/schema/media_entity.schema.yml
    @@ -0,0 +1,69 @@
    +    third_party_settings:
    +      type: sequence
    +      label: 'Third party settings'
    +      sequence:
    +        type: media_entity.bundle.third_party.[%key]
    

    the config entity type defines third party settings, id and label, so you don't need those.

  2. +++ b/core/modules/media_entity/media_entity.install
    @@ -0,0 +1,103 @@
    + */
    +function _media_entity_check_entity_version() {
    +  if (\Drupal::moduleHandler()->moduleExists('entity')) {
    +    $info = system_get_info('module', 'entity');
    +    if (version_compare($info['version'], '8.x-1.0-alpha3') >= 0) {
    +      return TRUE;
    +    }
    +  }
    +
    +  return FALSE;
    +}
    +
    

    this can go :)

  3. +++ b/core/modules/media_entity/media_entity.install
    @@ -0,0 +1,103 @@
    + */
    +function media_entity_requirements($phase) {
    +  $requirements = [];
    +  if ($phase == 'update' && !_media_entity_check_entity_version()) {
    +    $requirements['entity'] = [
    +      'title' => t('Media entity'),
    +      'value' => t('Entity API missing'),
    

    this too then

  4. +++ b/core/modules/media_entity/media_entity.install
    @@ -0,0 +1,103 @@
    + * Remove "type" base field.
    + */
    +function media_entity_update_8001() {
    +  $fields = \Drupal::database()->query('DESCRIBE {media_field_data}')->fetchCol();
    +  if (in_array('type', $fields)) {
    +    \Drupal::database()->update('media_field_data')
    +      ->fields(['type' => NULL])
    +      ->execute();
    +  }
    +
    +  $manager = \Drupal::entityDefinitionUpdateManager();
    +  if ($field = $manager->getFieldStorageDefinition('type', 'media')) {
    +    $manager->uninstallFieldStorageDefinition($field);
    +  }
    

    not sure about update functions, but I think we shouldn't take them over? Especially since this is actually mysql specific and something that would be allowed in core.

    We could include a hook_update_last_removed() maybe to ensure that people have updated to the latest version before updating to 8.3.x?

  5. +++ b/core/modules/media_entity/media_entity.links.action.yml
    @@ -0,0 +1,12 @@
    +  title: 'Add media bundle'
    

    I fear the use of bundle in the UI is going to be a problem for core, which tries to avoid that *very hard*.

  6. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,84 @@
    +
    +/**
    + * Implements hook_help().
    + */
    +function media_entity_help($route_name, RouteMatchInterface $route_match) {
    

    we'll probably need to update the help?

  7. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,84 @@
    + * @throws Exception
    + */
    

    @throws needs a description.

  8. +++ b/core/modules/media_entity/media_entity.permissions.yml
    @@ -0,0 +1,21 @@
    +view media:
    +  title: 'View media'
    +update media:
    +  title: 'Update media'
    +update any media:
    +  title: 'Update any media'
    +delete media:
    +  title:  'Delete media'
    +delete any media:
    +  title:  'Delete any media'
    +create media:
    +  title: 'Create media'
    

    The names are a bit strange, only got the non-all permissions after seeing the code below. at least the labels should probably be changed to * own media?

  9. +++ b/core/modules/media_entity/media_entity.theme.inc
    @@ -0,0 +1,65 @@
    + * Prepares variables for list of available media bundles.
    + *
    + * Default template: media-add-list.html.twig.
    + *
    + * @param array $variables
    + *   An associative array containing:
    + *   - content: An array of content types.
    + */
    +function template_preprocess_media_add_list(&$variables) {
    +  $variables['bundles'] = [];
    +  if (!empty($variables['content'])) {
    +    foreach ($variables['content'] as $bundle) {
    

    I think we have that in core now.. entity-list-add. highly unlikely that someone customized this, so might be ok to drop this.. especially since it is backend and backend UI is excluded from BC afaik.

  10. +++ b/core/modules/media_entity/media_entity.tokens.inc
    @@ -0,0 +1,193 @@
    +  $sanitize = !empty($options['sanitize']);
    

    sanitize doesn't exist anymore, token should always return the unsanitzed version.

  11. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    + * )
    + */
    +class Media extends ContentEntityBase implements MediaInterface {
    +
    +  use EntityChangedTrait;
    +
    

    https://www.drupal.org/node/2789315 is close and we should be able to switch that when it lands.

  12. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getChangedTime() {
    +    return $this->get('changed')->value;
    +  }
    +
    

    this is provided by EntityChangedTrait

  13. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +   * {@inheritdoc}
    +   */
    +  public function getPublisherId() {
    +    return $this->get('uid')->target_id;
    +  }
    

    Using published means this isn't compatible with EntityOwnerInterface, maybe deprecate these methods or at least add the others too?

  14. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +    // revision author.
    +    if (!$this->get('revision_uid')->entity) {
    +      $this->set('revision_uid', $this->getPublisherId());
    +    }
    

    we should implement RevisionLogInterface if we don't already and uset setRevisionLogUid() like Node does.

  15. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +    foreach ($this->bundle->entity->field_map as $source_field => $destination_field) {
    

    do we have a getter for field_map?

  16. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +      // Only save value in entity field if empty. Do not overwrite existing
    +      // data.
    +      // @TODO We might modify that in the future but let's leave it like this
    +      // for now.
    +      if ($this->hasField($destination_field) && $this->{$destination_field}->isEmpty() && ($value = $this->getType()->getField($this, $source_field))) {
    +        $this->set($destination_field, $value);
    +      }
    

    clearly that did't happen so it will also not happen after a stable release, I'd remove the @todo, otherwise you'll have to create an issue for it :)

  17. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +      /** @var \Drupal\file\FileInterface $file */
    +      $file = $this->entityTypeManager()->getStorage('file')->create(['uri' => $thumbnail_uri]);
    +      if ($publisher = $this->getPublisher()) {
    +        $file->setOwner($publisher);
    +      }
    +      $file->setPermanent();
    +      $file->save();
    +      $this->thumbnail->target_id = $file->id();
    

    should we register a usage for the thumbnail?

  18. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +    // TODO - We should probably use something smarter (tokens, ...).
    +    $this->thumbnail->alt = t('Thumbnail');
    +    $this->thumbnail->title = $this->label();
    

    Todo needs proper formatting and ilikely an issue.. or get removed :)

  19. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    +    $fields['mid'] = BaseFieldDefinition::create('integer')
    +      ->setLabel(t('Media ID'))
    +      ->setDescription(t('The media ID.'))
    +      ->setReadOnly(TRUE)
    +      ->setSetting('unsigned', TRUE);
    

    we should call the parent and let it generate the standard fields.

  20. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +    $fields['vid'] = BaseFieldDefinition::create('integer')
    +      ->setLabel(t('Revision ID'))
    +      ->setDescription(t('The media revision ID.'))
    +      ->setReadOnly(TRUE)
    +      ->setSetting('unsigned', TRUE);
    +
    

    awww.. using vid is a sad copy & paste from node that we can't change now :)

  21. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +    $fields['revision_timestamp'] = BaseFieldDefinition::create('created')
    +      ->setLabel(t('Revision timestamp'))
    +      ->setDescription(t('The time that the current revision was created.'))
    +      ->setQueryable(FALSE)
    +      ->setRevisionable(TRUE);
    +
    +    $fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
    +      ->setLabel(t('Revision publisher ID'))
    

    as mentioned earlier, we should be able to replace this with the new trait+interface.

  22. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,486 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getRevisionCreationTime() {
    +    return $this->revision_timestamp->value;
    +  }
    

    ah, you actually have th methods, just don't use the trait yet..

  23. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,244 @@
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function id() {
    +    return $this->id;
    +  }
    

    this is the same as the parent implementation.

  24. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,244 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function isNewRevision() {
    +    return $this->new_revision;
    +  }
    +
    +  /**
    

    I think the interface in the other issue documents this as shouldCreateNewRevision() or so.

  25. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,246 @@
    +
    +    $form['#attached']['library'][] = 'node/form';
    

    hidden node.module dependency?

  26. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,246 @@
    +   */
    +  protected function actions(array $form, FormStateInterface $form_state) {
    +    $element = parent::actions($form, $form_state);
    +    $media = $this->entity;
    +
    +    // Add a "Publish" button.
    +    $element['publish'] = $element['submit'];
    +    // If the "Publish" button is clicked, we want to update the status to
    +    // "published".
    +    $element['publish']['#published_status'] = TRUE;
    +    $element['publish']['#dropbutton'] = 'save';
    +    if ($media->isNew()) {
    +      $element['publish']['#value'] = $this->t('Save and publish');
    +    }
    +    else {
    +      $element['publish']['#value'] = $media->isPublished() ? $this->t('Save and keep published') : $this->t('Save and publish');
    +    }
    +    $element['publish']['#weight'] = 0;
    

    Yet another thing I'd rather not see copied from node module :)

    See #2068063: Change "Save and keep un-/published" buttons to a "Published" checkbox and an included "Save" button and #1901216: Modules cannot reliably enhance or alter the node form anymore

  27. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,246 @@
    +    if ($this->entity->id()) {
    +      $form_state->setValue('id', $this->entity->id());
    +      $form_state->set('id', $this->entity->id());
    +      $form_state->setRedirectUrl($this->entity->toUrl('canonical'));
    +    }
    +    else {
    +      // In the unlikely case something went wrong on save, the entity will be
    +      // rebuilt and entity form redisplayed.
    +      drupal_set_message($this->t('The entity could not be saved.'), 'error');
    +      $form_state->setRebuild();
    +    }
    

    another node.module thing. if saving fails, we throw an exception. this else case will *never* run. if you would want to handle that it would need to be in a try/catch. Or drop it.

  28. +++ b/core/modules/media_entity/src/MediaInterface.php
    @@ -0,0 +1,100 @@
    +  /**
    +   * Automatically determines the most appropriate thumbnail and sets
    +   * "thumbnail" field.
    +   */
    

    docblock > 80 chars.

  29. +++ b/core/modules/media_entity/src/MediaStorage.php
    @@ -0,0 +1,12 @@
    +/**
    + * Media storage class.
    + */
    +class MediaStorage extends SqlContentEntityStorage implements MediaStorageInterface {
    +
    +}
    

    what's the custom class for if it doesn't do anything?

  30. +++ b/core/modules/media_entity/src/MediaStorageInterface.php
    @@ -0,0 +1,12 @@
    +/**
    + * Provides an interface defining a media storage controller.
    + */
    +interface MediaStorageInterface extends EntityStorageInterface {
    +
    

    .. and the interface?

  31. +++ b/core/modules/media_entity/src/MediaTypeBase.php
    @@ -0,0 +1,161 @@
    +abstract class MediaTypeBase extends PluginBase implements MediaTypeInterface, ContainerFactoryPluginInterface {
    +  use StringTranslationTrait;
    

    why extend from PluginBase in Component and not Core which includes StringTranslationTrait and DependencySerializationTrait?

  32. +++ b/core/modules/media_entity/src/MediaTypeBase.php
    @@ -0,0 +1,161 @@
    +      $container->get('config.factory')->get('media_entity.settings')
    

    this means that any new createInstance() call needs to load that config, but we probably only need it on very few cases' It is in general better to inject factories and only load things that you need when you actually do and avoid logic/code during create()/construct()

  33. +++ b/core/modules/media_entity/src/MediaViewsData.php
    @@ -0,0 +1,31 @@
    +    $data['media_field_data']['table']['wizard_id'] = 'media';
    +    $data['media_field_revision']['table']['wizard_id'] = 'media_revision';
    +    $data['media']['media_bulk_form'] = [
    +      'title' => $this->t('Media operations bulk form'),
    +      'help' => $this->t('Add a form element that lets you run operations on multiple media entities.'),
    +      'field' => [
    +        'id' => 'media_bulk_form',
    +      ],
    

    isn't the bulk form generic now, what's this for exactly, just copied from NOde?

  34. +++ b/core/modules/media_entity/src/Plugin/Action/SaveMedia.php
    @@ -0,0 +1,37 @@
    +  public function execute($entity = NULL) {
    +    // We need to change at least one value, otherwise the changed timestamp
    +    // will not be updated.
    +    $entity->changed = 0;
    +    $entity->save();
    

    what's the point of this operation exactly? Node has it too and obviously copied from there but for what exactly? :)

  35. +++ b/core/modules/media_entity/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,187 @@
    +
    +    // Collect cache tags related to the image style setting.
    +    $image_style = $this->imageStyleStorage->load($image_style_setting);
    +    $this->renderer->addCacheableDependency($elements, $image_style);
    +
    

    wouldn't this fail if you select "original" ?

  36. +++ b/core/modules/media_entity/src/Plugin/QueueWorker/thumbnailDownloader.php
    @@ -0,0 +1,31 @@
    +      // Indicate that the entity is being processed from a queue and that
    +      // thumbnail images should be downloaded.
    +      $entity->setQueuedThumbnailDownload();
    +      $entity->save();
    

    not sure why we do this like this and don't just call a method that does the logic and then save?

  37. +++ b/core/modules/media_entity/src/Plugin/views/field/MediaBulkForm.php
    @@ -0,0 +1,21 @@
    +class MediaBulkForm extends BulkForm {
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function emptySelectedMessage() {
    +    return $this->t('No media selected.');
    +  }
    +
    +}
    

    not really that useful to override but OK :)

  38. +++ b/core/modules/media_entity/src/Plugin/views/wizard/Media.php
    @@ -0,0 +1,84 @@
    +
    +    // Add permission-based access control.
    +    $display_options['access']['type'] = 'perm';
    +    $display_options['access']['options']['perm'] = 'access content';
    

    that seems copied from node and not really adjusted?

slashrsm’s picture

Status: Needs work » Needs review
FileSize
193.89 KB
34.69 KB

Thank you!

Done: 2, 3, 4, 7, 8, 9, 10, 12, 13, 14, 15, 16, 19, 23, 24, 25, 27, 28, 29, 30, 31, 35, 36, 38

1: Removed third_party_settings. Was looking for id and label on config_object but couldn't find them. Are you sure they are provided there?

5: I understand the concern. We can't use "type" as we use this for something else already. The idea when we were originally designing this was that the type better relates to the type of asset (local image, YT video, Tweet, ...), while bundle is a bunch of fields and some configuration attached to a given type. "Sub-type" was another suggestion back then. Would love to hear more ideas. I am also concerned how much confusion would changing this in UI and not in the backend cause.

6: Didn't touch this. Still needs work.

17: Image field registers it already.

18: I would really like to remove the @TODO, but I really think that "Thumbnail" for alt isn't the best solution. I am not sure which solution is best here. Probably, as comment already directs, some token-based approach which is configurable somewhere and falls back to "Thumbnail" if empty. Ideas?

20: Yes :(

14., 21., 22.: We do implement the RevisionLogInterface since we have different field names. We already tried to convert to RevisionableContentEntityBase in #2712135: Update media entity to 8.1.x functionality but miserably failed. One of the blockers seems to be #2248983: Define the revision metadata base fields in the entity annotation in order for the storage to create them only in the revision table.

26: That issue if far from straightforward. Seems that there is no consensus there yet. Should we go with the checkbox or wait to see what is decided there and be consistent?

32. Done, but literary every plugin implementation in contrib uses this. We will need to update all of them. Could we add magic getter to keep existing plugins working until they catch up?

33 and 37. Yes :) Node and User do this exactly like that. I removed the override and now we're depending on generic implementation.

34. Yes. Another node copy-paste. Someone could technically use it and removing it would break that. But that is also very unlikely. Not sure.

Gábor Hojtsy’s picture

Reviewed and discussed the UI of this module on the the Drupal Usability meeting today, see https://twitter.com/drupalux/status/803710630729486336 for recording.

Berdir’s picture

1. You're right, id/label isn't there. Always get confused about which things are defined there by default vs. having some default logic in config entity. Would be too easy if that were in sync :)

5. Yeah, I don't have a suggestion either. I think sub-type isn't better. We'll see what others have to say. And yes, I think content (type) vs node (type) is at least as confusing as using "bundle".

14 and so on: Ah yes, that issue. Hopefully it will be resolved soon ;)

26. Not sure. Personally, I'd say yes, we actually use that patch in our projects as HEAD is a huge pain for allowing non-admins to control status. But that's definitely not my decision to make, so lets wait for more feedback.

32. Fine with keeping it for BC, we could also inject both the factory and the config and deprecate the config? But that would also be a constructor change which will break all plugins that have their own injection. that pattern is so annoying for base classes :)

34. Nope, not unlikely at all. Every existing installation has the config, so the least we'd have to do is write an update function. And assuming that views actually defines config dependencies on actions (not sure about that), we might end up deleting views too, which wouldn't be cool. So, lets keep it.

Just one more nitpick from the interdiff:

+++ b/core/modules/media_entity/src/Entity/Media.php
@@ -479,7 +452,7 @@ public function getRevisionLogMessage() {
    */
   public function setRevisionLogMessage($revision_log_message) {
-    $this->revision_log->value = $revision_log_message;
+    $this->revision_log-> value = $revision_log_message;
     return $this;
slashrsm’s picture

Watched recording that @Gábor Hojtsy linked in #15. My main comments.

Thumbnail

Thumbnail is set first time and then not touched again. There are requests to change that, but in case of remote media it might introduce a huge performance regression if we would fetch thumbnail every time an entity is saved.

One idea was to add a checkbox that would force thumbnail to be re-calculated on save. Having input about from UX people about this would definitely help achieve a decision.

Source field

The fact that you need to create bundle, create field, return to bundle and set source field is obviously annoying. We are working on a patch that would auto-generate necessary field: #2625854: Provide default source_field when creating new media entity bundles. Would that improve the situation?

Default media listing

We should completely remove that and replace it with the media library that is planned for core in my opinion.

Missing media type plugins

It is definitely plan to ship with more type plugins (as proposed in the planned issue: #2801277: [META] Support remote media assets as first-class citizens by adding Media Entity module in core). I would think at least image, document and YouTube. The reason why they are not included in this patch is to not make it even bigger.

Media bundle form

It would be great if UX team would propose how to improve issues there (things not being refreshed, etc.). This is first time anyone checked this form from the UX perspective, which is extremely valuable.

slashrsm’s picture

Status: Needs work » Needs review
FileSize
3.08 KB
196 KB

Fixed nit and two other nits that should pass more tests.

slashrsm’s picture

Status: Needs work » Needs review
FileSize
196.65 KB
78.55 KB

#2796901: Convert all web tests to BTB where possible, increase test coverage and split existing tests into more granular classes. landed and I think that it makes more sense to include that now than wait. Huge interdiff, but main patch stays more or less the same. And all web tests are gone so we don't have to deal with them later.

chr.fritsch’s picture

Thumbnail

We definitely need to update the thumbnail. A checkbox to force this is one option. What about checking of the source_field? If the value changed there, than fetching a new thumbnail.

Or we add an action somewhere to re-fetch the thumbnail.

Source field

IMHO, integration of #2625854: Provide default source_field when creating new media entity bundles would improve the situation.

Default media listing

I agree and will provide a patch later this day for that.

Missing media type plugins

After media entity is into core, we should open follow-ups to get media_entity_image into core, too. For video support we should think about if we can grab something from video_embed_field module. It ships integration with lots of remote video providers.

chr.fritsch’s picture

Status: Needs work » Needs review
FileSize
171.16 KB
33.56 KB

Here is a new patch

  1. I removed the media listing
  2. Addedthe media template for stable and classy
  3. Fixed the hook_help test

Renaming media_entity into media

One think i would like to discuss is, should we rename media_entity into media. I would say, yes, to follow core pattern. The problem is we already have in contrib a module called media. But the D8 usage of that is really low https://www.drupal.org/project/usage/media

phenaproxima’s picture

One think i would like to discuss is, should we rename media_entity into media. I would say, yes, to follow core pattern.

I've always thought that the term "Media" was a bit too generic and overloaded. If I had my druthers, I would prefer that we rename Media Entity to one of Multimedia, Multimedia API, or Media API.

pixelmord’s picture

Having watched the review of the current state in the UX meeting and having used the module(s) in contrib I have to say that the mentioned requirement that media providers shall take care of creating the needed field(s) on the bundle makes sense #2625854: Provide default source_field when creating new media entity bundles.

However I think then still the process/user journey is backwards, because you kind of create a generic bundle and then select a (possibly generic) provider and then the provider might bring configuration options into the form (after reloading the form). From a UX perspective a user wants to create a media of type XXXXX right away, e.g. a youtube or image media and does not care that this concept is divided in a provider and a bundle by the API.

If we had the option to use a multi-step form I would suggest beginning with the selection of the provider. Since we do not have that in core, I would suggest starting the form with a label (for the bundle) and the selection of the provider and not set the generic provider by default. After the selection of the desired provider, even if it is the generic one the form can show the fields specific to that provider and then eventually the form can be submitted.
That way the user performs the necessary actions in an order that feels a bit more natural. At the moment the provider selection is not visually promoted to be important because it is somewhere down the list of fields and fieldsets...

Gábor Hojtsy’s picture

@phenaproxima: the problem with calling it "X API" is it is not an API module, it manages the UI too. Like Views has a UI module and an API module and there is no UI if you don't enable the UI module, but in this case, the module both provides a base API and a UI with place for plugins. That is why also the current contrib module is confusingly named in the human readable name as "Media Entity API".

phenaproxima’s picture

@phenaproxima: the problem with calling it "X API" is it is not an API module, it manages the UI too.

Why don't we split them apart, then? I can definitely see use cases where you might want to support media entities, but provide your own UI from top to bottom.

slashrsm’s picture

Media bundle form (re #28)

Yes! I think that multi-step form or at least multiple screens could solve a lot of UX problems there if done right. Field mapping is definitely a good candidate for a separate screen in my opinion. But I also don't want to be too smart here and let UX experts have their say.

The question is how to approach this. Do the total overhaul as part of this patch? Try to fix lowest hanging fruit here and then do bigger changes in a follow-up? I would slightly prefer second option as it would allow us to keep moving faster.

Name of the module

I understand the concerns and I am aware that there are better names than media_entity. I would only suggest to consider amount of effort needed for a serious renaming of the module and potential update paths for existing sites that would be required. Compare this to the benefits that we get from that and make sure we get best ROI for the resources that we're investing into this.

dawehner’s picture

This is not a proper review, I was just bored and read a bit of the patch.

  1. +++ b/core/modules/media_entity/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    +        return AccessResult::allowedIf($account->hasPermission('view media') && $entity->status->value);
    

    This one misses a cachePerPermissions

  2. +++ b/core/modules/media_entity/src/MediaInterface.php
    @@ -0,0 +1,107 @@
    +  /**
    +   * Returns the media publisher user entity.
    +   *
    +   * @return \Drupal\user\UserInterface
    +   *   The author user entity.
    +   *
    +   * @deprecated in Drupal 8.3.0, will be removed before Drupal 9.0.0. Use
    +   *   \Drupal\user\EntityOwnerInterface::getOwner() instead.
    +   */
    +  public function getPublisher();
    ...
    +  /**
    +   * Returns the media publisher user ID.
    +   *
    +   * @return int
    +   *   The author user ID.
    +   *
    +   * @deprecated in Drupal 8.3.0, will be removed before Drupal 9.0.0. Use
    +   *   \Drupal\user\EntityOwnerInterface::getOwnerId() instead.
    +   */
    +  public function getPublisherId();
    ...
    +  /**
    +   * Sets the media publisher user ID.
    +   *
    +   * @param int $uid
    +   *   The author user id.
    +   *
    +   * @return \Drupal\media_entity\MediaInterface
    +   *   The called media entity.
    +   *
    +   * @deprecated in Drupal 8.3.0, will be removed before Drupal 9.0.0. Use
    +   *   \Drupal\user\EntityOwnerInterface::setOwnerId() instead.
    +   */
    +  public function setPublisherId($uid);
    

    It is weird that we add deprecated method right from the start

  3. +++ b/core/modules/media_entity/tests/src/FunctionalJavascript/MediaViewsWizardTest.php
    @@ -0,0 +1,89 @@
    +    $session->wait(2000);
    ...
    +    $session->wait(2000);
    

    This test should use assertJsCondition instead of just waiting, see #2830485: \Drupal\Tests\outside_in\FunctionalJavascript\OutsideInBlockFormTest fails randomly

slashrsm’s picture

Status: Needs work » Needs review
FileSize
171.23 KB
1.57 KB

This should fix the last failing test.

EDIT: Cross-post. Items from #32 are not addressed in this patch.

chr.fritsch’s picture

I fixed everything @dawehner addressed in #32

slashrsm’s picture

#32-2 is true, but we have to be aware that this entity type exists and there are thousands of sites using it already. Those sites might have custom code relying on this functions. I don't think that we should remove them. This was done based on #11-13.

dawehner’s picture

@slashrsm
Fair point, and yes, I actually agree.

chr.fritsch’s picture

Ok, the deprecated functions are back.

Made the interdiff from comment #33

Maybe if there comes a decision to rename the module, then we can think about removing those functions again.

chr.fritsch’s picture

+++ b/core/modules/media_entity/src/Entity/MediaBundle.php
@@ -0,0 +1,244 @@
+  /**
+   * The machine name of this media bundle.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The human-readable name of the media bundle.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * A brief description of this media bundle.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * The type plugin id.
+   *
+   * @var string
+   */
+  public $type = 'generic';
+
+  /**
+   * Are thumbnail downloads queued.
+   *
+   * @var bool
+   */
+  public $queue_thumbnail_downloads = FALSE;
...
+  /**
+   * Default status of this media bundle.
+   *
+   * @var array
+   */
+  public $status = TRUE;

Is there a reason why all these properties are public?

phenaproxima’s picture

  1. +++ b/core/modules/media_entity/config/schema/media_entity.schema.yml
    @@ -0,0 +1,64 @@
    +      label: 'Base folder for icons installation'
    

    Strange wording. Can this be something like "URI where media type icons will be installed"?

  2. +++ b/core/modules/media_entity/config/schema/media_entity.schema.yml
    @@ -0,0 +1,64 @@
    +field.formatter.settings.media_thumbnail:
    +  type: mapping
    +  label: 'Media thumbnail field display format settings'
    +  mapping:
    +    image_link:
    +      type: string
    +      label: 'Link image to'
    +    image_style:
    +      type: string
    +      label: 'Image style'
    

    This looks quite similar to the core image formatter. Can we re-use its config schema?

  3. +++ b/core/modules/media_entity/js/media_bundle_form.js
    @@ -0,0 +1,45 @@
    +      $('#edit-language', context).drupalSetSummary(function (context) {
    

    Should be using $context.find().

  4. +++ b/core/modules/media_entity/js/media_bundle_form.js
    @@ -0,0 +1,45 @@
    +        vals.push($('.js-form-item-language-configuration-langcode select option:selected', context).text());
    

    This too.

  5. +++ b/core/modules/media_entity/js/media_bundle_form.js
    @@ -0,0 +1,45 @@
    +        $('input:checked', context).next('label').each(function () {
    

    ...and this.

  6. +++ b/core/modules/media_entity/media_entity.api.php
    @@ -0,0 +1,25 @@
    +function hook_media_entity_type_info_alter(&$types) {
    

    $types should be type hinted.

  7. +++ b/core/modules/media_entity/media_entity.libraries.yml
    @@ -0,0 +1,17 @@
    +  dependencies:
    +    - core/jquery
    +    - core/drupal
    +    - core/drupal.form
    

    drupal.form depends on drupal and jquery, so we can probably remove those dependencies.

  8. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,85 @@
    +            ':media_entity_url' => 'https://www.drupal.org/project/media_entity',
    

    If this is going into core, we probably should not be linking to a contrib page :)

  9. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,85 @@
    +function media_entity_copy_icons($source, $destination) {
    

    Should we mark this function @internal? I don't see why it would ever be used under normal circumstances...

  10. +++ b/core/modules/media_entity/media_entity.theme.inc
    @@ -0,0 +1,40 @@
    +function template_preprocess_media(&$variables) {
    

    $variables should be type hinted.

  11. +++ b/core/modules/media_entity/src/EmbedCodeValueTrait.php
    @@ -0,0 +1,35 @@
    +    elseif ($value instanceof FieldItemInterface) {
    

    This should also be able to handle FieldItemListInterface.

  12. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    + * @ContentEntityType(
    + *   id = "media",
    + *   label = @Translation("Media"),
    + *   bundle_label = @Translation("Media bundle"),
    

    It'd be nice to have singular_label and plural_label defined. I suggest "media item" and "media items", respectively.

  13. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,386 @@
    +/**
    + * Form controller for node type forms.
    + */
    +class MediaBundleForm extends EntityForm {
    

    Node type forms? Not so much...

  14. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,386 @@
    +    $form['#entity'] = $bundle = $this->entity;
    

    Might be preferable to use $this->getEntity() here.

  15. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,238 @@
    +    if (!$this->entity->isNew()) {
    

    It might be more prudent to use $this->getEntity() here.

  16. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,238 @@
    +    $entity_type = $this->entity->getEntityType();
    

    Same here.

tim.plunkett’s picture

$form['#entity'] = $bundle = $this->entity;
Please remove the $form['#entity'] part.

tstoeckler’s picture

Status: Needs review » Needs work

Wow, this is great to see being pushed into core. Maybe we will have Media management in core in 2017. That's still ahead of the curve, right? No one else is doing that yet, right? Right? Right? (Sorry, couldn't resist. Really awesome that you are pursuing this so vigorously, though, this is genuinely awesome! Thank you! <3)

Reviewed the entire patch but for the test coverage, however just from the names of the test classes, it seems the test coverage is quite extensive, nice work!

I like the architecture, and it's good to see that a lof of other issues have now fallen into that we do not have to duplicate so much work. Although of course at the same time it reveals a lot of places where we do need a lot more generalization. One issue in particular from looking at the patch is that I think we should open a parallel issue (or I guess there must be one already) for generic entity actions. There really is nothing media-specific in those action plugins. It would be unfair to block this on that, though.

The one larger problem I have architecturally is the field mappings. I think it would be nicer if the media type plugins could provide field definitions and then those fields would be added automatically to the entity. It seems A) like a waste of time and B) error-prone to make people go and configure the same fields over and over again in the UI, when the plugin knows exactly what it wants already. This is possible in Entity API in contrib, and I think is a pattern we should look at here as well. That's the only major quibble with the patch, the architecture with the plugins and bundles looks solid although it does show that we should really have better integration between Entity API and Plugin API to make this easier (again: not the fault of this patch).

Yay!

Some notes on the actual patch:

  1. +++ b/core/modules/media_entity/config/schema/media_entity.schema.yml
    @@ -0,0 +1,64 @@
    +    type:
    +      type: string
    +      label: 'Type plugin ID'
    ...
    +    type_configuration:
    +      type: media_entity.bundle.type.[%parent.type]
    

    I know this is nitpicking, but it would be nice to have these directly one after the other

  2. +++ b/core/modules/media_entity/media_entity.info.yml
    @@ -0,0 +1,11 @@
    +  - drupal:views
    

    This is fairly problematic IMHO, I think there is a lot of valid use-cases of building drupal sites without Views including media ones.

    Is config/optional for all the views not an option?

  3. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,85 @@
    +  $suggestions[] = 'media__' . $media->id();
    +  $suggestions[] = 'media__' . $media->id() . '__' . $sanitized_view_mode;
    

    Let's remove those. See #2808481: [PP-1] Introduce generic entity template with standard preprocess and theme suggestions for more background on the different suggestions for suggestions (;-)) but removing the ID-specific ones is pretty uncontroversial I think.

  4. +++ b/core/modules/media_entity/media_entity.routing.yml
    @@ -0,0 +1,14 @@
    +entity.media_bundle.collection:
    +  path: '/admin/structure/media'
    +  defaults:
    +    _entity_list: 'media_bundle'
    +    _title: 'Media bundles'
    +  requirements:
    +    _permission: 'administer media bundles'
    

    This should now no longer be necessary if you add a collection link and an admin permission to the entity definition

  5. +++ b/core/modules/media_entity/media_entity.tokens.inc
    @@ -0,0 +1,180 @@
    +    'name' => t("Media ID"),
    ...
    +    'name' => t("Media UUID"),
    ...
    +    'name' => t("Revision ID"),
    ...
    +    'name' => t("Media bundle"),
    ...
    +    'name' => t("Media bundle name"),
    +    'description' => t("The human-readable name of the media bundle."),
    ...
    +    'name' => t("Author"),
    ...
    +    'name' => t("URL"),
    +    'description' => t("The URL of the media."),
    ...
    +    'name' => t("Edit URL"),
    ...
    +    'name' => t("Date created"),
    ...
    +    'name' => t("Date changed"),
    +    'description' => t("The date the media was most recently updated."),
    

    Sorry for the nitpick: Single quotes please.

  6. +++ b/core/modules/media_entity/src/Annotation/MediaType.php
    @@ -0,0 +1,43 @@
    +   * @var \Drupal\Core\Annotation\Translation (optional)
    

    The (optional) is not a thing for @var declarations as far as I'm aware. I think we can just drop it.

  7. +++ b/core/modules/media_entity/src/EmbedCodeValueTrait.php
    @@ -0,0 +1,35 @@
    +   * @param mixed $value
    

    Should be \Drupal\Core\Field\FieldItemInterface|string

  8. +++ b/core/modules/media_entity/src/EmbedCodeValueTrait.php
    @@ -0,0 +1,35 @@
    +  protected function getEmbedCode($value) {
    +    if (is_string($value)) {
    ...
    +    elseif ($value instanceof FieldItemInterface) {
    

    Adding a trait with only a single method that excepts either a string or a field item is very suspicious to me, but OK...

  9. +++ b/core/modules/media_entity/src/EmbedCodeValueTrait.php
    @@ -0,0 +1,35 @@
    +      $class = get_class($value);
    +      $property = $class::mainPropertyName();
    

    This should be

        $value->getFieldDefinition()->getFieldStorageDefinition()->getMainPropertyName();
    
  10. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    + *   label = @Translation("Media"),
    
    +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,244 @@
    + *   label = @Translation("Media bundle"),
    

    Let's add a bunch of labels (plural / singular / count)

  11. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    + *   links = {
    ...
    + *   }
    

    Per the above, let's add a "collection" link and then we can drop the collection route.

  12. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +  /**
    +   * Value that represents the media being published.
    +   */
    +  const PUBLISHED = 1;
    ...
    +  /**
    +   * Value that represents the media being unpublished.
    +   */
    +  const NOT_PUBLISHED = 0;
    ...
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function isPublished() {
    +    return (bool) $this->get('status')->value;
    +  }
    ...
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setPublished($published) {
    +    $this->set('status', $published ? Media::PUBLISHED : Media::NOT_PUBLISHED);
    +    return $this;
    +  }
    

    This should use EntityPublishedTrait instead.

  13. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    return $this->get('uid')->entity;
    ...
    +    return $this->get('uid')->target_id;
    ...
    +    $this->set('uid', $uid);
    

    I think, if we're using EntityOwnerInterface, this should be using $this->getOwner() internally. Or the other way around. But we should be duplicating the actual field name and so forth here, IMO.

    Same for getPublisherId() vs. getOwnerId() and setPublisherId() vs. setOwnerId()

  14. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    // Try to set a default name for this media, if there is no label provided.
    

    "... if no label is provided." would sound better IMO. Also the comma is not necessary as far as I remember.

  15. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    if (empty($this->label())) {
    

    Minor, but you can just replace empty($this->label()) with !$this->label()

  16. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    if ($this->bundle->entity->getQueueThumbnailDownloads() && $this->isNew()) {
    +      $thumbnail_uri = $this->getType()->getDefaultThumbnail();
    +    }
    +    else {
    +      $thumbnail_uri = $this->getType()->thumbnail($this);
    +    }
    

    I think this is worth a comment.

    Also I think this makes it clear the getQueueThumbnailDownloads() is a bad method name. It should be something like queueThumbnailDownloads() or similar.

    Also what happens if the entity is saved for the first time and then very quickly saved again, before the queue has been processed? Or do we not need to care about that?

  17. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    $existing = \Drupal::entityQuery('file')
    ...
    +      $file = $this->entityTypeManager()->getStorage('file')->create(['uri' => $thumbnail_uri]);
    

    Let's put $file_storage into a separate variable and the we can do $file_storage->getQuery() instead of \Drupal::entityQuery('file')

  18. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +      if ($publisher = $this->getOwner()) {
    +        $file->setOwner($publisher);
    

    If we're calling this $publisher then let's also call the getPublisher() method.

  19. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    $this->thumbnail->title = $this->label();
    

    Above there is a fallback in case $this->label() is not set. But this is called before that, so that seems problematic.

  20. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) {
    +      // If we are updating an existing node without adding a new revision, we
    +      // need to make sure $entity->revision_log is reset whenever it is empty.
    +      // Therefore, this code allows us to avoid clobbering an existing log
    +      // entry with an empty one.
    +      $record->revision_log = $this->original->revision_log->value;
    +    }
    

    I think this should be removed here and discussed for ContentEntityBase in a separate issue. It totally makes sense and is actually arguably a bug report that you can "delete" the revision log for existing nodes, for example.

  21. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +  public function validate() {
    +    $this->getType()->attachConstraints($this);
    +    return parent::validate();
    +  }
    

    Shouldn't the constraints be attached in getConstraints()? Maybe someone wants to use the constraints for something other than the validate() method.

  22. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +      ->setDisplayConfigurable('form', TRUE)
    +      ->setDisplayOptions('view', [
    

    I know it's pretty nitpicky but the order of these (form vs. view display options) is inconsistent among a bunch of these fields.

  23. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +      ->setDisplayConfigurable('view', TRUE);
    

    Are you sure this is a good idea? This runs into the whole page title vs. entity label issue which we really haven't solved properly yet. See #2353867: D8: Title does not appear as a field in Manage Display for some background on this. I think for now it would be better to leave this out.

  24. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +        'weight' => 1,
    ...
    +        'weight' => 0,
    

    Can we use a larger difference here (like 5) so people have a better chance of fitting in between.

  25. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,459 @@
    +    $fields['revision_timestamp'] = BaseFieldDefinition::create('created')
    +      ->setLabel(t('Revision timestamp'))
    +      ->setDescription(t('The time that the current revision was created.'))
    +      ->setQueryable(FALSE)
    +      ->setRevisionable(TRUE);
    +
    +    $fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
    +      ->setLabel(t('Revision publisher ID'))
    +      ->setDescription(t('The user ID of the publisher of the current revision.'))
    +      ->setSetting('target_type', 'user')
    +      ->setQueryable(FALSE)
    +      ->setRevisionable(TRUE);
    +
    +    $fields['revision_log'] = BaseFieldDefinition::create('string_long')
    +      ->setLabel(t('Revision Log'))
    +      ->setDescription(t('The log entry explaining the changes in this revision.'))
    +      ->setRevisionable(TRUE)
    +      ->setTranslatable(TRUE);
    ...
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getRevisionCreationTime() {
    +    return $this->revision_timestamp->value;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setRevisionCreationTime($timestamp) {
    +    $this->revision_timestamp->value = $timestamp;
    +    return $this;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getRevisionUser() {
    +    return $this->revision_uid->entity;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setRevisionUser(UserInterface $account) {
    +    $this->revision_uid->entity = $account;
    +    return $this;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getRevisionUserId() {
    +    return $this->revision_uid->target_id;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setRevisionUserId($user_id) {
    +    $this->revision_uid->target_id = $user_id;
    +    return $this;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getRevisionLogMessage() {
    +    return $this->revision_log->value;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setRevisionLogMessage($revision_log_message) {
    +    $this->revision_log->value = $revision_log_message;
    +    return $this;
    +  }
    

    This should use RevisionLogTrait instead.

  26. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,244 @@
    +    $bundle = static::load($media->bundle());
    +    return $bundle ? $bundle->label() : FALSE;
    

    It seems like something is broken if a media's bundle points to a non-existing bundle entity. So instead of MediaBundle::getLabel($media) you should always be able to do $media->bundle->entity->label(). If you think it's annoying to do $media->bundle-entity see you in #2699835: Add a method to ContentEntityBase for getting its Bundle entity.

    Or even in #969180: Add a convenience method for getting the label of a bundle which would obsolete this label method entirely.

    So I think this method should be dropped.

  27. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,244 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function exists($id) {
    +    return (bool) static::load($id);
    +  }
    

    Why is this needed? If it is for an 'exists' callback of machine name element, you can just use load() directly.

  28. +++ b/core/modules/media_entity/src/Form/MediaBundleDeleteConfirm.php
    @@ -0,0 +1,59 @@
    +      $caption = '<p>' . $this->formatPlural($num_entities, '%type is used by 1 piece of content on your site. You can not remove this content type until you have removed all of the %type content.', '%type is used by @count pieces of content on your site. You may not remove %type until you have removed all of the %type content.', ['%type' => $this->entity->label()]) . '</p>';
    

    Minor, but it's nice to use @count instead of 1 even in the singular case, because in some languages singular != 1

  29. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,386 @@
    +    $entity->set('type_configuration', $plugin_configuration);
    ...
    +    $entity->field_map = array_filter(
    

    I think this should use ->set() consistently

  30. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,238 @@
    +    // Save as a new revision if requested to do so.
    +    if (!$form_state->isValueEmpty('revision')) {
    +      $this->entity->setNewRevision();
    +    }
    

    Should this not happen in buildEntity()?

  31. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,238 @@
    +    $form_state->setValue('id', $this->entity->id());
    +    $form_state->set('id', $this->entity->id());
    

    Why is this is needed, this seems pretty non-standard?

  32. +++ b/core/modules/media_entity/src/MediaTypeException.php
    @@ -0,0 +1,45 @@
    +class MediaTypeException extends \Exception {
    

    This seems to have nothing to do with media, can we add something like this to \Drupal\Core\Form.

  33. +++ b/core/modules/media_entity/src/Plugin/Action/PublishMedia.php
    @@ -0,0 +1,38 @@
    +class PublishMedia extends ActionBase {
    ...
    +      ->andIf($object->status->access('edit', $account, TRUE));
    
    +++ b/core/modules/media_entity/src/Plugin/Action/UnpublishMedia.php
    @@ -0,0 +1,38 @@
    +class UnpublishMedia extends ActionBase {
    ...
    +      ->andIf($object->status->access('edit', $account, TRUE));
    

    Why "edit"? (and not "update")

  34. +++ b/core/modules/media_entity/src/Plugin/MediaEntity/Type/Generic.php
    @@ -0,0 +1,53 @@
    +    $form['text'] = [
    +      '#type' => 'markup',
    +      '#markup' => $this->t("This type provider doesn't need configuration."),
    +    ];
    

    I think we should have this. I think instead ConfigurablePluginInterface should be optional and then MediaBundleForm should check for that and only conditionally call buildConfigurationForm() that's also the pattern used for e.g. Image toolkits or search pages in core.

  35. +++ b/core/modules/media_entity/src/Plugin/MediaEntity/Type/Generic.php
    --- /dev/null
    +++ b/core/modules/media_entity/src/Plugin/QueueWorker/thumbnailDownloader.php
    

    Typo in the filename: Should be uppercase "t".

  36. +++ b/core/modules/media_entity/templates/media.html.twig
    @@ -0,0 +1,19 @@
    +  {% if content %}
    +    {{ content }}
    +  {% endif %}
    

    This probably needs some handling for the label/title for the full-page/non-full-page scenario. See node module or #2808481: [PP-1] Introduce generic entity template with standard preprocess and theme suggestions for more information.

  37. +++ b/core/modules/media_entity/tests/modules/media_entity_test_type/src/Plugin/MediaEntity/Type/TestType.php
    --- /dev/null
    +++ b/core/modules/media_entity/tests/modules/media_entity_test_views/config/install/views.view.test_media_entity_bulk_form.yml
    

    As said above, I think this should be in config/optional

tstoeckler’s picture

Some more notes.

I looked into 21. of #41 and I guess the reason you are not adding the constraints generically is because you need the entity object? But what is that use-case? That seems suspect to me.

  1. +++ b/core/modules/media_entity/media_entity.install
    @@ -0,0 +1,22 @@
    +  media_entity_copy_icons($source, $destination);
    

    It seems this is the only place that function is being used. If so, let's inline it please.

  2. +++ b/core/modules/media_entity/src/MediaTypeBase.php
    @@ -0,0 +1,160 @@
    +  public function getDefaultThumbnail() {
    +    return '';
    +  }
    

    Seems this could actually use the generic thumbnail by default and then Generic::thumbnail() could internally call $this->getDefaultThumbnail(), which seems like a reasonable bas implementation anyway, right?

  3. +++ b/core/modules/media_entity/src/MediaTypeInterface.php
    @@ -0,0 +1,88 @@
    +  public function label();
    ...
    +  public function providedFields();
    

    Also a get* Prefix would be nice here.

  4. +++ b/core/modules/media_entity/src/MediaTypeInterface.php
    @@ -0,0 +1,88 @@
    +  public function thumbnail(MediaInterface $media);
    ...
    +  public function getDefaultThumbnail();
    

    These should consistently use the get* prefix. Also I personally would find it more self-documenting to call them get*ThumbnailUri(), but I can see how that would make the function name pretty long, so feel free to ignore this.

Gábor Hojtsy’s picture

The mapping UI came up as a UX problem too. First that you need to add all those fields and second to do the mappings. @slashrsm explained that the idea was that solution/feature modules would ship with the entity bundles with respective fields, so in practice installing the media module or lightning or np8 distro, etc. would get you a preconfigured set. But with this being in core that situation is different because then its not just a fundamental lego block but it should actually make users' life simpler too :)

Berdir’s picture

We could find a way to define that as an optional, non-BC breaking step? There could be a button "Create default fields" above the mapping that a plugin can support with an additional new interface? or even in the same, with an empty default method (then we can't conditionally show the button, though)

tstoeckler’s picture

But why even make it an empty step? Why not just do it in the first place?

Berdir’s picture

To give you a chance to do it different?

If each plugin defines its own field, then each needs to provide unique, prefixed field storages, so that they don't conflict. Otherwise twitter and youtube might define a field with the same name that has a different type.

However, the whole point of having the field mapping is that you can re-use the same field across different bundles, so that it helps you simplify theming/views configuration.

chr.fritsch’s picture

Worked on the comments in #39

1. Fixed
2. We can open a follow up to generalize that
3., 4., 5. Fixed
6. Fixed
7. Fixed
8. We can fix it later, when we have a project page
9. Not sure, other media modules can use that in their install function, too.
10. Fixed
11. Needs work
12. Fixed
13. Fixed
14. Fixed
15. Fixed

slashrsm’s picture

Thank you all for all the hard work, nice words and valuable input!

I feel that the metadata mapping part causes a bit of a confusion. Let me try to explain the reasoning behind it and its architecture. In order to do that we have to go back into the distant (D7 :)) past....

Media solutions in D7 were problematic in many ways, but they were useful. We were able to achieve many different things with them, but there was at least one feature that was nonexistent. Metadata handling (specially useful and important when you are dealing with remote media). There were a lot of modules that were providing support for various providers. Some of them even provided some metadata, but the APIs were not consistent. Every module would have its way of doing things. For example, if your site hosted YouTube and Vimeo videos and you wanted to display their titles and captions with them you most likely needed to implement two totally separate ways of getting that information in your theme. One per provider.

Idea of the metadata handling in Media entity was to standardize that. And it works on two levels. First level is API on media type plugins that allows for standardized discovery and retrieval of metadata information that a given type plugin provides. This API can be used by developers to access and use that information. This API was there way before mapping thing even existed. You can use this information to fill data into the themeing layer completely bypassing Entity API fields. Sometimes this can be a good idea and sometimes not. It depends....

Problems are with fields that we fetch from remote APIs since their retrieval is a slow process. Those you generally want to store locally to save many slow API calls. Cache is one option. If you aggressively cache rendered representation of the media it might be enough in some cases. And the other options are entity fields.

Only at this point we've reached 2nd level. We have this immensely powerful data storage layer in Drupal and it would be shame not to offer out-of the box, site-builder ready integration with our metadata api. Mapping is born. Tada! There are two main reasons why mapping is not enabled/defined by default: a) Types can provide *many* fields and it is unlikely we will need all of them all the time and b) we would need to auto-create many fields for them, which would be huge overhead IMO.

Solution to this problem is quite simple in my opinion:
1. UX team gives input on mapping form and we improve it. Maybe we move it on a separate screen. We make it smarter so it detects if there are no fields to map to, dynamically update when things change, ... This is an easy task assuming someone with UX/interaction design experience leads the way (we would do that in contrib already, but it seems that relevant people became interested in this only now).
2. Default bundles () that we create provide limited level of mapping that makes sense for 80% use case. This means 2 things: a) 80% of people won't need to touch this at all b) default configuration will act as an example. Relevant issues from parent meta: #2831936: Add "File" MediaSource plugin, #2831937: Add "Image" MediaSource plugin and #2831944: Implement media type plugin for remote video.

ekes’s picture

It was suggested I put this use case in here, I didn't quite think it was the issue, but now I see the metadata discussion I understand:

I'm presently looking at the metadata extraction for indexing (via search api). First instance is the obvious pdf text, but then there's audio track information (even lyrics), exif data location. These are made available depending on the extractors installed (tika is in solr, but there could be any number of other external programs, services, as well as things like exif_* commands being available in php). They are exposed as additional properties that are not actually on the entity that search api can index.

It seemed to make sense to make these available to the site elsewhere. A tagged service was suggested.

So media could offer up the additional potential fields for mapping if there is a service installed for the media type(s). I was thinking that media would just call all tagged services for the potential mime type supported by the field; but it's not defined anywhere what mime types each media entity could support (*, audio/* etc). Or maybe it is only the work of the individual providers?

amateescu’s picture

#2789315: Create EntityPublishedInterface and use for Node and Comment got committed recently so here's an updated patch which makes proper use of it :)

jibran’s picture

Some minor observations.

  1. +++ b/core/modules/media_entity/media_entity.install
    @@ -0,0 +1,22 @@
    +/**
    + * Implements hook_update_last_removed().
    + */
    +function media_entity_update_last_removed() {
    +  return 8003;
    +}
    

    Do we need this in core?

  2. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,433 @@
    +    // If no revision author has been set explicitly, make the media owner the
    +    // revision author.
    +    if (!$this->get('revision_uid')->entity) {
    +      $this->setRevisionUserId($this->getOwnerId());
    +    }
    

    Why can't we use setDefaultValueCallback for r_uid basefields?

  3. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,384 @@
    +        'exists' => ['\Drupal\media_entity\Entity\MediaBundle', 'exists'],
    

    We can use class constant here.

marcoscano’s picture

I'm not sure whether this saves anyone some time or only adds confusion (sorry if it's the case), but I have advanced some quickfixes from review #41:

41-1) Fixed
41-2) Needs work
41-3) Fixed
41-4) Fixed
41-5) Fixed
41-6) Fixed
41-7) Fixed
41-8) Needs work
41-9) Fixed
41-10) Fixed
41-11) Was this refering to MediaBundle.php instead of Media.php? The collection link is present in the first case, and there is no route 41-for the second (it's a view)
41-12) Fixed in #52
41-13) Needs work
41-14) Fixed
41-15) Fixed
41-16) Needs work
41-17) Fixed
41-18) Needs work
41-19) Needs work
41-20) Needs work
41-21) Needs work
41-22) Fixed
41-23) Needs work
41-24) Fixed
41-25) Needs work (might lead to some BC issues?)
41-26) Needs work
41-27) Needs work
41-28) Fixed
41-29) Fixed
41-30) Needs work
41-31) Needs work
41-32) Needs work
41-33) Needs work
41-34) Needs work
41-35) Fixed
41-36) Needs work. (the solution implemented here will probably also solve #2754277)
41-37) Needs work

chr.fritsch’s picture

Assigned: chr.fritsch » Unassigned
Status: Needs work » Needs review
FileSize
170.54 KB
2.66 KB

41-2) Since we remove the media view because it will be replaced with the library, i think its fine when we remove the dependency
41-8) Needs still work
41-13) Fixed
41-16) Added a comment, not sure about renaming the function, because we want to try not to change the APIs
41-18) Renamed variable to owner, we want to use methods from EntityOwnerInterface
41-19) Setting the thumbnail is now the last action
41-20) Needs follow up
41-21) Needs still work
41-23) As suggested, lets leave it out
41-30) Needs work
41-31) Needs work
41-32) Needs work
41-33) Needs work
41-34) Needs work
41-36) Needs work
41-37) This is just for testing purpose and test will not work without. So i think in config/install is fine

39-11, #42 and #54 still needs to be addressed

slashrsm’s picture

#41-8: This trait is used just by two contrib plugins (Twitter and Instagram AFAIK). Since it wouldn't have any usage in core we should remove it IMO.
#41-16: Improved the comment a bit. I agree that we should try not to break things.
#41-21: Is there getConstraints() on the entity level? I remember looking for this when initially implementing this but wasn't able to find better way. Note that I am not super familiar with this part of D8 and would appreciate any hints.
#41-30: This is how #2669802: Add a content entity form which allows to set revisions is solving this. Main reason for this approach is the fact that you can't decide not to save as new revision once you've set it (which happens in prepareEntity()).
#41-31: Removed.
#41-32: This exception is used only in contrib modules. We could move it to some other namespace inside core, but since there would be no core usages of it that doesn't make much sense in my opinion.
#41-33: Fixed.
#41-34: Removed the message. Didn't change bundle form part yet as I am not sure how to approach this? Should we still display plugin configuration fieldset with a message or entirely remove it it?
#41-36: Fixed.
#39-11: Not relevant after #41-8 anymore.
#42-1: A lot of contrib modules also use this function. I'd prefer if we can leave it like this.
#42-2: Done.
#42-3 and #42-4: We also need to consider BC aspect. I'd be totally for standardization of function names, but would prefer to leave existing functions in and declare them deprecated. Thoughts?
#54-1: Yes. It was added in #14 based on #11-4
#54-2: Done. While working on this I also notice that we're not updatng r_uid and r_timestamp correctly. Also fixed that.
#54-3: Fixed.

jibran’s picture

Thanks @slashrsm for fixing the review.
Can we add docs around media_entity_update_last_removed() and magic 8003?

slashrsm’s picture

Good idea. Here we go.

dawehner’s picture

Just some additional feedback/questions.

  1. +++ b/core/modules/media_entity/config/schema/media_entity.schema.yml
    @@ -0,0 +1,64 @@
    +action.configuration.media_delete_action:
    +  type: action_configuration_default
    +  label: 'Delete media configuration'
    +
    +action.configuration.media_save_action:
    +  type: action_configuration_default
    +  label: 'Save media configuration'
    +
    +action.configuration.media_publish_action:
    +  type: action_configuration_default
    +  label: 'Publish media configuration'
    +
    +action.configuration.media_unpublish_action:
    +  type: action_configuration_default
    +  label: 'Unpublish media configuration'
    +
    

    It is sad that action.configuration.* doesn't exist.

  2. +++ b/core/modules/media_entity/media_entity.links.menu.yml
    @@ -0,0 +1,11 @@
    +  title: 'Add a new media'
    

    For me "adding" implies "new", so "Add media" would mean the same

  3. +++ b/core/modules/media_entity/media_entity.theme.inc
    @@ -0,0 +1,38 @@
    +
    +  $variables['attributes']['class'][] = 'media';
    +  $variables['attributes']['class'][] = Html::getClass('media-' . $variables['media']->bundle());
    +  $variables['attributes']['class'][] = Html::getClass('view-mode-' . $variables['elements']['#view_mode']);
    +  if (!$variables['media']->isPublished()) {
    +    $variables['attributes']['class'][] = 'unpublished';
    +  }
    

    Isn't this added on the template level only now these days? So aka. move this to classy templates.

  4. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    + *   render_cache = TRUE,
    

    I don't see any use of this annotation in core

  5. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    + *     "published" = "status",
    

    Wait, shouldn't this be 'status' = 'published', given that \Drupal\node\Entity\Node::isPublished gets the value from the 'status' key.

  6. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    +class Media extends ContentEntityBase implements MediaInterface {
    ...
    +    $fields['revision_timestamp'] = BaseFieldDefinition::create('created')
    +      ->setLabel(t('Revision timestamp'))
    +      ->setDescription(t('The time that the current revision was created.'))
    +      ->setQueryable(FALSE)
    +      ->setRevisionable(TRUE);
    +
    +    $fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
    +      ->setLabel(t('Revision publisher ID'))
    +      ->setDescription(t('The user ID of the publisher of the current revision.'))
    +      ->setDefaultValueCallback('Drupal\media_entity\Entity\Media::getCurrentUserId')
    +      ->setSetting('target_type', 'user')
    +      ->setQueryable(FALSE)
    +      ->setRevisionable(TRUE);
    +
    +    $fields['revision_log'] = BaseFieldDefinition::create('string_long')
    +      ->setLabel(t('Revision Log'))
    +      ->setDescription(t('The log entry explaining the changes in this revision.'))
    +      ->setRevisionable(TRUE)
    +      ->setTranslatable(TRUE);
    

    Its sad we cannot use RevisionableContentEntityBase

  7. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    +    if (!$update && $this->bundle->entity->getQueueThumbnailDownloads()) {
    

    I was wondering whether this should be fetched from the media type instead.

  8. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function automaticallySetThumbnail() {
    +    /** @var \Drupal\Core\Entity\EntityStorageInterface $file_storage */
    +    $file_storage = $this->entityTypeManager()->getStorage('file');
    +
    +    // If thumbnail fetching should be queued then temporary use default
    +    // thumbnail or fetch it immediately otherwise.
    +    if ($this->bundle->entity->getQueueThumbnailDownloads() && $this->isNew()) {
    +      $thumbnail_uri = $this->getType()->getDefaultThumbnail();
    +    }
    +    else {
    +      $thumbnail_uri = $this->getType()->thumbnail($this);
    +    }
    +    $existing = $file_storage->getQuery()
    +      ->condition('uri', $thumbnail_uri)
    +      ->execute();
    +
    +    if ($existing) {
    +      $this->thumbnail->target_id = reset($existing);
    +    }
    +    else {
    +      /** @var \Drupal\file\FileInterface $file */
    +      $file = $file_storage->create(['uri' => $thumbnail_uri]);
    +      if ($owner = $this->getOwner()) {
    +        $file->setOwner($owner);
    +      }
    +      $file->setPermanent();
    +      $file->save();
    +      $this->thumbnail->target_id = $file->id();
    +    }
    +
    +    // TODO - We should probably use something smarter (tokens, ...).
    +    $this->thumbnail->alt = t('Thumbnail');
    +    $this->thumbnail->title = $this->label();
    +  }
    

    This seems logic which could live on its own service outside of the main media content entity.

  9. +++ b/core/modules/media_entity/src/Form/DeleteMultiple.php
    @@ -0,0 +1,199 @@
    +    $this->entityInfo = $this->tempStoreFactory->get('media_multiple_delete_confirm')->get(\Drupal::currentUser()->id());
    +    if (empty($this->entityInfo)) {
    

    Note: You can use $this->currentUser() directly here.

  10. +++ b/core/modules/media_entity/src/Form/DeleteMultiple.php
    @@ -0,0 +1,199 @@
    +    $form_state->setRedirect('system.admin_content');
    

    Can't we redirect to the media library?

  11. +++ b/core/modules/media_entity/src/Form/MediaDeleteForm.php
    @@ -0,0 +1,20 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getCancelUrl() {
    +    return Url::fromUri('internal:/');
    +  }
    

    Wait, is that the same as using Url::fromRoute('<front>') which seems a little bit more semantic.

  12. +++ b/core/modules/media_entity/src/MediaTypeBase.php
    @@ -0,0 +1,160 @@
    +  /**
    +   * The entity type manager service.
    +   *
    +   * @var \Drupal\Core\Entity\EntityTypeManagerInterface;
    +   */
    +  protected $entityTypeManager;
    +
    +  /**
    +   * The entity field manager service.
    +   *
    +   * @var \Drupal\Core\Entity\EntityFieldManagerInterface;
    +   */
    +  protected $entityFieldManager;
    ...
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    +    return new static(
    +      $configuration,
    +      $plugin_id,
    +      $plugin_definition,
    +      $container->get('entity_type.manager'),
    +      $container->get('entity_field.manager'),
    +      $container->get('config.factory')
    +    );
    +  }
    

    At least these two services seems not needed by default, nor by the generic plugin. Are you sure this will be useful in most of the cases?

seanB’s picture

Regarding #31. I understand that it is probably a lot of work to write a upgrade path when renaming the module, but having the inconsistency between the media entity and the node/user/taxonomy entities right from the start doesn't feel right. Just naming the module 'media' should be the way to go imho.

How about providing a upgrade path in contrib so this could be out of scope for the initial development (in the media_entity module for example)? The media entity bundles people defined might not be compatible with the defaults in core so there might be trouble when upgrading anyway?

Bojhan’s picture

Issue tags: +Needs screenshots

I need up-to-date screenshots to review. Also likely better to split the UI review into a seperate issue.

yoroy’s picture

Issue tags: -Needs screenshots

I collected screenshots in here: https://docs.google.com/document/d/1BnkA4fXZWG1nC4ipyl90BSvnE6Buqyjuz4gT...

Separate issue for the ui parts is a good idea.

phenaproxima’s picture

  1. +++ b/core/modules/media_entity/config/schema/media_entity.schema.yml
    @@ -0,0 +1,64 @@
    +field.formatter.settings.media_thumbnail:
    +  type: mapping
    +  label: 'Media thumbnail field display format settings'
    +  mapping:
    +    image_link:
    +      type: string
    +      label: 'Link image to'
    +    image_style:
    +      type: string
    +      label: 'Image style'
    

    This looks pretty similar to the config schema for field.formatter.settings.image. Can we simply re-use that here?

  2. +++ b/core/modules/media_entity/media_entity.api.php
    @@ -0,0 +1,25 @@
    + * Hooks related to media entity and it's plugins.
    

    Supernit: should be "its".

  3. +++ b/core/modules/media_entity/media_entity.info.yml
    @@ -0,0 +1,10 @@
    +description: 'Media entity API.'
    

    We are probably going to need something a little more descriptive here.

  4. +++ b/core/modules/media_entity/media_entity.info.yml
    @@ -0,0 +1,10 @@
    +dependencies:
    +  - drupal:image
    +  - drupal:user
    +  - drupal:system
    

    I've never seen this pattern before.

  5. +++ b/core/modules/media_entity/media_entity.links.menu.yml
    @@ -0,0 +1,11 @@
    +  title: 'Media bundles'
    

    I don't care what we call these internally, but the users should never see the word "bundles" -- it's too techy. Can we expose these as "media types"? I'm told that the UX people are in agreement about this...

  6. +++ b/core/modules/media_entity/media_entity.links.menu.yml
    @@ -0,0 +1,11 @@
    +  title: 'Add a new media'
    

    Can this be "Add media"?

  7. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,83 @@
    +            ':media_entity_url' => 'https://www.drupal.org/project/media_entity',
    

    We probably should not be linking to the contrib module page :)

  8. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,83 @@
    +function media_entity_copy_icons($source, $destination) {
    

    Discussed with @seanB in Berlin -- I really don't like that we're relying on a library of non-vector icons existing in the file system. It's beyond the scope of this patch, but I think we should supply a font with all the media type icons, and a style sheet providing classes that use those glyphs. That way people can still override the icons if they need to, and we completely avoid any file system-related jankiness. We should create a follow-up issue to discuss and implement this approach.

  9. +++ b/core/modules/media_entity/media_entity.permissions.yml
    @@ -0,0 +1,18 @@
    +  title: 'Administer media bundles'
    

    s/bundles/types

  10. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    + *   data_table = "media_field_data",
    + *   revision_table = "media_revision",
    + *   revision_data_table = "media_field_revision",
    

    I'm not too concerned, but why did we override these in the first place?

  11. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    +    if ($this->bundle->entity->getQueueThumbnailDownloads() && $this->isNew()) {
    +      $thumbnail_uri = $this->getType()->getDefaultThumbnail();
    +    }
    +    else {
    +      $thumbnail_uri = $this->getType()->thumbnail($this);
    +    }
    

    I think I'd prefer it if we first tried $this->getType()->thumbnail($this), and if it returns a falsy value, fall back to getDefaultThumbnail().

  12. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    +    if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) {
    

    The last bit of this can be empty($record->revision_log), for simplicity's sake..

  13. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,438 @@
    +      ->setDefaultValueCallback('Drupal\media_entity\Entity\Media::getCurrentUserId')
    

    Can this be static::class . '::getCurrentUserId'?

  14. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    + *   label_singular = @Translation("media bundle item"),
    + *   label_plural = @Translation("media bundle items"),
    + *   label_count = @PluralTranslation(
    + *     singular = "@count media bundle item",
    + *     plural = "@count media bundle items"
    

    Let's get rid of the "item(s)" here. It reads strangely. Also, s/bundle(s)?/type$1

  15. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +    return $this->description;
    

    Can we use $this->get()?

  16. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +    $this->description = $description;
    +    return $this;
    

    Can we do return $this->set('description', $description)?

  17. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +    return $this->type_configuration;
    

    For consistency, can this be changed to $this->get()?

  18. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +  public function setTypeConfiguration($configuration) {
    

    Can $configuration be type hinted?

  19. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +    $this->type_configuration = $configuration;
    

    I'd prefer using $this->set().

  20. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +    return $this->queue_thumbnail_downloads;
    

    $this->get()

  21. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +    $this->queue_thumbnail_downloads = $queue_thumbnail_downloads;
    

    Can we use $this->set() here?

  22. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,243 @@
    +    return $this->status;
    

    $this->get()

  23. +++ b/core/modules/media_entity/src/Form/DeleteMultiple.php
    @@ -0,0 +1,199 @@
    +class DeleteMultiple extends ConfirmFormBase {
    

    So it looks like this code was copied more or less directly from Node's DeleteMultiple class. If we're going to re-use this logic, I suggest that we open a follow-up issue to move Node's form into a generic core namespace for re-use.

  24. +++ b/core/modules/media_entity/src/Form/MediaBundleDeleteConfirm.php
    @@ -0,0 +1,62 @@
    +class MediaBundleDeleteConfirm extends EntityDeleteForm {
    

    Again, this is adapted from Node. We should move the code out of Node so the rest of core can reuse it.

    ...in a followup, of course.

  25. +++ b/core/modules/media_entity/src/Form/MediaDeleteForm.php
    @@ -0,0 +1,20 @@
    +class MediaDeleteForm extends ContentEntityDeleteForm {
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getCancelUrl() {
    +    return Url::fromUri('internal:/');
    +  }
    

    I'm not sure why we're doing this. Can we simply use core's ContentEntityDeleteForm instead?

  26. +++ b/core/modules/media_entity/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    +    $is_owner = ($account->id() && $account->id() == $entity->getPublisherId()) ? TRUE : FALSE;
    

    Should be using getOwnerId(), not the deprecated getPublisherId().

  27. +++ b/core/modules/media_entity/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    +        return AccessResult::allowedIf($account->hasPermission('view media') && $entity->status->value)->cachePerPermissions()->addCacheableDependency($entity);
    

    Can we use methods of EntityPublishedInterface instead of $entity->status->value?

  28. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +  /**
    +   * Constructs a new class instance.
    +   *
    +   * @param \Drupal\media_entity\MediaTypeManager $media_type_manager
    +   *   Media type manager.
    +   */
    +  public function __construct(MediaTypeManager $media_type_manager, EntityFieldManagerInterface $entity_field_manager) {
    

    Missing documentation for $entity_field_manager.

  29. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +      '#description' => $this->t('Describe this media bundle. The text will be displayed on the <em>Add new media</em> page.'),
    

    s/bundle/type

  30. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +      '#description' => $this->t('Media type provider plugin that is responsible for additional logic related to this media.'),
    

    Should be "...logic related to this media type."

  31. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +        'progress' => [
    +          'type' => 'throbber',
    +          'message' => $this->t('Updating type provider configuration form.'),
    +        ],
    

    Not sure we need this.

  32. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +      $plugin_configuration = (empty($this->configurableInstances[$plugin->getPluginId()]['plugin_config'])) ? $bundle->getTypeConfiguration() : $this->configurableInstances[$plugin->getPluginId()]['plugin_config'];
    

    Nit: No need for the switching expression to be in parentheses.

  33. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +      $skipped_fields = [
    +        'mid',
    +        'uuid',
    +        'vid',
    +        'bundle',
    +        'langcode',
    +        'default_langcode',
    +        'uid',
    +        'revision_timestamp',
    +        'revision_log',
    +        'revision_uid',
    +      ];
    

    I'd prefer to do an array_diff_key against $entityFieldManager->getBaseFieldDefinitions(), but this is OK for now.

  34. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +    if ($this->moduleHandler->moduleExists('language')) {
    

    $this->moduleHandler is not explicitly injected; if there's a moduleHandler() helper method, let's use it.

  35. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +    $actions['submit']['#value'] = $this->t('Save media bundle');
    +    $actions['delete']['#value'] = $this->t('Delete media bundle');
    

    Either s/bundle/type, or (my preference) just say "Save" and "Delete".

  36. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +      drupal_set_message($this->t('The media bundle %name has been updated.', $t_args));
    

    s/bundle/type

  37. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,385 @@
    +      drupal_set_message($this->t('The media bundle %name has been added.', $t_args));
    

    s/bundle/type

  38. +++ b/core/modules/media_entity/src/MediaBundleInterface.php
    @@ -0,0 +1,115 @@
    +  /**
    +   * Checks if the bundle exists.
    +   *
    +   * @param int $id
    +   *   The Media bundle ID.
    +   *
    +   * @return bool
    +   *   TRUE if the bundle with the given ID exists, FALSE otherwise.
    +   */
    +  public static function exists($id);
    

    This is only used by the bundle form; IMHO it does not belong on the entity interface.

  39. +++ b/core/modules/media_entity/src/MediaBundleInterface.php
    @@ -0,0 +1,115 @@
    +  /**
    +   * Returns the Media bundle description.
    +   *
    +   * @return string
    +   *   Returns the Media bundle description.
    +   */
    +  public function getDescription();
    

    Why not simply use EntityDescriptionInterface?

  40. +++ b/core/modules/media_entity/src/MediaBundleInterface.php
    @@ -0,0 +1,115 @@
    +  public function setTypeConfiguration($configuration);
    

    I'd like $configuration to be type hinted.

  41. +++ b/core/modules/media_entity/src/MediaBundleListBuilder.php
    @@ -0,0 +1,51 @@
    +    $build['#empty'] = $this->t('No media bundle available. <a href="@link">Add media bundle</a>.', [
    

    s/bundle(s)?/type$1

  42. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,242 @@
    +      $this->entity->setRevisionUserId(\Drupal::currentUser()->id());
    

    Let's use $this->currentUser()->id().

  43. +++ b/core/modules/media_entity/src/MediaForm.php
    @@ -0,0 +1,242 @@
    +    $t_args = ['@type' => $this->entity->bundle->entity->label(), '%info' => $this->entity->label()];
    

    Why are we calling the label %info? Can it be called %label?

  44. +++ b/core/modules/media_entity/src/MediaInterface.php
    @@ -0,0 +1,87 @@
    +  /**
    +   * Returns the media type.
    +   *
    +   * @return \Drupal\media_entity\MediaTypeInterface
    +   *   The media type.
    +   */
    

    For clarity, can we say "type plugin" here instead of just "type"? Ideally we would rename the method as well, but I understand that we can't for BC reasons.

  45. +++ b/core/modules/media_entity/src/MediaTypeInterface.php
    @@ -0,0 +1,88 @@
    +  /**
    +   * Returns the display label.
    +   *
    +   * @return string
    +   *   The display label.
    +   */
    

    Needs more explanation. Returns the display label of what, exactly?

  46. +++ b/core/modules/media_entity/src/MediaTypeInterface.php
    @@ -0,0 +1,88 @@
    +   *   Field value or FALSE if data unavailable.
    

    What if FALSE is a valid value for a field? <evil grin>

  47. +++ b/core/modules/media_entity/src/MediaTypeInterface.php
    @@ -0,0 +1,88 @@
    +   * if no other is available. This functions should always return a valid URI.
    

    Nit: "functions" should be "function".

  48. +++ b/core/modules/media_entity/src/MediaTypeInterface.php
    @@ -0,0 +1,88 @@
    +   * @param MediaInterface $media
    +   *   Media.
    

    Needs FQCN and a better description.

  49. +++ b/core/modules/media_entity/src/MediaTypeInterface.php
    @@ -0,0 +1,88 @@
    +   *   Uri of the default thumbnail image.
    

    Nit: s/Uri/URI

  50. +++ b/core/modules/media_entity/src/MediaTypeManager.php
    @@ -0,0 +1,32 @@
    +   * Constructs a new MediaTypeManager.
    +   *
    +   * @param \Traversable $namespaces
    +   *   An object that implements \Traversable which contains the root paths
    +   *   keyed by the corresponding namespace to look for plugin implementations.
    +   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
    +   *   Cache backend instance to use.
    +   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    +   *   The module handler.
    

    I think we could just {@inheritdoc} here...?

  51. +++ b/core/modules/media_entity/src/Plugin/Action/DeleteMedia.php
    @@ -0,0 +1,98 @@
    +   * @param AccountInterface $current_user
    

    Needs FQCN.

  52. +++ b/core/modules/media_entity/src/Plugin/Action/DeleteMedia.php
    @@ -0,0 +1,98 @@
    +    foreach ($entities as $media) {
    

    We can't assume $media is an object, given the logic in execute().

  53. +++ b/core/modules/media_entity/src/Plugin/Action/DeleteMedia.php
    @@ -0,0 +1,98 @@
    +    $this->tempStore->set($this->currentUser->id(), $info);
    

    It doesn't look like anything else in this class uses the tempstore...what is this needed for?

  54. +++ b/core/modules/media_entity/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,188 @@
    +class MediaThumbnailFormatter extends ImageFormatter {
    

    I only quick-skimmed this, but I don't understand why we can't just use the core image formatter. I see no dramatic differences between this one and that one.

  55. +++ b/core/modules/media_entity/src/Plugin/views/wizard/Media.php
    @@ -0,0 +1,84 @@
    + * Tests creating media views with the wizard.
    

    Tests? This does not appear to be a test :)

  56. +++ b/core/modules/media_entity/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,95 @@
    + * Tests creating media revision views with the wizard.
    

    Not a test.

  57. +++ b/core/modules/media_entity/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,95 @@
    +class MediaRevision extends WizardPluginBase {
    

    Given some of the code in this wizard, it looks like it should extend the Media one rather than WizardPluginBase.

yoroy’s picture

Re: 8 in #67 There's an issue in place for creating SVG file type icons: https://www.drupal.org/node/2833768

slashrsm’s picture

This is addressing #67:

#1 Done
#2 Done
#3 Tried to improve it. More ideas welcome.
#4 Contrib uses this notation a lot. Core does not though. Removed.
#5 This is being discussed as part of UX review. It seems that it will be changed to "types". Let's wait for final decision and address this together with other UX things.
#6 Done
#7 Needs work (whole hook_help() needs to be rewritten)
#8 Great to see that this is already in progress. If icons issue is done when this is ready we can change the icons as part of this patch. If not we can totally do this in a follow-up in my opinion.
#9 See #5
#10 I believe that every content entity type needs to define those.
#11 This has to do with queued thumbnail downloads. If queued downloads are enabled we set default field and rely on queue to set it properly later (i.e works as designed).
#12 Done.
#13 Done.
#14 Removed "items". See #5 for s/bundle/type
#15, #16, #17, #19, #20, #21, #22 Should be equivalent. Other core bundle entity types use the current approach as well.
#18 Done.
#23, #24 Yes, let's do that!
#25 Done.
#26 Done.
#27 Done.
#28 Done.
#29 See #5
#30 Done.
#31 Done.
#32 Done.
#34 I'd love to if it existed :)
#35, #36, #37 See #5
#38 Done. I used MediaBundle::load(), which should do the job.
#39 Done.
#40 Done.
#41 See #5
#42 Done.
#43 Done.
#44 Done.
#45 Done.
#46 Good point. How to address this? Throw an exception if data is not available?
#47 Done.
#48 Done.
#49 Done.
#50 Manager has different signature than parent.
#51 Done.
#52 Done.
#53 It is the way action communicates with the confirm form. Another node C/P.
#54 Main difference is field type. Core formatter is for image fields while this one is for entity reference fields.
#55 Well, since core does it it must be OK, right? :D Fixed. We probably want to open a follow-up to fix this in other core classes as well.
#56 Same as a above.
#57 I am not sure. There are some similarities, but no part is exactly the same (at least table name is different). Extending Media wizard might save few lines of code but require few overrides that would make core more confusing IMO.

slashrsm’s picture

Status: Needs work » Needs review
FileSize
168.87 KB
630 bytes

This should fix the tests

samuel.mortenson’s picture

I noticed at /admin/structure/media/add that the "Type Provider Configuration" area is visible even when a provider has no configuration (i.e. the Generic Provider). The same applies to "Field Mapping", which isn't of much use if no metadata fields are available.

Can we conditionally hide these?

slashrsm’s picture

Status: Needs work » Needs review
FileSize
168.88 KB
675 bytes

Try nr. 2.

tkoleary’s picture

  1. +++ b/core/modules/media_entity/config/install/system.action.media_delete_action.yml
    @@ -0,0 +1,10 @@
    +label: 'Delete media'
    
    +++ b/core/modules/media_entity/config/install/system.action.media_publish_action.yml
    @@ -0,0 +1,10 @@
    +label: 'Publish media'
    
    +++ b/core/modules/media_entity/config/install/system.action.media_save_action.yml
    @@ -0,0 +1,10 @@
    +label: 'Save media'
    
    +++ b/core/modules/media_entity/config/install/system.action.media_unpublish_action.yml
    @@ -0,0 +1,10 @@
    +label: 'Unpublish media'
    

    A 'Media" is not a thing, and a noun is not really needed. 'delete, publish, unpublish, save' should suffice.

  2. +++ b/core/modules/media_entity/config/schema/media_entity.schema.yml
    @@ -0,0 +1,57 @@
    +  label: 'Media bundle'
    

    'Bundle' is tecnical language leaking into UI. We have content and comment types, this should be 'media type'

Berdir’s picture

@tkoleary: Media type has been discussed before and we expected that this would come up. The problem we have is that we have two different "types". We have plugins and we have config entities (the bundles). You could have multiple bundles that use the same type (media type plugin)

The most obvious way where this would show up is if we would rename Media bundle to Media type is that when you click on "Add media type", then the first thing (well the second, after the label) that you are asked is which Type your Type should be :)

That's why media_entity maintainers in contrib made the decision to use Bundle in the UI. The other options are:

a) deal with having two different things being called "Type"
b) Use "Plugin" instead of "Type" in the UI for the plugins, but I suppose that's not really better than using Bundle for the bundles..
c) Come up with a different name for either of the two types for the UI.. any ideas?

tkoleary’s picture

I'm leaning towards a) deal with having two different things being called "Type"

We could have:

a) 'media type' and 'Mime type' if mime type maps to the plugin (does it?).
b) If we think that even 'Mime type' is too jargony for end users (I do) we could just not refer to mime types at all but only to the plugins.

In that case we could call the plugins whatever we want. I'm leaning towards 'Media handlers'. To me that implies 'the thing that gets the media to actually show up on the page'.

Now it makes a lot more sense to the user following the logic "I can create a media type and call it whatever I want but if I want the thing to display I need to go find a 'handler' for it".

bojanz’s picture

I agree that the bundle/type duality is confusing. At this point we've been well trained that bundle == $entity_label type.
Feels a lot clearer to use "media type" for the bundle entity, and use "plugin" or "handler" for what is currently the media type (So "@MediaHandler" then?).

tkoleary’s picture

@bojanz yeah, so I'd suggest:

Media bundle => 'Media type'
Provider/plugin => 'Media handler'
Media entity => 'Media item'

As I suggested above, we don't need to say 'Media item' every time there'a an action (Delete media item), just 'Delete' is fine, that's what we do with other entities. We can remove nouns even in other contexts like 'Delete media configuration' which can just be 'delete configuration'.

Also when we return a DSM we can shorten it to 'item' eg. 'The item was deleted'.

It's important to remember that the word 'Media' is plural (it's the plural of Medium, though it is now also used as the plural for 'a bunch of things created in a variety of mediums [sic]'). For that reason we should never use it in the singular eg. 'Edit the media'. This is why I'm suggesting 'Media item', but also why the tab 'Media' is ok (you are going to a page with a number of media items), as is '+Add media' (you may be adding more than one piece of media).

In addition to all the strings where 'Media', 'Media bundle', 'Media entity', or 'Provider' need to be changed there are also a bunch of stray outliers like:

'Allows for media assets to be created and used on the site.' - should be 'media items'
'The user ID of the media publisher.' -should be '...of the author of the media item'
'The following media translations will be deleted' -here we can dispense with media I think and just say 'the following translations...'

And I'm still looking for other instances.

One other thing:

Strings that say 'The date that' or 'The time that the media item was edited/updated/deleted' don't need 'that'. Just 'The date the media item was...' is less wordy and perfectly good grammar.

Bojhan’s picture

@tkoleary Thanks, item is indeed correct as it aligns with how we name content - which we use content item for the singular.

catch’s picture

Haven't reviewed the whole patch yet but a couple of things:

  1. - there's no migration destination here - do we need a follow-up to add one?
  2. Similarly, if we're planning to deprecate the 'raw' image and file fields, we should also deprecate the file/imagefield destinations and switch to migrating into media fields instead.
  3. +++ b/core/modules/media_entity/media_entity.install
    @@ -0,0 +1,26 @@
    +
    +/**
    + * Implements hook_update_last_removed().
    + *
    + * Media entity module lived in contrib before being adopted by core. 8003 was
    + * last schema version before that happened and this should make sure existing
    + * sites update to last contrib before using core version of the module.
    + */
    +function media_entity_update_last_removed() {
    +  return 8003;
    +}
    

    Has this been manually tested? Looks fine in theory but don't think we've done this before in the same way.

  4. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,83 @@
    +
    +  $suggestions[] = 'media__' . $sanitized_view_mode;
    +  $suggestions[] = 'media__' . $media->bundle();
    

    These conflict. Do we have to add them here? Or could we remove them and put them back once https://www.drupal.org/node/2831144 is done?

  5. +++ b/core/modules/media_entity/media_entity.install
    @@ -0,0 +1,26 @@
    +  $source = drupal_get_path('module', 'media_entity') . '/images/icons';
    

    Can use __DIR__.

    Also is it even possible for this to be different each time? Maybe with a $conf override?

slashrsm’s picture

- #81 implemented
- "bundles" renamed to "types" in UI
- "types" renamed to "handlers" in UI
- Bundle form: field mapping is now updated when handler is changed and hidden if no metadata fields are available for mapping
- Bundle form: field mapping and handler configuration fieldsets are now hidden until a handler is selected
- Removed "Generic" plugin. Since we have no plugins now it is impossible to test. We could merge #2831937: Add "Image" MediaSource plugin into this patch. Another option is to enable "media_entity_test_handler" test module that provides dummy handler for test purposes.

We still need to decide how field mapping should look. See this video by @yoroy and my comment: https://docs.google.com/document/d/1BnkA4fXZWG1nC4ipyl90BSvnE6Buqyjuz4gT...

#84-1: Not sure. It is possible that default content entity destination will work. We would have to research this a bit. Do this in follow-up and create new plugin if we identify it is needed?
#84-2: We are planning to do that, but not yet. Media entity is just a first step. More things (handlers, configuration, field widgets, ...) will follow. I'd officially deprecate them when all this is ready and we're just about to start using new system in standard profile.

tkoleary’s picture

We could merge #2831937: Add "Image" media type plugin into this patch.

+1, It's really not an MVP without at least one plugin.

slashrsm’s picture

It's really not an MVP without at least one plugin.

That's not even a question. Question is if we want to make this patch even bigger or do that as part of: #2831937: Add "Image" MediaSource plugin.

yoroy’s picture

The MVP is what we aim to have at 8.3 release time. There can be many commits towards that. So lets take it step by step and work on #2831937: Add "Image" MediaSource plugin when this is in place. Lets have more than one moment to celebrate progress :-)

slashrsm’s picture

Improved underlying mapping logic to eventually allow us to auto-create fields and tried to made it more extensible along the way.

Sam152’s picture

Should thumbnails be part of the media type interface? It doesn't get satisfied for document, audio, tweet etc. Would a "preview" view mode be a more generic, non-assuming concept to use for the same purpose. It would take a lot of complexity out of the module.

slashrsm’s picture

This is rebase on top of #2625854: Provide default source_field when creating new media entity bundles, which should solve one of the biggest UX problems for site builders.

It affects handler plugins which now need to:
- declare allowed fields in an annotation
- implement createSourceFieldStorage() and createSourceField()

slashrsm’s picture

Status: Needs work » Needs review
FileSize
181.53 KB
9.52 KB
Gábor Hojtsy’s picture

Issue summary: View changes
Issue tags: -Needs usability review

Created a placeholder docs page for media module at https://www.drupal.org/docs/8/core/modules/media (and an overview page under this), similar to how other proposed core modules are listed (eg. trash and workspaces). Links to docs on the help screen should be updated to point to this new docs and not a 3rd party site.


Based on the discussion at the UX meeting yesterday (as suggested by webchick) and after careful consideration, we decided to go with the "media" module namespace.

Also taking off the usability review tag, since we have that. See video at https://www.youtube.com/watch?v=3yZat7zXfoE

Updated issue summary.

phenaproxima’s picture

Among other things, this patch renames Media Entity to Media. Have a nice day!

My feelings on this: http://img.michaeljacksonspictures.com/wp-content/uploads/2014/08/popcor...

Gábor Hojtsy’s picture

Let's unify the base field naming to be more consistent and user friendly. Comparing to nodes:

For example where we take the username for the author, the UI says we want an ID :) These are the green ones. (But there are some other inconsistencies with revision authors, so I would just propose to do a sweep of all base fields).

Then I added the orange stuff. I don't think the description for the name field provides any additional info whatsoever. And the title of it should be "Name" because we are already on the media item form.

Gábor Hojtsy’s picture

slashrsm’s picture

  1. +++ b/core/modules/media/config/schema/media.views.schema.yml
    similarity index 100%
    rename from core/modules/media_entity/js/media_bundle_form.js
    
    rename from core/modules/media_entity/js/media_bundle_form.js
    rename to core/modules/media/js/media_bundle_form.js
    

    Rename this too?

  2. Rename issue nitpiks:

  3. +++ b/core/modules/media/media.install
    @@ -0,0 +1,15 @@
    +function media_install() {
    +  $source = drupal_get_path('module', 'media') . '/images/icons';
    +  $destination = \Drupal::config('media.settings')->get('icon_base');
    +  media_copy_icons($source, $destination);
    +}
    

    How do existing images relate to this.

dawehner’s picture

It would be great to address #62 as well :)

  1. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -154,14 +154,9 @@ public function getType() {
    +    // Try to set fields provided by the handler and mapped in bundle config.
         foreach ($this->bundle->entity->getFieldMap() as $source_field => $destination_field) {
    -      // Only save value in entity field if empty. Do not overwrite existing
    -      // data.
    -      if ($this->hasField($destination_field) && $this->{$destination_field}->isEmpty() && ($value = $this->getType()->getField($this, $source_field))) {
    -        $this->set($destination_field, $value);
    -      }
    +      $this->getType()->mapFieldValue($this, $source_field, $destination_field);
         }
    

    Its good to put that onto the type itself

  2. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -215,10 +215,14 @@ public function form(array $form, FormStateInterface $form_state) {
    +      foreach ($handler->providedFields() as $field_name => $field_definition) {
    +        if (!is_array($field_definition)) {
    +          $field_definition = ['label' => $field_definition];
    +        }
    

    What about checking for the interface instead. It is a bit easier to read in that case

  3. +++ b/core/modules/media_entity/src/MediaTypeBase.php
    @@ -157,4 +157,15 @@ public function getDefaultName(MediaInterface $media) {
    +    if ($media->hasField($destination_field) && $media->{$destination_field}->isEmpty() && ($value = $this->getField($media, $source_field))) {
    

    I prefer to use $media->get($destination_field) as that's way less magic and no {} involved

  1. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,83 @@
    +function media_entity_help($route_name, RouteMatchInterface $route_match) {
    +  switch ($route_name) {
    +    case 'help.page.media_entity':
    +      $output = '<h3>' . t('About') . '</h3>';
    +      $output .= '<p>' . t('The <a href=":media_entity_url">Media entity</a> module provides a "base" entity for media. This is a very basic entity which can reference to all kinds of media-objects (local files, YouTube videos, Tweets, Instagram photos, ...). Media entity provides a relation between your website and the media resource. You can reference to/use this entity within any other entity on your site. For more information, see the <a href=":media_entity_handbook">online documentation for the Media entity module</a>.',
    +          [
    +            ':media_entity_url' => 'https://www.drupal.org/project/media_entity',
    +            ':media_entity_handbook' => 'https://drupal-media.gitbooks.io/drupal8-guide/content/modules/media_entity/intro.html',
    +          ]) . '</p>';
    +      $output .= '<h3>' . t('Uses') . '</h3>';
    +      $output .= '<p>' . t('For detailed information about the usage of this module please refer to <a href=":media_entity_handbook">the official documentation</a>.',
    +          [
    +            ':media_entity_handbook' => 'https://drupal-media.gitbooks.io/drupal8-guide/content/modules/media_entity/intro.html',
    +          ]) . '</p>';
    +
    +      return $output;
    +  }
    +}
    

    I'll try to write a better hook_help() at some point

  2. +++ b/core/modules/media_entity/media_entity.module
    @@ -0,0 +1,83 @@
    +    throw new Exception("Unable to create directory $destination.");
    ...
    +      throw new Exception("Unable to copy {$file->uri} to $destination.");
    

    That feels like a legitimate usecase of \RuntimeException

  3. +++ b/core/modules/media_entity/media_entity.permissions.yml
    @@ -0,0 +1,18 @@
    +delete any media:
    +  title:  'Delete any media'
    

    This should I think better to a restrict access permission.

  4. +++ b/core/modules/media_entity/media_entity.theme.inc
    @@ -0,0 +1,38 @@
    +use Drupal\Core\Link;
    

    Nitpick: this is unused

  5. +++ b/core/modules/media_entity/media_entity.tokens.inc
    @@ -0,0 +1,180 @@
    +        case 'name':
    +          $replacements[$original] = $media->name->value;
    +          break;
    

    Should we have a dedicated method for the name, much like there is $node->getTitle()

  6. +++ b/core/modules/media_entity/src/Entity/Media.php
    @@ -0,0 +1,434 @@
    +      ->setDefaultValueCallback('Drupal\media_entity\Entity\Media::getCurrentUserId')
    ...
    +      ->setDefaultValueCallback(static::class . '::getCurrentUserId')
    

    Let's make that consistent

  7. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,235 @@
    + *       "delete" = "Drupal\media_entity\Form\MediaBundleDeleteConfirm"
    

    NOte: A form should end with Form, shouldn't it?

  8. +++ b/core/modules/media_entity/src/Entity/MediaBundle.php
    @@ -0,0 +1,235 @@
    +  public $field_map = [];
    

    why is this public?

  9. +++ b/core/modules/media_entity/src/Form/MediaBundleDeleteConfirm.php
    @@ -0,0 +1,62 @@
    +    if ($num_entities) {
    +      $caption = '<p>' . $this->formatPlural($num_entities,
    +          '%type is used by @count piece of content on your site. You can not remove this content type until you have removed all of the %type content.',
    +          '%type is used by @count pieces of content on your site. You may not remove %type until you have removed all of the %type content.',
    +          ['%type' => $this->entity->label()]) . '</p>';
    +      $form['#title'] = $this->getQuestion();
    +      $form['description'] = ['#markup' => $caption];
    

    This <p> tags look weird, maybe you could use inline_template

  10. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,390 @@
    +      $skipped_fields = [
    +        'mid',
    +        'uuid',
    +        'vid',
    +        'bundle',
    +        'langcode',
    +        'default_langcode',
    +        'uid',
    ...
    +        'revision_log',
    +        'revision_uid',
    +      ];
    +      $options = ['_none' => $this->t('- Skip field -')];
    +      foreach ($this->entityFieldManager->getFieldDefinitions('media', $bundle->id()) as $field_name => $field) {
    +        if (!in_array($field_name, $skipped_fields)) {
    +          $options[$field_name] = $field->getLabel();
    

    Can't we filter by FieldConfigInterface or so? Aren't these simple the base fields?

  11. +++ b/core/modules/media_entity/src/MediaBundleForm.php
    @@ -0,0 +1,390 @@
    +    catch (PluginNotFoundException $e) {}
    

    It is confusing that we just catch the exception, this sounds like trying to hide problems in the future.

  12. +++ b/core/modules/media_entity/src/MediaBundleListBuilder.php
    @@ -0,0 +1,51 @@
    +    $row['description'] = Xss::filterAdmin($entity->getDescription());
    

    NoteTypeListBuilder uses $row['description']['data'] = ['#markup' => $entity->getDescription()];

  13. +++ b/core/modules/media_entity/tests/modules/media_entity_test_handler/src/Plugin/MediaEntity/Type/Test.php
    @@ -0,0 +1,67 @@
    +use Drupal\media_entity\Plugin\MediaEntity\Type\Generic;
    ...
    +class Test extends MediaTypeBase {
    

    Generic no longer exists :)

  14. +++ b/core/modules/media_entity/tests/src/Functional/MediaBulkFormTest.php
    @@ -0,0 +1,118 @@
    +class MediaBulkFormTest extends MediaEntityFunctionalTestBase {
    

    That's a serious testing effort

  15. +++ b/core/modules/media_entity/tests/src/Kernel/BasicCreationTest.php
    @@ -0,0 +1,115 @@
    +class BasicCreationTest extends KernelTestBase {
    

    Note: All assertion methods have the expectation and the actual value in the wrong order, its assertion($expected, $actual)

  16. +++ b/core/modules/media_entity/tests/src/Kernel/BasicCreationTest.php
    @@ -0,0 +1,115 @@
    +
    +
    

    Ubernitpick: two empty lines :)

Gábor Hojtsy’s picture

Here is a massive help text update. Part of this was reviewed in the UX google doc but I made some further reorganization on putting this together to explain the media handlers and how they relate to types and fields. So the basic concepts are pinned down. Otherwise the extent/approach of the help is based off of node module.

No other changes.

slashrsm’s picture

Status: Needs work » Needs review
FileSize
177.86 KB
17.37 KB

#101, #103 and #104 addressed.

xjm’s picture

After Dublin, I had suggested we add Media as a beta experimental module, thus communicating the expectations that the API and data model should remain stable, but that we might need to do some work in followups. Otherwise we have to be a lot more stringent in reviewing this.

slashrsm’s picture

#62 and few other things addressed.

Gábor Hojtsy’s picture

@xjm that is quite different from what the core management team approved on https://www.drupal.org/node/2786785/revisions/view/10181694/10191559 (this is the edit webchick made while on the meeting). The only way to add it as a beta is to add it as an experimental. We definitely cannot build anything on top of that then for the standard profile (eg. default media types would need to be with the media module instead of the standard profile unlike eg. node types), or even start deprecating file fields or direct CKEditor file uploads. All of those would need to live in some experimental module then and later moved to other places in core which may cause problems for people who try them out and will be more pain to support with upgrade paths.

dawehner’s picture

Issue summary: View changes

It would be great, especially for additional follow ups, which I work on atm. to have a media component: #2835817: Add a media component to the core issue component field
Not sure who to ask though to be honest.

xjm’s picture

I discussed with @Gábor Hojtsy that the signoff he mentions above were only on the high-level idea, not the detailed plan, which I was not given opportunity to sign off on subsequently. It's not a big concern though because it is a one-line change to turn an experimental module into a stable one or vice versa. I'm definitely not proposing moving the code around.

We definitely cannot build anything on top of that then for the standard profile (eg. default media types would need to be with the media module instead of the standard profile unlike eg. node types), or even start deprecating file fields or direct CKEditor file uploads

Is there the expectation that those things should happen in this initial patch?

mtodor’s picture

Changed default source field creation in MediaHandlerBase. Removed 2 abstract methods -> and provided only one, that should return type of default source field.

naveenvalecha’s picture

Component: entity system » media system

@Gabor, help text looks good.
As #2835817: Add a media component to the core issue component field has been done, Is "media system" component change fine ?

slashrsm’s picture

It's not a big concern though because it is a one-line change to turn an experimental module into a stable one or vice versa.

It may be a simple change but it can have significant influence on many things :).

Is there the expectation that those things should happen in this initial patch?

Oh, no! Please! :) Plan for this patch is more or less to do what it already does. We are not planning to introduce any significant changes. At this point it needs feedback to see if there are any missing pieces that people from outside of the Media initiative would bring up and can not be done in follow-ups.

Things that @Gábor Hojtsy brought up will follow after this gets in/are already in progress in other issues (as per the plan aligned in #2825215: Media initiative: Essentials - first round of improvements in core and #2801277: [META] Support remote media assets as first-class citizens by adding Media Entity module in core). The thing is that a lot of those steps depend on Media being stable and not achieving that would make the plan much harder to realize.

tim.plunkett’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -0,0 +1,64 @@
    +      type: media.handler.[%parent.handler]
    

    This needs a corresponding media.handler.*

  2. +++ b/core/modules/media/media.info.yml
    @@ -0,0 +1,10 @@
    +  - user
    +  - system
    

    This is a little odd, the only module that declares an explicit dependency on system is user.module... But I guess it's not wrong :)

  3. +++ b/core/modules/media/media.links.action.yml
    @@ -0,0 +1,10 @@
    +media.bundle_add:
    +  route_name: entity.media_type.add_form
    +  title: 'Add media type'
    +  appears_on:
    +    - entity.media_type.collection
    +
    +media.add:
    +  route_name: entity.media.add_page
    +  title: 'Add media'
    +  weight: 10
    
    +++ b/core/modules/media/media.links.contextual.yml
    @@ -0,0 +1,9 @@
    +entity.media.edit_form:
    +  route_name: entity.media.edit_form
    +  group: media
    +  title: Edit
    +entity.media.delete_form:
    +  route_name: entity.media.delete_form
    +  group: media
    +  title: Delete
    +  weight: 10
    

    Half of the yml files have a blank line between entries, might as well pick one

  4. +++ b/core/modules/media/media.module
    @@ -0,0 +1,84 @@
    + * Copy the media file icons to files directory for use with image styles.
    

    Copies

  5. +++ b/core/modules/media/media.module
    @@ -0,0 +1,84 @@
    + * @throws Exception
    

    @throws \RuntimeException

  6. +++ b/core/modules/media/media.module
    @@ -0,0 +1,84 @@
    +    throw new RuntimeException("Unable to create directory $destination.");
    ...
    +      throw new RuntimeException("Unable to copy {$file->uri} to $destination.");
    

    Haven't written a procedural function like this in a while, but usually we explicitly have \RuntimeException

  7. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,179 @@
    + * Builds placeholder replacement tokens for media_entity-related data.
    

    media_entity or media?

  8. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,179 @@
    +          /** @var \Drupal\user\UserInterface $account */
    

    Should be unnecessary since $media is typehinted above

  9. +++ b/core/modules/media/src/Annotation/MediaHandler.php
    @@ -0,0 +1,50 @@
    + * Defines a media handler plugin annotation object.
    + *
    + * @see hook_media_handler_info_alter()
    + *
    + * @Annotation
    + */
    

    This docblock needs expansion. See \Drupal\Core\Display\Annotation\DisplayVariant for an example, but it should @see the interface, base class, and manager, should describe the plugin namespace, and some working examples.

  10. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,382 @@
    + *       "default" = "Drupal\media\MediaForm",
    ...
    + *       "edit" = "Drupal\media\MediaForm"
    
    +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    + *       "add" = "Drupal\media\MediaTypeForm",
    + *       "edit" = "Drupal\media\MediaTypeForm",
    

    Please do not use 'default' as a form operation.

  11. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,382 @@
    +      ));
    +
    +
    +    return $fields;
    

    Extra blank line

  12. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,382 @@
    +   * @return array
    

    string[] or int[]?

  13. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    + *   entity_keys = {
    + *     "id" = "id",
    + *     "label" = "label"
    + *   },
    ...
    + *     "status",
    

    Status is usually an entity key as well.

  14. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    + *     "third_party_settings",
    

    Does not need to be specified here.

  15. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    + *   links = {
    + *     "add-form" = "/admin/structure/media/add",
    + *     "edit-form" = "/admin/structure/media/manage/{media_type}",
    + *     "delete-form" = "/admin/structure/media/manage/{media_type}/delete",
    + *     "collection" = "/admin/structure/media",
    + *   }
    + * )
    

    The links section should have a trailing comma

  16. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    +   * The handler plugin id.
    

    ID

  17. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    +   * Are thumbnail downloads queued.
    

    This is a question with no question mark, please rephrase

  18. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    +   * Lazy collection of handler plugin(s).
    

    This uses DefaultSingle, so it's just one

  19. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    +  public function getHandlerConfiguration() {
    +    return $this->handler_configuration;
    +  }
    

    Why would this need to be exposed? And the implementation must be return $this->getHandler()->getConfiguration() if it is kept.

  20. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    +   */
    +  public function setHandlerConfiguration(array $configuration) {
    +    $this->handler_configuration = $configuration;
    +    $this->handlerPluginCollection = NULL;
    +  }
    ...
    +    parent::preSave($storage);
    ...
    +      $configuration = $handler->getConfiguration();
    +      $configuration['source_field'] = $storage->getName();
    +      $this->setHandlerConfiguration($configuration);
    

    ConfigEntityBase::preSave() loops through the plugin collections and updates the entity with the correct values.
    Then this will do it again, always adding in 'source_field'? Why isn't that handled (hah) by the plugin itself?

    Also, I disagree with exposing setHandlerConfiguration as it's own method, and then blowing away the collection within it.
    If you keep the method, it should be as shorthand for $media_type->getHandler()->setConfiguration().

  21. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,306 @@
    +  public function setHandlerConfiguration(array $configuration) {
    ...
    +  public function setQueueThumbnailDownloads($queue_thumbnail_downloads) {
    ...
    +  public function setNewRevision($new_revision) {
    

    Half of the setters are fluent (return $this), we should be consistent.

  22. +++ b/core/modules/media/src/Form/DeleteMultiple.php
    @@ -0,0 +1,199 @@
    +    $form = parent::buildForm($form, $form_state);
    +
    +    return $form;
    

    Nit: can combine these

  23. +++ b/core/modules/media/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    +    $is_owner = ($account->id() && $account->id() == $entity->getOwnerId()) ? TRUE : FALSE;
    

    Don't need the ? TRUE : FALSE part

  24. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,186 @@
    +class MediaForm extends ContentEntityForm {
    ...
    +      $this->entity->setCreatedTime(\Drupal::time()->getRequestTime());
    

    Inject this service

  25. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,186 @@
    +  protected function actions(array $form, FormStateInterface $form_state) {
    

    Going to just not read this whole method O_O

  26. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,186 @@
    +    if (isset($element['#published_status'])) {
    +      if ((bool) $element['#published_status']) {
    +        $media->setPublished();
    +      }
    +      else {
    +        $media->setUnpublished();
    +      }
    +    }
    

    if (!empty($element['#published_status'])) { is just as good. Also that (bool) cast is redundant.

  27. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,186 @@
    +      if ((bool) $element['#published_status']) {
    

    AFAIK the cast is redundant

  28. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,186 @@
    +  public function save(array $form, FormStateInterface $form_state) {
    +    $insert = $this->entity->isNew();
    +    $this->entity->save();
    

    \Drupal\Core\Entity\EntityFormInterface::save() has a return value!

    $return = parent::save($form, $form_state);
    $insert = $return === SAVED_NEW;
    ...
    return $return;
    
  29. +++ b/core/modules/media/src/MediaHandlerBase.php
    @@ -0,0 +1,279 @@
    +  public function attachConstraints(MediaInterface $media) {}
    ...
    +  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {}
    ...
    +  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {}
    

    Put } on a blank line please

  30. +++ b/core/modules/media/src/MediaHandlerBase.php
    @@ -0,0 +1,279 @@
    +        '#title' => $this->t('Field with source information.'),
    

    #title strings shouldn't end with any punctuation

  31. +++ b/core/modules/media/src/MediaHandlerBase.php
    @@ -0,0 +1,279 @@
    +        '#empty_value' => NULL,
    

    Curious why this is needed. If it's really important, please a line of documentation above it.

  32. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,103 @@
    +/**
    + * Defines the interface for media handler.
    + */
    +interface MediaHandlerInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {
    

    As mentioned before with the annotation, this should @see a bunch of stuff

  33. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,103 @@
    +  public function providedFields();
    

    Please rename to getProvidedFields()

  34. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,103 @@
    +   * @param MediaInterface $media
    

    Missing \Drupal\media\

  35. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,103 @@
    +   * Media handler plugin is responsible for returning URI of the generic thumbnail
    +   * if no other is available. This function should always return a valid URI.
    

    Needs rewrapping to 80 chars

  36. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,103 @@
    +   *   The media entity.
    ...
    +   *   Media entity object.
    

    This should be consistent

  37. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,103 @@
    +  public function thumbnail(MediaInterface $media);
    ...
    +  public function getDefaultThumbnail();
    

    These look odd together, but at least rename the first to getThumbnail()

  38. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,103 @@
    +  public function mapFieldValue(MediaInterface $media, $source_field, $destination_field);
    +}
    

    Missing blank line

  39. +++ b/core/modules/media/src/MediaHandlerManager.php
    @@ -0,0 +1,32 @@
    +    parent::__construct('Plugin/media/Handler', $namespaces, $module_handler, 'Drupal\media\MediaHandlerInterface', 'Drupal\media\Annotation\MediaHandler');
    

    Can use the ::class version of these to be all modern

  40. +++ b/core/modules/media/src/MediaThumbnailHandler.php
    @@ -0,0 +1,66 @@
    + * Provides an interface defining a media thumbnail service.
    ...
    +class MediaThumbnailHandler implements MediaThumbnailHandlerInterface {
    

    Not an interface

  41. +++ b/core/modules/media/src/MediaThumbnailHandler.php
    @@ -0,0 +1,66 @@
    +    $this->entityTypeManager = $entity_type_manager;
    ...
    +    /** @var \Drupal\Core\Entity\EntityStorageInterface $file_storage */
    +    $file_storage = $this->entityTypeManager->getStorage('file');
    

    Instead of storing the entityTypeManager, do this retrieval in __construct. Also typehint by \Drupal\file\FileStorageInterface

  42. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +  protected $configurableInstances = [];
    

    Without reading further: why is the form storing the instances? Seems like it's just asking for serialization problems

  43. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +   * @param array $form
    +   *   The form array.
    +   * @param \Drupal\Core\Form\FormStateInterface $form_state
    +   *   Current form state.
    +   *
    +   * @return \Drupal\Core\Ajax\AjaxResponse
    +   *   The ajax response.
    

    All of this isn't needed for a callback

  44. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +   *   The ajax response.
    

    Ajax

  45. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    /** @var \Drupal\media\MediaTypeInterface $bundle */
    +    $form['#entity'] = $bundle = $this->getEntity();
    

    Please do not put it in $form.
    Also, just use $this->entity throughout, and then you don't need the @var

  46. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $handler = NULL;
    +    if ($bundle->get('handler')) {
    +      // Handler is not set when the entity is initially created.
    +      $handler = $bundle->getHandler();
    +      $this->configurableInstances[$handler]['plugin_config'] = empty($configuration[$handler]) ? [] : $configuration[$handler];
    +    }
    

    Very unclear why this is needed. Also that ternary seems backwards.

  47. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    if ($this->operation == 'add') {
    

    No reason not to use ===. Also, yuck this is why I prefer to use individual classes for each operation (with a base class)

  48. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    elseif ($this->operation == 'edit') {
    +    }
    

    Remove

  49. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +      '#size' => 30,
    ...
    +      '#maxlength' => 32,
    

    No problem here, just not used to seeing these specified

  50. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +      '#weight' => -100,
    ...
    +      '#weight' => -90,
    ...
    +      '#weight' => -80,
    ...
    +      '#weight' => -70,
    ...
    +        '#weight' => -60,
    ...
    +      '#weight' => -50,
    

    Why specify these? Seems like overkill.

  51. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +        'source' => ['label'],
    

    This can be omitted, that's the default

  52. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $plugins = $this->handlerManager->getDefinitions();
    ...
    +    foreach ($plugins as $plugin => $definition) {
    +      $options[$plugin] = $definition['label'];
    

    $plugin_id for clarity, also are you sure you don't want getSortedDefinitions here?

  53. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +      $handler_configuration = empty($this->configurableInstances[$handler->getPluginId()]['plugin_config']) ? $bundle->getHandlerConfiguration() : $this->configurableInstances[$plugin->getPluginId()]['plugin_config'];
    +      /** @var \Drupal\media\MediaHandlerBase $instance */
    +      $instance = $this->handlerManager->createInstance($handler->getPluginId(), $handler_configuration);
    +      // Store the configuration for validate and submit handlers.
    +      $this->configurableInstances[$handler->getPluginId()]['plugin_config'] = $handler_configuration;
    

    Oh my. Still no docs on why the instances are being stored.

  54. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +      $form['handler_dependent']['handler_configuration'][$handler->getPluginId()] = $instance->buildConfigurationForm(['#type' => 'container'], $form_state);
    

    Should use SubformState, see \Drupal\block\BlockForm::form()

  55. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +      'description' => [
    +        '#type' => 'html_tag',
    +        '#tag' => 'p',
    +        '#value' => $this->t('Media handlers can provide metadata fields such as title, caption, size information, credits, etc. Media can automatically save this metadata information to entity fields, which can be configured below. Information will only be mapped if the entity field is empty.'),
    

    Why is the

    important? And other places usually the

    is just concatenated onto the translated string.

  56. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +        if (!($field instanceof BaseFieldDefinition) || $field_name == 'name') {
    

    Is there an interface to check here instead?

  57. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +        // This is a BC layer. In the past this function returned string and now
    +        // it returns arrays.
    

    Needs an @todo or something

  58. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $workflow_options = [
    +      'status' => $bundle->getStatus(),
    +      'new_revision' => $bundle->shouldCreateNewRevision(),
    +      'queue_thumbnail_downloads' => $bundle->getQueueThumbnailDownloads(),
    +    ];
    ...
    +    $keys = array_keys(array_filter($workflow_options));
    +    $workflow_options = array_combine($keys, $keys);
    

    Seems a bit odd. Maybe a small protected method getWorkflowOptions($bundle)?

  59. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $plugin = $this->entity->getHandler()->getPluginId();
    +    $plugin_configuration = !empty($this->configurableInstances[$plugin]['plugin_config']) ? $this->configurableInstances[$plugin]['plugin_config'] : [];
    +    $instance = $this->handlerManager->createInstance($plugin, $plugin_configuration);
    +    $instance->validateConfigurationForm($form, $form_state);
    

    See note above about SubformState

  60. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $this->entity->setQueueThumbnailDownloads((bool) $form_state->getValue(['options', 'queue_thumbnail_downloads']));
    +    $this->entity->setStatus((bool) $form_state->getValue(['options', 'status']));
    +
    +    $this->entity->setNewRevision((bool) $form_state->getValue(['options', 'new_revision']));
    

    Why not mess with #parents on the options and then let copyFormValuesToEntity() handle these?

  61. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $instance->submitConfigurationForm($form, $form_state);
    

    Once again with SubformState

  62. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $configuration = $form_state->getValue('handler_configuration');
    +
    +    // Store previous plugin config.
    +    $plugin = NULL;
    +    if ($entity->get('handler')) {
    +      // Handler is not set when the entity is initially created.
    +      $plugin = $entity->getHandler()->getPluginId();
    +      $this->configurableInstances[$plugin]['plugin_config'] = empty($configuration[$plugin]) ? [] : $configuration[$plugin];
    +    }
    ...
    +    // Use type configuration for the plugin that was chosen.
    +    $plugin = $entity->getHandler()->getPluginId();
    +    $plugin_configuration = empty($configuration[$plugin]) ? [] : $configuration[$plugin];
    +    $entity->set('handler_configuration', $plugin_configuration);
    

    You shouldn't need any of this code. Once submitConfigurationForm is called, you should let \Drupal\Core\Config\Entity\ConfigEntityBase::preSave() handle this.

  63. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    // Save field mapping.
    +    $entity->set('field_map', array_filter(
    +      $form_state->getValue('field_mapping', []),
    +      function ($item) { return $item != '_none'; }
    +    ));
    

    This should be in submitForm, before calling parent::, and should directly manipulate the form_state values.

  64. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +  public function save(array $form, FormStateInterface $form_state) {
    

    Same note as the last form, you might as well call parent::save, and you should return the result at the end

  65. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    if ($status == SAVED_UPDATED) {
    

    Yep, this is what needs to be done in the other form

  66. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    $value = (bool) $form_state->getValue(['options', 'status']);
    +    if ($media->status->value != $value) {
    

    Either cast it and then use strict comparison, or don't.

  67. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,90 @@
    +   *   The Media entity.
    

    This is the third or fourth way I've seen it (media entity, Media entity, media entity object, etc)

  68. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,90 @@
    +  /**
    +   * Returns whether thumbnail downloads are queued.
    +   *
    +   * @return bool
    +   *   Returns download now or later.
    +   */
    +  public function getQueueThumbnailDownloads();
    

    I don't know which now or later is, TRUE or FALSE. If you want to keep it as a Boolean, maybe getQueueThumbnailDownloadsNow()? Not much better.

  69. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,90 @@
    +  public function getHandler();
    ...
    +  public function getHandlerConfiguration();
    ...
    +  public function setHandlerConfiguration(array $configuration);
    

    Called this out above, but I don't think the configuration should be directly accessible.

  70. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,90 @@
    +  public function getStatus();
    

    \Drupal\Core\Config\Entity\ConfigEntityInterface::status() already exists

  71. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,90 @@
    +   * Sets whether a new revision should be created by default.
    

    A new revision of what? Not the media type itself, but media entities of this media type?

  72. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,90 @@
    +   * Returns the metadata field map.
    

    Can a longer explanation of what this is for be added here?

  73. +++ b/core/modules/media/src/MediaTypeListBuilder.php
    @@ -0,0 +1,51 @@
    +    $row['description']['data'] = ['#markup' => Xss::filterAdmin($entity->getDescription())];
    

    It's my understanding that filtering here isn't needed.

  74. +++ b/core/modules/media/src/Plugin/Action/DeleteMedia.php
    @@ -0,0 +1,98 @@
    + *   confirm_form_route_name = "entity.media.multiple_delete_confirm"
    

    Can have a trailing comma to reduce noisy diffs for future additions

  75. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,188 @@
    +    $media = $this->getEntitiesToView($items, $langcode);
    ...
    +    /** @var \Drupal\media\MediaInterface $media_item */
    

    This is fine here, but you could also typehint $media as MediaInterface[]

  76. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestTrait.php
    @@ -0,0 +1,50 @@
    + * @package Drupal\Tests\media\Functional
    

    Not sure we use @package?

  77. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestTrait.php
    @@ -0,0 +1,50 @@
    +   * @return \Drupal\Core\Entity\EntityInterface
    
    +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,165 @@
    +    /** @var \Drupal\media\MediaTypeInterface $bundle */
    

    If you fix the @return, then you don't need to use @var later

  78. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestTrait.php
    @@ -0,0 +1,50 @@
    +  protected function drupalCreateMediaBundle(array $values = [], $type_name = 'test') {
    

    All of the drupalCreate* methods were renamed to create* and then imported with aliases like createNode as drupalCreateNode, this should follow that pattern.

  79. +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,165 @@
    +    /** @var \Drupal\media\MediaInterface $media */
    +    $media = $this->container->get('entity_type.manager')
    +      ->getStorage('media')
    +      ->loadUnchanged($media_id);
    

    Could be worth a protected method to do this, then you can codify the use of loadUnchanged and remove the need for the extra @var

  80. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -0,0 +1,158 @@
    +    $bundle_exists = (bool) $bundle_storage->load($this->testBundle->id());
    +    $this->assertTrue($bundle_exists, 'The new media type has not been correctly created in the database.');
    

    Instead of casting it to a Boolean and using assertTrue, try $this->assertInstanceOf(MediaTypeInterface::class, $bundle); (or assertNotInstanceOf in the opposite case)

    Side note, this assertion method seems to disagree with the assertion being made?

  81. +++ b/core/modules/media/tests/src/Kernel/TokensTest.php
    @@ -0,0 +1,76 @@
    +  /**
    +   * Modules to install.
    +   *
    +   * @var array
    +   */
    

    {@inheritdoc}

xjm’s picture

@slashrsm, so my suggestion of having it beta initially could be for a very short period of time, and there would still be a strong chance of it being stable by 8.3.0.

The requirement is that it be stable before we make changes to stable core or contrib, right? So such issues are definitely blocked on the module being stable, but this initial commit itself does not necessarily need to be blocked on it being stable.

It's just an option to make iterative progress and allow us to do things in followups rather than all in the initial patch; beta still means we expect a stable API and stable data model. So the difference is mostly about followups. I'll make a recommendation once I have the chance to review an RTBC version of this patch in full. Seems like it is making really fast progress!

effulgentsia’s picture

I'll have more comments on this issue later, but just a quick drive-by:

beta still means we expect a stable API

A key difference though is expect is different than commit to. As an example, big_pipe module was marked as beta for 8.2.0, and just today, in #2835604: BigPipe provides functionality, not an API: mark all classes & interfaces @internal we changed its public API to @internal, in anticipation of breaking it in #2657684: Refactor BigPipe internals to allow a contrib module to extend BigPipe with the ability to stream anonymous responses and prime Page Cache for subsequent visits. This highlights that there's both value in making/keeping something experimental until potentially API-changing follow-ups are done, and risk to contrib relying on beta-level module APIs.

tim.plunkett’s picture

Issue tags: +Needs tests

Other notes from running PHPStorm's code inspect:

  1. +++ b/core/modules/media/js/media_form.js
    @@ -0,0 +1,40 @@
    +(function ($, Drupal, drupalSettings) {
    
    +++ b/core/modules/media/js/media_type_form.js
    @@ -0,0 +1,45 @@
    +})(jQuery, Drupal);
    

    This neither passes in nor uses drupalSettings

  2. +++ b/core/modules/media/js/media_type_form.js
    @@ -0,0 +1,45 @@
    +(function ($, Drupal) {
    +  'use strict';
    

    Missing blank line

  3. +++ b/core/modules/media/media.module
    @@ -0,0 +1,84 @@
    +      $output .= '<dd>' . t('When a new media item is created, the Media module records basic information about it, including the author, date of creation, and the <a href=":media-type">Media type</a>. It also manages the <em>publishing options</em>, which define whether or not the item is published. Default settings can be configured for each <a href=":media-type">type of media</a> on your site.', [':media-type' => \Drupal::url('entity.media_type.collection')]) . '</dd>';
    

    Here, and elsewhere, use Url::fromRoute() instead of \Drupal::url()

  4. +++ b/core/modules/media/media.theme.inc
    @@ -0,0 +1,29 @@
    +use Drupal\Component\Utility\Html;
    

    Unused

  5. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,179 @@
    +use Drupal\media\Entity\MediaBundle;
    

    Unused

  6. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +use Drupal\Component\Plugin\Exception\PluginNotFoundException;
    ...
    +use Drupal\field\Entity\FieldConfig;
    

    Unused

  7. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +      $handler = $bundle->getHandler();
    +      $this->configurableInstances[$handler]['plugin_config'] = empty($configuration[$handler]) ? [] : $configuration[$handler];
    

    In addition to all of this code being problematic (see last review), \Drupal\media\MediaTypeInterface::getHandler() documented as returning a \Drupal\media\MediaHandlerInterface object, which can't be used as an array key here.
    Which leads me to believe this has no test coverage?

  8. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,392 @@
    +    foreach ($plugins as $plugin => $definition) {
    ...
    +      $handler_configuration = empty($this->configurableInstances[$handler->getPluginId()]['plugin_config']) ? $bundle->getHandlerConfiguration() : $this->configurableInstances[$plugin->getPluginId()]['plugin_config'];
    

    $plugin may or may not be defined after this foreach loop, and it is used below here. This code also is not covered.

  9. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,188 @@
    +  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityStorageInterface $image_style_storage, RendererInterface $renderer) {
    

    $image_style_storage should be typehinted with \Drupal\image\ImageStyleStorageInterface and is missing an @param

  10. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,188 @@
    +      $container->get('entity.manager')->getStorage('image_style'),
    

    Use entity_type.manager

  11. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,188 @@
    +   * This has to be overriden because FileFormatterBase expects $item to be
    

    overridden

  12. +++ b/core/modules/media/tests/modules/media_test_handler/src/Plugin/media/Handler/Test.php
    @@ -0,0 +1,77 @@
    +use Drupal\media\MediaTypeInterface;
    

    Unused

  13. +++ b/core/modules/media/tests/src/Kernel/TokensTest.php
    @@ -0,0 +1,76 @@
    +    $this->assertEquals($media->label(), $replaced_value, 'Token replacement for the media label was sucessful.');
    

    successful

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
179.24 KB
43.95 KB

I took a brave stab at fixing the stuff in #123. Here is what happened:

  1. Question: is it kosher to define media.handler.* as a mapping, but not actually define any of the mapping keys?
  2. Fixed.
  3. Fixed, the way I like it :)
  4. Fixed.
  5. Fixed.
  6. Fixed.
  7. Fixed.
  8. Fixed.
  9. Improved. Hopefully fixed :)
  10. Both Node and User specify a default form handler, so I'm not sure what to do here.
  11. Fixed.
  12. Fixed.
  13. We were hoping not to introduce data model changes, since that complicates the transition from Media Entity. With that in mind, not sure if I should add status as an entity key (even though, being a config entity, it won't change database schema AFAIK).
  14. Fixed.
  15. Fixed.
  16. Fixed.
  17. Fixed.
  18. Fixed.
  19. I'm not equipped to answer this one.
  20. The handler may create its source field storage entity during preSave(). In that case, the field storage must be saved and the configuration must be updated. The reason MediaType is doing that stuff is because it doesn't trust the handler to do it. It's paranoid on purpose (I know because I wrote it). If that's very objectionable, we could probably give that responsibility to $handler->getSourceField(). The idea was to strongly guarantee that the field storage is always saved (if new) and the relevant configuration is always updated.
  21. Fixed.
  22. Fixed.
  23. Fixed.
  24. Fixed. Turns out it's already injected by the parent class.
  25. *nods*
  26. Fixed.
  27. Fixed.
  28. Nice catch. Fixed.
  29. Fixed.
  30. Fixed.
  31. Fixed.
  32. It's not. Fixed.
  33. Fixed.
  34. Fixed.
  35. Fixed.
  36. Fixed.
  37. Fixed.
  38. Fixed.
  39. Fixed.
  40. Right you are. Fixed.
  41. Fixed.
  42. Fixed.
  43. Don't know how to respond to this.
  44. Fixed.
  45. No longer relevant.
  46. Fixed.
  47. I'm not in a position to respond to this.
  48. I don't see why we should use === here. We're not using it anywhere else...?
  49. Fixed.
  50. OK.
  51. It might have been a conscious UX-related decision. I'll leave this be for now.
  52. Fixed.
  53. Fixed $plugin_id, but according to PHPStorm, $this->handlerManager doesn't have getSortedDefinitions(). Leaving alone for now.
  54. Leaving this for someone who knows the internals better than I do.
  55. Leaving for now, because I'm not comfortable enough with SubformState or the inner workings of this thing to mess with it.
  56. Fixed.
  57. There doesn't appear to be one.
  58. We've already gone ahead and thrown BC to the lions, so we might not need this anymore. Leaving this pending discussion with @slashrsm.
  59. It is a bit weird, but I'll leave this for @slashrsm to decide.
  60. I'm not yet comfortable enough with SubformState to fix this.
  61. No idea; punting.
  62. Punting once again. :)
  63. Leaving for @slashrsm to evaluate.
  64. I'm not sure I understand what you mean by "directly manipulate the form_state values" in this case.
  65. Fixed.
  66. Yep, already fixed.
  67. Fixed.
  68. Fixed.
  69. Better?
  70. I'd like @slashrsm to weigh in on this.
  71. Fixed.
  72. Fixed.
  73. This is up to @slashrsm.
  74. I don't know for sure, so punting for now. Can someone weigh in?
  75. Fixed.
  76. I've never seen it either. Removed.
  77. Fixed.
  78. Fixed.
  79. Doesn't seem like much ROI there, punting.
  80. Joyfully fixed!
  81. Fixed.

Then I did #127. The scoreboard from that:

  1. Fixed.
  2. Fixed.
  3. Fixed all instances in hook_help().
  4. Fixed.
  5. Fixed.
  6. Punting. This calls for a smarter programmer than myself.
  7. Ditto.
  8. Fixed.
  9. Fixed.
  10. Fixed.
  11. It is used, see createSourceField().
  12. Fixed.

I also incorporated a few of the changes requested in #42 (naming MediaHandlerInterface's getters consistently), and refactored @mtodor's changes in #120; MediaHandlerBase now implements createSourceField(), but it leaves createSourceFieldStorage() abstract so as not to over-abstract the act of creating the field storage. I have an idea for a possible compromise that would also remove the need for an abstract createSourceFieldStorage(), but I'll discuss with him tomorrow.

tim.plunkett’s picture

#128 This is great! I went through and verified each, and below wrote @todo for the ones that still need doing. Most of it is around MediaTypeForm's use of plugins.

Parts from #123
1) Yes exactly, in core.data_types.schema.yml we have

field.storage_settings.*:
  type: mapping
  label: 'Settings'

10) Your change looks good. Default was supposed to be removed in #2006348: Remove default/fallback entity form operation but it was too late, let's not add more

13) It won't break anything, and honestly it won't do anything just yet as it's not being used properly: #2052083: EntityType::getKey() should be used instead of hardcoding id, status, weight, etc.
Best to add it IMO

19) @todo

20) @todo Let's revisit this one, but I believe it needs to change.

24) Oh, that's new! Nice

From here the number got off by 1, your 43 is referring to my 42, keeping my numbering

42) @todo

45) @todo The interdiff shows it being commented out, let's remove it

46) @todo

47) @todo I was pleased to see TRUE as a third param to in_array() elsewhere in the patch. I say default to strict comparisons unless there's a reason otherwise

50) @todo If they weren't in order for some reason, that'd make more sense. But they are.

52) For some reason I thought getSortedDefinitions() was available, but that's only CategorizingPluginManagerInterface which this doesn't use. Worth looking into though

53) @todo

54) @todo

57) @todo

58-63) @todo

68) @todo Docs are way better, method name still very iffy. It does NOT get you the thumbnail downloads, it returns a Boolean

69) @todo

72) @todo

73) @todo

Parts from #127

7) @todo

8) @todo

There are still several "unused import" warnings from PHPStorm, but maybe @alexpott can run his script to fix those later

Berdir’s picture

Small note on 13. The actually meaningful key is now "published", not status, a per #2789315: Create EntityPublishedInterface and use for Node and Comment. We should use that trait now btw.

tim.plunkett’s picture

Even for ConfigEntity? I don't remember any discussion about changing status to published.

Berdir’s picture

Oh, misread that, got confused by the "want to avoid schema changes", that happens when you only read half of it :) Nope, status for config entities is fine, but I think that's actually a pointless left-over anyway? As far as I see, status isn't actually used and is probably a left-over, this really is the "published by default or not" setting like node types have, and is now stored as a field definition override.

tedbow’s picture

Nits

  1. +++ b/core/modules/media/src/Annotation/MediaHandler.php
    @@ -0,0 +1,62 @@
    +  public $allowed_field_types = [];
    

    Class field name should be camel case

  2. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,308 @@
    +          entity_get_form_display($entity_type, $bundle, 'default')
    

    entity_get_form_display is deprecated

  3. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,308 @@
    +          entity_get_display($entity_type, $bundle, 'default')
    

    entity_get_display is deprecated

  4. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,185 @@
    +use Symfony\Component\DependencyInjection\ContainerInterface;
    

    Unused use statement

  5. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,110 @@
    +   * @param $source_field
    +   *   Name of the source metadata field.
    +   * @param $destination_field
    

    Missing parameter type

  6. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,377 @@
    +use Drupal\Component\Plugin\Exception\PluginNotFoundException;
    

    Unused use statement

  7. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,377 @@
    +use Drupal\field\Entity\FieldConfig;
    

    Unused use statement

  8. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,191 @@
    +use Drupal\Core\Entity\EntityStorageInterface;
    

    Unused use statement

  9. +++ b/core/modules/media/src/Plugin/views/wizard/Media.php
    @@ -0,0 +1,84 @@
    +  /**
    +   * Set the created column.
    +   */
    +  protected $createdColumn = 'media_field_data-created';
    +
    +  /**
    +   * Set default values for the filters.
    +   */
    

    Missing @var tag in member variable comment

  10. +++ b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,95 @@
    +  /**
    +   * Set the created column.
    +   */
    +  protected $createdColumn = 'changed';
    +
    +  /**
    +   * Set default values for the filters.
    +   */
    

    Missing @var tag in member variable comments

Errors

When editing as existing media type I got the error:

Warning: Illegal offset type in Drupal\media\MediaTypeForm->form() (line 96 of core/modules/media/src/MediaTypeForm.php).

From

if ($bundle->get('handler')) {
      // Handler is not set when the entity is initially created.
      $handler = $bundle->getHandler();
      $this->configurableInstances[$handler]['plugin_config'] = empty($configuration[$handler]) ? [] : $configuration[$handler];
    }

$handler was equal to an instance of \Drupal\media_test_handler\Plugin\media\Handler\Test(or the other handler).
I see @tim.plunkett commented on this. Also noticed that $configuration not declared before this line so empty($configuration[$handler]) will always be true.
And I think @phenaproxima punted. It looks like it should be $handler->getPluginId().

I am not why this error is not being picked up in MediaUiJavascriptTest it seems to test the edit form. Shouldn't a warning cause a test failure?

Later on at 171 we are setting this again

// Store the configuration for validate and submit handlers.
      $this->configurableInstances[$handler->getPluginId()]['plugin_config'] = $handler_configuration;

The earlier code assigning $this->configurableInstances[$handler]['plugin_config'] will also equal "[]".
I commented out that line and MediaUiJavascriptTest still passes. Which makes me think the only thing that block is doing is setting $handler which has to be there for later if statements

Usability

"Field with source information"

The "Field with source information" on the Media Type edit/add form was confusing to me. I tested this with just the 1 handler in media_test_handler test module and then again with a test handler I made media_file_handler.

The first time I created a Media Type after I choose the test handler there was no "Field with source information" shown because there were no existing fields to choose from(I only knew this because I looked the code). This was a bit confusing because there was the "FIELD MAPPING" section that referred to "metadata fields" but no field for the source or indication that 1 would made. There was also no message that 1 had been made for me.

The second time I created a Media Type after I choose the test handler again there was now a "Field with source information" section with the select let me create a new field or re-use the existing 1. This was bit confusing because the automatic field creation was not something I was told about when creating the first type.

The 3rd time I created a Media Type after I choose a different handler, "file" there again was no "Field with source information" section. I could switch the handler to "test" and then I would see there was an option to create a new field.

The 3rd time was especially confusing because I just made the 2nd Media Type and had the "Field with source information" on the form. Switching to the other handler made me think that maybe the new handler didn't store it the media in the field as the other handler did.

I think it would make sense to always show "Field with source information" and if there is no field to reuse still let the user know that the field will be created automatically. Maybe even indicate the field type that will be created.

HANDLER CONFIGURATION vs FIELD MAPPING

These 2 sections are confusing because under "HANDLER CONFIGURATION" you are actually mapping a field, the source field and there could be multiple options here especially when editting a type. Would it make sense to move the source field under "FIELD MAPPING" and have 2 sub-heading Source Field and Metadata Fields?

tedbow’s picture

+++ b/core/modules/media/src/MediaThumbnailHandler.php
@@ -0,0 +1,63 @@
+    $media->thumbnail->alt = t('Thumbnail');

Can we use StringTranslationTrait in this class?

Re my confusion about "Field with source information" section in #134 I see now there is
interface SourceFieldInterface extends MediaHandlerInterface
So not all media handlers will use source fields? If so then I think this is even more reason explicitly tell the user when a source field is being created(if there are no existing fields to re-use they are currently not if I understand).

phenaproxima’s picture

Status: Needs work » Needs review
FileSize
179.36 KB
9.9 KB

Addressing some of the stuff from #123 that I had previously punted on:

  • #1 is now fixed.
  • #10 is wontfixed.
  • #45 is fixed.
  • #47 is wontfixed. Duly noted for the future, though.
  • #50 is fixed.

@mtodor and I agreed to remove the createSourceFieldStorage() abstract method
in favor of defaulting to the first field type listed in the allowed_field_types
array in the handler's annotation. This is documented as well.

Addressing @tedbow's nitpicks in #134:

  1. Not in an annotation class.
  2. It has no replacement -- I'm actually working on that over in #2367933: Move entity_get_(form_)display() to the entity display repository. Until that lands, we are stuck with the procedural function.
  3. Ditto.
  4. Fixed.
  5. Fixed.
  6. Fixed.
  7. Fixed.
  8. Fixed.
  9. Fixed.
  10. Fixed.

Also fixed #135.

Gábor Hojtsy’s picture

@xjm: well, if the plan was not signed off on in general, just the idea then this is by far not the only issue to review. Also not sure how confident can we be in working on any of them given the exact same situation where sample content and theme initiatives were both of which had plans that they were confident that will be fine to implement (and ideas signed off on) but turned out to not getting signoff on their plans that they started to implement.

Berdir’s picture

I already mentioned this in IRC and it has been discussed a bit, so here is the official comment to propose my idea:

First, note that media_entity/media.module is very different for pretty much anything (the closest is moderation/workflow) else we put in core as experimental. bigpipe is a perfect use case for an experimental module. As it now official declares it has no API, it has no data.
You can enable it and disable it again, any time you want.

Media is different. It contains data but just as important, it *is* an API for a large ecosystem of contrib and custom modules that integrate with it. I think I can speak for all the media related/depending projects and distributions (like Thunder and Lightning) maintainers
that having experimental media module in core would bring us nothing and it would be impossible (mostly due to the amount of time it would require) to maintain modules that work with both media in core and media_entity in contrib, and as a result it would also be of extremely limited usefulness for users.

And while we can argue that moderation/workflow data can be removed without losing too much (your content is still there and still correctly published/not published), that is not the case for Media. You can't just uninstall it because we have no upgrade path in
8.4.x, all your media and metadata for it would be gone.

This proposal based on @catch's comment in https://www.drupal.org/node/2789315#comment-11762228, where he confirmed that anything new that has been added to 8.3.x is "experimential" until we release 8.3.0-beta1.

That means if we commit this issue now, then we still have time until then to improve and stabilize it to reach a MVP. What we need is a set of goals that we need to achieve until then. Both processes (like code/UX/testing quality) but also an actual feature set.

If we do not achieve that in time (say, a certain amount of days before beta1), we remove it again and try again for for 8.4.x and stick to media_entity in contrib. Nobody of us wants that, but IMHO it is better than having something experimental in core that nobody can use.

Basically, this is exactly what Dries proposed in his keynote in Barcelona with Feature branches, we just don't have them yet. But since (almost?) everything we do will be contained in one module, it should be relatively easy to remove again if we have to.

Wim Leers’s picture

#126: While slightly off-topic, I felt like I needed to response to rectify and clarify something.

As an example, big_pipe module was marked as beta for 8.2.0, and just today, in #2835604 we changed its public API to @internal, in anticipation of breaking it in #2657684.

This is categorically wrong. It's not in anticipation of API breaks. It's to clearly establish that BigPipe offers no API. I'd been wanting to do that for a long time now, I just hadn't gotten to it yet. It turns out it would also help close/address some questions in #2657684, so I figured this was a good a time as any to do it. Please see the issue summary of #2835604: BigPipe provides functionality, not an API: mark all classes & interfaces @internal, that contains the full rationale.


#139:

First, note that media_entity/media.module is very different from pretty much anything else we put in core as experimental. bigpipe is a perfect use case for an experimental module. As it now official declares it has no API, it has no data.
You can enable it and disable it again, any time you want.

Indeed, BigPipe has it easy. It simply layers on top of existing infrastructure (Render API + request processing system) and just delivers HTML in a different way. Zero configuration. Zero API (because it layers on top of existing infrastructure). Zero data (because it just optimizes a process). Hence it's simple & safe to install/uninstall at any time.

Media is different. It contains data but just as important, it *is* an API for a large ecosystem of contrib and custom modules that integrate with it.
[…]
You can't just uninstall it because we have no upgrade path in 8.4.x, all your media and metadata for it would be gone.

Indeed.

If we do not achieve that in time (say, a certain amount of days before beta1), we remove it again and try again for for 8.4.x and stick to media_entity in contrib. Nobody of us wants that, but IMHO it is better than having something experimental in core that nobody can use.
[…]
since (almost?) everything we do will be contained in one module, it should be relatively easy to remove again if we have to.

This seems very reasonable & practical. (Although we should ensure there's no (almost?) in there, otherwise we risk not being able to do that.)

Gábor Hojtsy’s picture

Issue summary: View changes

@berdir: thanks for the eloquent description of the situation. I think the #1 thing the media team needs is to identify any MUST have things that should be implemented in this issue and any MUST haves that should be implemented as followups either to accept this as stable out of the gate or just generally before 8.3.0. That would help us work on the most critical things rather than the nice to haves.

tstoeckler’s picture

Re #123.10: The default form operation is (unfortunately) needed for Content Translation. See \Drupal\content_translation\Controller\ContentTranslationController::add() and \Drupal\content_translation\Controller\ContentTranslationController::edit(). Let's bring that back please.

xjm’s picture

I responded to @Berdir's comments on #2825215: Media initiative: Essentials - first round of improvements in core as @Gábor Hojtsy quoted them there. Let's talk about the overall plan there, because as I said, it's at most a one-line change for this patch itself, so I don't think anyone working on this issue needs to be concerned about it until we are at the committer review stage.

slashrsm’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
178.96 KB
37.12 KB

#123-19 - Removed it.
#123-20 - Removed setHandlerConfiguration(). Moved source_field logic into handler plugin.
#123-52 - Would be nice to have ability to get them sorted, but that assumes we introduce categories to handlers which is an overkill IMO.
#123-57 - Handler plugins will need to update anyway. Removed.
#123-58 - Done.
#123-60 - Unfortunately I can't do that since the individual values of the element can't be represented separately, which prevents us form moving it to the top level of the values array.
#123-63 - Done.
#123-68 - Looks better?
#123-69 - As already mentioned, killed and forgotten. :)
#123-72 - Done.
#123-73 - Done.
#123-42,46,53,54,59,61,62 and #127-7,8 (AKA media type form strangeness) Killed static saving instances and used sub form state correctly. Form's code looks much nicer and cleaner after the change. Thank you for pointing this out! Also, the object being used as an array caused a PHP warning, but didn't break the form's functionality (another proof how bloated the code was :)). We have extensive JS tests for that form that obviously didn't fail. Not sure if how to test for a harmless PHP warning.

#134 - Usability

Source field and fields in the mapping part have different purpose and should not be presented together. I tried to improve messages/descriptions that are displayed related to the source field. Hopefully this helps clarify this.

I've also decided to hide the source field drop-down when editing an existing type. Source field is so basic part of a media entity that should not be changed after the type has been initially created.

#142 default is back

All things raised by @tim.plunkett, @tedbow and @tstoeckler should be addressed at this point.

tim.plunkett’s picture

Not going to respond to all of the changes that were perfect, because there were a ton, but thank you for being so thorough.

  1. +++ b/core/modules/media/src/Entity/Media.php
    @@ -30,6 +30,7 @@
    + *       "default" = "Drupal\media\MediaForm",
      *       "add" = "Drupal\media\MediaForm",
      *       "edit" = "Drupal\media\MediaForm",
    

    Okay having 3 is a bit odd now. Not sure what to do here, but this is even more confusing.
    One idea would be to follow Term's pattern, and just use a single 'default', and vary by $entity->isNew() instead of $this->operation.

  2. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -246,12 +237,9 @@ public function preSave(EntityStorageInterface $storage) {
    +      // We need to save handler configuration that have just changed above and
    +      // won't be included otherwise.
    +      $this->handler_configuration = $handler->getConfiguration();
    

    If you moved the rest of the handler section before the parent::preSave() you wouldn't need to do this hunk yourself

  3. +++ b/core/modules/media/src/MediaHandlerBase.php
    @@ -176,6 +193,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form
    +    $this->setConfiguration($form_state->getValues());
    

    This could cause major schema errors in more complex handlers. They'd have to strip other values from $form_state before calling parent::, or skip calling parent:: altogether. I know of no other plugin that blindly does this.

  4. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -82,26 +75,20 @@ public function ajaxHandlerData(array $form, FormStateInterface $form_state) {
    +    $form_state->set('bundle', $this->entity->id());
    

    Why have this? a) not used in the patch b) it's already accessible via $form_state->getFormObject()->getEntity()->id();

  5. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -82,26 +75,20 @@ public function ajaxHandlerData(array $form, FormStateInterface $form_state) {
    +    $handler = $this->entity->get('handler') ? $handler = $this->entity->getHandler() : NULL;
    

    The second $handler = is redundant

tstoeckler’s picture

+++ b/core/modules/media/src/Entity/Media.php
@@ -30,6 +30,7 @@
+ *       "default" = "Drupal\media\MediaForm",
  *       "add" = "Drupal\media\MediaForm",
  *       "edit" = "Drupal\media\MediaForm",

Okay having 3 is a bit odd now. Not sure what to do here, but this is even more confusing.
One idea would be to follow Term's pattern, and just use a single 'default', and vary by $entity->isNew() instead of $this->operation.

This is completely out of scope in my opinion. We want to have dedicated form operations for clarity and to eventually be able to get rid of the default form operation. Therefore omitting them promotes a discouraged pattern, namely the default form operation. As stated above, we simply cannot omit the default operation because Content Translation has a hardcoded dependency on it. I am all for improving this situation and also making this more consistent among the other core entity types, but for the scope of this issue let us just leave it at that.

slashrsm’s picture

#1 Left like this for now as per #146
#2 Neato! Thank you for the suggestion. Done
#3 Changed a bit. Should still save all values automatically assuming plugins add them in defaultConfiguration()
#4 Removed.
#5 Grr! Removed.

We also just realized that we introduced circular config dependency. Removed that as well.

slashrsm’s picture

Nits that were discussed on IRC.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

Been thinking about tagging this Needs framework manager review but this does not satisfy the definition as in an issue significantly impacts (or has the potential to impact) multiple subsystems or represents a significant change in architecture or public APIs, and their signoff is needed. This introduces a new subsystem and does not in itself affect multiple subsystems and is not an architectural change it is an addition. The bigger picture in #2825215: Media initiative: Essentials - first round of improvements in core would obviously do that so tagged that one Needs framework manager review.

At the same time realized this needs a maintainer.txt entry since its a new component / module.

slashrsm’s picture

Status: Needs work » Needs review
FileSize
179.26 KB
3.06 KB

Injected a service into MediaHandlerBase.

Gábor Hojtsy’s picture

BTW as per https://www.drupal.org/contribute/core/maintainers#decisions

If the change involves adding a new subsystem maintainer, a product, framework, or release manager must approve or refuse it (depending on the subsystem).

. Not sure how to tell which subsystem is connected to which maintainer, but this has the release manager review tag, so that may in itself be enough. If somebody knows which manager is needed for approving a subsystem maintainer, let us know. I assume release manager is enough.

Gábor Hojtsy’s picture

Based on https://www.drupal.org/node/1207020/revisions/view/9588197/10252774

As per @xjm, the issue may get to RTBC without the reviews of the respective managers, but committers would consult the respective managers.

phenaproxima’s picture

Only one tiny suggestion that should not block RTBC.

  1. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -267,34 +235,73 @@ public function form(array $form, FormStateInterface $form_state) {
    +    $subform_state = SubformState::createForSubform($form['handler_dependent']['handler_configuration'], $form, $form_state);
    +    $subform_state->set('operation', $this->operation);
    +    $subform_state->set('type', $this->entity);
    

    SubformState::set() is chainable, so these can all be chained.

chr.fritsch’s picture

Status: Needs work » Needs review
FileSize
14.06 KB
180.07 KB

In order to fix the tests for https://www.drupal.org/node/2831936#comment-11827805 we had to move the logic for saving the source_field from the api into the ui.

Fixed #155 on that way.

tim.plunkett’s picture

  1. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -318,9 +318,55 @@
    -    $status = parent::save($form, $form_state);
    ...
    +    if ($handler instanceof SourceFieldInterface) {
    +      $field = $handler->getSourceField($bundle);
    +      $status = parent::save($form, $form_state);
    ...
    +    } else {
    +      $status = parent::save($form, $form_state);
    +    }
    

    This seems a little odd. Does getSourceField() have side-effects that matter to calling parent::save()? If so, it's not a getter.

    Also the } else { syntax is wrong, else belongs on a newline.

  2. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaJavascriptTestBase.php
    @@ -111,2 +107,42 @@
    +    $assert_session->pageTextContains('The media type ' . $id . ' has been added.');
    +
    +
    +    return $this->container->get('entity_type.manager')
    

    Extra blank line

  3. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php
    @@ -165,7 +165,7 @@
    -    $bundle2 = $this->drupalCreateMediaType();
    +    $bundle2 = $this->createMediaTypeViaUi();
    

    IMO this is a red flag. The API and the UI should be interchangeable, and unless you are explicitly trying to test the UI, this should use the API. Written another way, helper create*() methods shouldn't use the UI.

  4. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -112,14 +112,17 @@
    +    $field->getFieldStorageDefinition()->save();
    +    $field->save();
    

    This also looks worrisome.

  5. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -127,7 +130,7 @@
    -    $this->assertFalse($field->isNew());
    +    $this->assertTrue($field->isNew());
    

    O_O

chr.fritsch’s picture

I removed the lock on the source field, because it was not possible to change any field settings. Instead of that, i added an entity_access hook, which forbid to use the "delete" operation.

Beside of that, i also made the source field required.

slashrsm’s picture

A bit different approach to #159 which should fix concerns form #160 in my opinion. Instead of different logic for UI I basically kept pre #159 approach with a flag that allows us to control whether field will be created automatically or not.

Note that we have a bit complicated logic around automatic field creation due to dependencies. Field storage needs to be saved first, media type follows and field save should happen at the end. This results in a bit unconventional code. I added few more test asserts to really make sure everything works as intended.

Also added another helper function to the handler, which could be useful (speaking based on experience working on #2831944: Implement media type plugin for remote video).

tkoleary’s picture

+++ b/core/modules/media/src/MediaTypeForm.php
@@ -0,0 +1,345 @@
+      $options = ['_none' => $this->t('- Skip field -')];

This is a strange pattern with no precedent. THe common pattern for an optional field is to have a parenthetical suffix added to the label eg. '(optional)', or to add it to the description eg. This field is not required.

In this case since this is the behavior for when the select does not find any available options (and the setting is not required) the expected pattern would be to either disable the field or hide it. My preference would be disable, in which case the default option does not need to change.

Gábor Hojtsy’s picture

@tkoleary: its fruitless to document a select element optional if you then have options and none of them are actually doing the *nothing* that you wanted. For it to be possibly optional but also let the user choose something, it needs an empty value and an empty value label. In fact Drupal select boxes have #empty_option and #empty_label for this as is used in the empty option code for example:

    $form['source_field'] = [
      '#type' => 'select',
      '#title' => $this->t('Field with source information'),
      '#default_value' => $this->configuration['source_field'],
      '#empty_option' => $this->t('- Create -'),

Which is what this code would ideally use as well, instead of adding this custom _none option.

catch’s picture

Massive +1 to #139, we need to balance scheduled releases, with what goes into each release, with minimal breakage for contrib, with not making very hard/impossible to reverse mistakes with people's data, and this covers all of those.

alexpott’s picture

  1. +++ b/core/modules/media/media.install
    @@ -0,0 +1,15 @@
    +  media_copy_icons($source, $destination);
    
    +++ b/core/modules/media/media.module
    @@ -0,0 +1,112 @@
    +/**
    + * Copies the media file icons to files directory for use with image styles.
    + *
    + * @param string $source
    + *   Source folder.
    + * @param string $destination
    + *   Destination folder.
    + *
    + * @throws \RuntimeException
    + *   Thrown when media icons can't be copied to their destination.
    + */
    +function media_copy_icons($source, $destination) {
    +  if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    +    throw new \RuntimeException("Unable to create directory $destination.");
    +  }
    +
    +  $files = file_scan_directory($source, '/.*\.(png|jpg)$/');
    +  foreach ($files as $file) {
    +    $result = file_unmanaged_copy($file->uri, $destination, FILE_EXISTS_REPLACE);
    +    if (!$result) {
    +      throw new \RuntimeException("Unable to copy {$file->uri} to $destination.");
    +    }
    +  }
    +}
    

    I don't understand why this is a method and this functionality is not just inlined here? As it stands media_copy_icons() is new procedural API.

  2. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -0,0 +1,205 @@
    +    $this->assertEquals(['source_field' => '', 'test_config_value' => 'Kakec'], $test_bundle->get('handler_configuration'), 'Could not assure the correct handler configuration.');
    
    +++ b/core/modules/media/tests/modules/media_test_type/config/install/media.type.test.yml
    @@ -0,0 +1,10 @@
    +  test_config_value: 'Kakec'
    
    +++ b/core/modules/media/tests/src/Kernel/TokensTest.php
    @@ -0,0 +1,74 @@
    +      'handler_configuration' => ['test_config_value' => 'Kakec'],
    

    Does kakec mean poop?

  3. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -0,0 +1,205 @@
    +use Drupal\Core\TypedData\PrimitiveInterface;
    +use Drupal\Core\TypedData\TypedDataInterface;
    

    Not used.

alexpott’s picture

FILE: /Volumes/devdisk/dev/drupal/core/modules/media/media.module
----------------------------------------------------------------------
 52 | ERROR | [x] Namespaced classes/interfaces/traits should be
    |       |     referenced with use statements
 53 | ERROR | [x] Namespaced classes/interfaces/traits should be
    |       |     referenced with use statements
 56 | ERROR | [x] Concat operator must be surrounded by a single
    |       |     space
 56 | ERROR | [x] There should be no white space before a closing ")"
 65 | ERROR | [x] Namespaced classes/interfaces/traits should be
    |       |     referenced with use statements
 65 | ERROR | [x] Namespaced classes/interfaces/traits should be
    |       |     referenced with use statements
 66 | ERROR | [x] Namespaced classes/interfaces/traits should be
    |       |     referenced with use statements
 69 | ERROR | [x] Namespaced classes/interfaces/traits should be
    |       |     referenced with use statements
 69 | ERROR | [x] Concat operator must be surrounded by a single
    |       |     space
 71 | ERROR | [x] Namespaced classes/interfaces/traits should be
    |       |     referenced with use statements
----------------------------------------------------------------------
FILE: ...drupal/core/modules/media/tests/src/Kernel/BasicCreationTest.php
----------------------------------------------------------------------
 104 | ERROR   | [x] Space found before comma in function call
----------------------------------------------------------------------
alexpott’s picture

Re #166.1 @Berdir said there had been earlier discussion - saying:

initially we hoped to get it in without API changes for existing contrib modules, but that's not going to happen now anyway

@slashrsm wrote

I'd prefer to solve that in a different way. It would be much easier if core shipped with a set of default icons and we'd just use those.

My thoughts are:

I just think it (media_copy_icons()) is weird but it is a weirdness that is liveable with. tbh the way in which assets are made available on module install needs a lot of work

tkoleary’s picture

I'm confused. Why can't we just reference the media type icons from core/misc/icons as we have done for patches to other modules. As long as they are new and not changed we are not breaking BC right?

slashrsm’s picture

#166-1: Removed media_copy_icons() and moved the logic inline. It would be nice to have some standard logic that would allow modules to declare assets that it ships with and need to be copied into a publicly accessible location. Follow-up?
#166-2: This is a small tribute to my younger daughter Lea. It is kind of an internal joke that the two of us have. And yes, it could be translated like that. :)
#166-3: Fixed.
#167: Fixed.

I'm confused. Why can't we just reference the media type icons from core/misc/icons as we have done for patches to other modules. As long as they are new and not changed we are not breaking BC right?

Entities can only reference managed files, which means that they need to be either in private:// or public://.

jhedstrom’s picture

I was attempting to review the queue-based thumbnail processing functionality, and realized the while part of this has been moved to a service independent of the Media entity as suggested in #62, that old method is still used in the queue worker:

+++ b/core/modules/media/src/Plugin/QueueWorker/ThumbnailDownloader.php
@@ -0,0 +1,30 @@
+  public function processItem($data) {
+    /** @var \Drupal\media\MediaInterface $entity */
+    if ($entity = Media::load($data['id'])) {
+      $entity->automaticallySetThumbnail();
+      $entity->save();

which will result in a fatal error since the method is gone. This also indicates we need a test around the queue worker I think.

jhedstrom’s picture

+++ b/core/modules/media/src/MediaThumbnailHandler.php
@@ -0,0 +1,70 @@
+  public function setThumbnail(MediaInterface $media) {
+    // If thumbnail fetching should be queued then temporary use default
+    // thumbnail or fetch it immediately otherwise.
+    if ($media->bundle->entity->getQueueThumbnailDownloadsStatus() && $media->isNew()) {
+      $thumbnail_uri = $media->getHandler()->getDefaultThumbnail();
+    }
+    else {
+      $thumbnail_uri = $media->getHandler()->getThumbnail($media);
+    }

I'm also not sure how or where thumbnails would actually be downloaded, or by what mechanism... This would appear to be the intended method for the queue worker to call, but since it offers no alter hooks (or events, etc), how would this ever be set to anything other than the default thumbnail image?

Edit, I see it would be performed in the handler--I missed the isNew() check.

seanB’s picture

Here is a patch that addresses the issue in #174. Besides that, a check for the source field was added to MediaHandlerBase as mentioned in https://www.drupal.org/node/2831936#comment-11827950

I had some issues writing the tests for #174. Also not sure if I'm doing it right. A separate patch is attached with what I got so far. Any feedback is very much appreciated, I'm still trying to figure out how to do proper tests.

seanB’s picture

Added a test for #174.

seanB’s picture

Added a fix for #2831937-26: Add "Image" MediaSource plugin. The thumbnail should be updated everytime a media entity is saved, since the source field could be changed. I added an extra flag to the entity to skip queueing, because without this saving and queueing ends up in a infinite loop.

tim.plunkett’s picture

I would have expected that interdiff to include test coverage. How much of this patch isn't covered?

slashrsm’s picture

Status: Needs review » Needs work

#176 and #179 look ok to me. Thanks!

Re #180:

+++ b/core/modules/media/src/Entity/Media.php
@@ -158,7 +178,7 @@
-    if (!$update && $this->bundle->entity->getQueueThumbnailDownloadsStatus()) {
+    if ($this->bundle->entity->getQueueThumbnailDownloadsStatus() && !$this->getSkipQueue()) {

Doing this unconditionally on every save is problematic. It can cause performance regressions for remote media types as they usually rely on a 3rd party API call to get the thumbnail.

What if we add an annotation value to handlers that would indicate if thumbnail should be updated on every update? That way we can enable this for handlers like images. For the handlers that can't do that I'd add something into UI that would allow editors to manually trigger update.

catch’s picture

Just another plug for #1189464: Add a 'poor mans queue runner' to core - if we had a way to trigger interim queue runs outside of cron, could we get away without inline processing at all?

tedbow’s picture

re @slashrsm's comment in #182

Doing this unconditionally on every save is problematic. It can cause performance regressions for remote media types as they usually rely on a 3rd party API call to get the thumbnail.

If this is an update rather than insert do we have access to the original entity here in preSave? If so could we just check to see if the source field value has been changed or not. It seems like if the source field value has not been changed then we could skip this.

If it has been changed we would probably want to generate the thumbnail for remote and local sources.

Gábor Hojtsy’s picture

@tedbow: local files we can maybe compare, but remote media may change the thumbnail anytime, so that definitely needs some other logic. This is a question that was raised even before the media sprint in December, and the conclusion at the time was that we need to define how big a priority is this / if it needs to be fixed before commit given that in the life of the contrib module so far people did not encounter this as a problem. Often media library items are created and forgotten, new ones created for new media instead of existing ones replaced (especially given that media entities may be reused so if you replace their media, you don't necessarily know what are you changing). So that informs the discussion about the severity of this task/bug.

tedbow’s picture

@Gábor Hojtsy
re:

@tedbow: local files we can maybe compare, but remote media may change the thumbnail anytime, so that definitely needs some other logic.

That makes sense. I could how we can just leave it the way it is and it would fine
But one the other hand....
For remote sources even though we can't guarantee that if the source field value is unchanged the thumbnail will be the same. I would think that is almost always guaranteed that if the source field value is changed then the thumbnail will be changed.

So something like

// Set thumbnail.
if (!$this->get('thumbnail')->entity || $this->sourceUpdated()) {
   \Drupal::service('media.thumbnail_handler')->setThumbnail($this);
}

Then we cover both remote and local and only make remote calls when can pretty be sure it is needed. Then we could solve the problem of remote sources not changing but still needing an updated thumbnail later in core or contrib.

seanB’s picture

Status: Needs work » Needs review
FileSize
188.37 KB
4.82 KB

After discussing this with slashrsm on IRC we came to the conclusion that the handler should provide a method the check when a thumbnail should be updated. By default the MediaBaseHandler will check if there is a thumbnail or if the source field value changes.

The interdiff is based on #179 since I used that version as a base for the patch.

seanB’s picture

Status: Needs work » Needs review
FileSize
188.19 KB
783 bytes

Doh, this should fix the tests...

Wim Leers’s picture

Reviewed this patch at #2831936: Add "File" MediaSource plugin with the file handler. Only halfway through my review, I realized I was reviewing all of media. I didn't notice the separate "plugin only" patch. Posting it here instead.

  1. +++ b/core/modules/media/config/install/media.settings.yml
    --- /dev/null
    +++ b/core/modules/media/config/optional/system.action.media_delete_action.yml
    

    Woah, I didn't expect this to support the action module in this first iteration!

    Nice to see it be so complete already.

  2. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -0,0 +1,72 @@
    +    icon_base:
    +      type: string
    +      label: 'URI where media icons will be installed'
    

    icon_base sounds like it's a base icon. What does that mean? The only meaning I can think of is fallback icon. But then the label says it's a URI.

    So I think icon_base_uri is probably a better name.

    Related nit: the description can also be cleaned up.

  3. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -0,0 +1,72 @@
    +    handler:
    

    Surely this is not just any handler? I'm guessing it's a media handler? Wouldn't media_handler then be a more appropriate config key?

  4. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -0,0 +1,72 @@
    +      label: 'Queue thumbnail downloads'
    

    Nit: Whether …, because this is a boolean.

  5. +++ b/core/modules/media/config/schema/media.schema.yml
    @@ -0,0 +1,72 @@
    +    new_revision:
    +      type: boolean
    +      label: 'Whether a new revision should be created by default'
    

    The description says by default. The config key implies it's not just a default. If it's a default, then the config key should be new_revision_default.

  6. +++ b/core/modules/media/images/icons/generic.png
    @@ -0,0 +1,8 @@
    +�PNG
    +
    +
    IHDR�����etEXtSoftwareAdobe ImageReady…
    

    This PNG still contains Adobe metadata. Please optimize it with something like ImageOptim to strip that.

  7. +++ b/core/modules/media/js/media_form.js
    @@ -0,0 +1,40 @@
    +   * Behaviors for tabs in the media edit form.
    ...
    +  Drupal.behaviors.mediaDetailsSummaries = {
    ...
    +      $context.find('.media-form-author').drupalSetSummary(function (context) {
    

    This provides summaries, but the docblock doesn't say so.

  8. +++ b/core/modules/media/js/media_form.js
    @@ -0,0 +1,40 @@
    +        var $authorContext = $(context);
    +        var name = $authorContext.find('.field--name-uid input').val();
    +        var date = $authorContext.find('.field--name-created input').val();
    

    Should use context.querySelector(…).

    There's no reason for this to depend on jQuery.

    EDIT: never mind, drupalSetSummary() is tied to jQuery. Gah.

  9. +++ b/core/modules/media/js/media_type_form.js
    @@ -0,0 +1,46 @@
    +   * Behaviors for setting summaries on media type form.
    ...
    +  Drupal.behaviors.mediaBundles = {
    ...
    +      $context.find('#edit-workflow').drupalSetSummary(function (context) {
    

    This provide summaries but the behavior name does not say so.

  10. +++ b/core/modules/media/media.api.php
    @@ -0,0 +1,25 @@
    + *   The array of handlers plugin definitions, keyed by plugin ID.
    

    Nit: handlers plugin definitions -> media handler plugin definitions

  11. +++ b/core/modules/media/media.info.yml
    @@ -0,0 +1,9 @@
    +description: 'Allows for media items to be created and used on the site.'
    

    Why even say media items? Why not just say media?

    Why say used on the site? Where else would you use them other than in Drupal?

  12. +++ b/core/modules/media/media.info.yml
    @@ -0,0 +1,9 @@
    +dependencies:
    +  - image
    +  - user
    

    Interesting that it depends on these, but not on file.

    Perhaps it does depend on file, but it's omitted for brevity, because image depends on file anyway?

    I think it's better to be explicit here.

  13. +++ b/core/modules/media/media.install
    @@ -0,0 +1,25 @@
    +  $source = drupal_get_path('module', 'media') . '/images/icons';
    +  $destination = \Drupal::config('media.settings')->get('icon_base');
    +  if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    +    throw new \RuntimeException("Unable to create directory $destination.");
    +  }
    +
    +  $files = file_scan_directory($source, '/.*\.(png|jpg)$/');
    +  foreach ($files as $file) {
    +    $result = file_unmanaged_copy($file->uri, $destination, FILE_EXISTS_REPLACE);
    +    if (!$result) {
    +      throw new \RuntimeException("Unable to copy {$file->uri} to $destination.");
    +    }
    +  }
    

    This seems … unorthodox. I guess it's because you want customizability?

    Still, it's interesting that you choose to then copy files over. Why not just point to the files that are shipped with the module?

    What if you update the default icons in the future?

    Why limit it to png and jpg, why not send everything over?

  14. +++ b/core/modules/media/media.libraries.yml
    @@ -0,0 +1,13 @@
    +    'js/media_form.js': {}
    ...
    +    'js/media_type_form.js': {}
    

    Nit: Unnecessary quoting.

  15. +++ b/core/modules/media/media.links.menu.yml
    @@ -0,0 +1,11 @@
    +  title: 'Media types'
    ...
    +  description: 'Manage media types.'
    

    - title: "media type"
    - desc: "media type"

    For consistency, it should be "media entity type bundle".

    Or, "entity" should be omitted from the other one.

  16. +++ b/core/modules/media/media.links.menu.yml
    @@ -0,0 +1,11 @@
    +  title: 'Add media'
    ...
    +  description: 'Add a new media entity.'
    

    - title: "media"
    - desc: "media entity"

    Should this not be "media item" for consistency with the help hook and other places?

  17. +++ b/core/modules/media/media.links.task.yml
    @@ -0,0 +1,25 @@
    +  title: 'View'
    ...
    +  title: 'Edit'
    

    Let's omit these quotes, we don't have them elsewhere either.

  18. +++ b/core/modules/media/media.links.task.yml
    @@ -0,0 +1,25 @@
    +  title: Edit
    ...
    +  title: Delete
    ...
    +  title: List
    

    Supernit: Title at the top vs bottom. Let's be consistent.

  19. +++ b/core/modules/media/media.module
    @@ -0,0 +1,91 @@
    +  if ($entity instanceof FieldConfig && $entity->getTargetEntityTypeId() == 'media') {
    

    ===

  20. +++ b/core/modules/media/media.module
    @@ -0,0 +1,91 @@
    +    if ($entity->id() == 'media.' . $media_type->id() . '.' . $media_type->getHandler()->getConfiguration()['source_field']) {
    

    ===

  21. +++ b/core/modules/media/media.module
    @@ -0,0 +1,91 @@
    +      unset($operations['delete']);
    

    Why is this removing the delete operation?

    Let's add documentation, or at least an @see.

  22. +++ b/core/modules/media/media.module
    @@ -0,0 +1,91 @@
    +  if ($operation == 'delete' && $entity instanceof FieldConfig && $entity->getTargetEntityTypeId() == 'media') {
    

    ===

    and more later. Won't repeat again.

  23. +++ b/core/modules/media/media.module
    @@ -0,0 +1,91 @@
    +  $suggestions[] = 'media__' . $sanitized_view_mode;
    +  $suggestions[] = 'media__' . $media->bundle();
    +  $suggestions[] = 'media__' . $media->bundle() . '__' . $sanitized_view_mode;
    

    Do we have test coverage for this? Do we need it?

  24. +++ b/core/modules/media/media.services.yml
    --- /dev/null
    +++ b/core/modules/media/media.theme.inc
    

    Didn't we banish *.inc files, except in extreme cases?

    i.e. views.theme.inc is >1000 LoC. What's in this file could easily be moved into the *.module file, no?

  25. +++ b/core/modules/media/media.theme.inc
    @@ -0,0 +1,28 @@
    + *   An associative array containing:
    + *   - media: An individual media for display.
    ...
    +  $variables['media'] = $variables['elements']['#media'];
    +  $variables['view_mode'] = $variables['elements']['#view_mode'];
    +  $variables['name'] = $variables['media']->label();
    

    Incomplete docs.

  26. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,177 @@
    +    'description' => t('The name of this media.'),
    ...
    +    'description' => t('The URL of the media.'),
    ...
    +    'description' => t("The URL of the media's edit page."),
    ...
    +    'description' => t('The date the media was most recently updated.'),
    

    s/media/media item/

  27. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,177 @@
    +          $bubbleable_metadata->addCacheableDependency($account);
    ...
    +          $bubbleable_metadata->addCacheableDependency($date_format);
    ...
    +          $bubbleable_metadata->addCacheableDependency($date_format);
    

    Yay, correct cacheability metadata!

  28. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,177 @@
    +          $replacements[$original] = \Drupal::service('date.formatter')
    +            ->format($media->getCreatedTime(), 'medium', '', NULL, $langcode);
    

    Supernit: I think we always have the first method call on the same line?

  29. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,177 @@
    +            ->format($media->getChangedTime(), 'medium', '', NULL, $langcode);
    

    s/'medium'/$date_format->id()/

  30. +++ b/core/modules/media/src/Annotation/MediaHandler.php
    @@ -0,0 +1,62 @@
    + * with a particular type of media asset. They provide various universal and
    

    First time we see "asset" mentioned!

    In fact, in all of this patch, this very important/noteworthy keyboard is only mentioned TWICE! And both times in this class.

    Let's remove it.

  31. +++ b/core/modules/media/src/Annotation/MediaHandler.php
    @@ -0,0 +1,62 @@
    +class MediaHandler extends Plugin {
    ...
    +   * A brief description of the plugin.
    

    So is it a handler or is it a plugin?

    Views is the only module in all of core that still mixes "handlers" and "plugins". This would add a second one.

    This adds unnecessary confusion.

    IMO media should be free of the term "handler", it should always use "plugin".

    For consistency sake.

  32. +++ b/core/modules/media/src/Annotation/MediaHandler.php
    @@ -0,0 +1,62 @@
    +   * The human-readable name of the handler.
    

    s/handler/media handler/

  33. +++ b/core/modules/media/src/Annotation/MediaHandler.php
    @@ -0,0 +1,62 @@
    +   * This will be shown when adding or configuring this display.
    

    Huh, display? How is this a display? Seems a c/p remnant.

  34. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,385 @@
    + *     "id" = "mid",
    + *     "revision" = "vid",
    

    I read this in sequence as "midvid" — sounds funny. At least in my head. :P

  35. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,385 @@
    +    if (!$update && $this->bundle->entity->getQueueThumbnailDownloadsStatus()) {
    +      $queue = \Drupal::queue('media_entity_thumbnail');
    +      $queue->createItem(['id' => $this->id()]);
    +    }
    

    This queues thumbnail creation. Elegant. Nice!

    It would be more understandable if getQueueThumbnailDownloadsStatus had a name like thumbnailDownloadsAreQueued… which is exactly what the interface docs say:

      /**
       * Returns whether thumbnail downloads are queued.
       *
       * @return bool
       *   TRUE if thumbnails are queued for download later, FALSE if they should be
       *   downloaded now.
       */
      public function getQueueThumbnailDownloadsStatus();
    

    Better name = more legible code.

    The current function name does not indicate at all that this returns a boolean.

  36. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,385 @@
    +      // If we are updating an existing node without adding a new revision, we
    

    "node" -> c/p remnant

  37. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,385 @@
    +      ->setDescription(t('The thumbnail of the media.'))
    

    s/media/media item/

  38. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,385 @@
    +      ->setDescription(t('The username of the media publisher.'))
    

    "username", but it's really a user ID. "username" is just what the default widget happens to allow you to type.

    "media publisher" -> first and only time in all of this patch that this terminology is used. Should be simplified.

  39. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,385 @@
    +      ->setLabel(t('Revision publisher ID'))
    +      ->setDescription(t('The user ID of the publisher of the current revision.'))
    

    Again "publisher". Let's remove this.

  40. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,385 @@
    +      ->setDisplayOptions('form', array(
    ...
    +        'settings' => array(
    

    Nit: s/array()/[]/

    (This is done everywhere else, except here! Tiny oversight.)

  41. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,301 @@
    +  protected $auto_create_source_field = FALSE;
    

    This is not present in media.type.file.yml, nor is it listed in config_export in the annotation of this class. That seems like an oversight, because e.g. \Drupal\Tests\media\Kernel\BasicCreationTest::setUp() does use this.

    If this is for internal or testing purposes only, then that should at least be documented.

  42. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,301 @@
    +  public static function getLabel(MediaInterface $media) {
    +    $bundle = static::load($media->bundle());
    +    return $bundle ? $bundle->label() : FALSE;
    +  }
    

    This seems like a strange helper method to have on a config entity type. If you keep this, consider marking it @internal.

  43. +++ b/core/modules/media/src/Form/DeleteMultiple.php
    @@ -0,0 +1,197 @@
    + * Provides a media deletion confirmation form.
    

    - s/media/media item/
    - doesn't mention this is for multiple, not single?

  44. +++ b/core/modules/media/src/Form/DeleteMultiple.php
    @@ -0,0 +1,197 @@
    +class DeleteMultiple extends ConfirmFormBase {
    ...
    +  public function getFormId() {
    +    return 'media_multiple_delete_confirm';
    +  }
    

    s/DeleteMultiple/DeleteMultipleForm/

    and probably actually:

    s/DeleteMultiple/MediaDeleteMultipleForm/

    well actually:

    s/DeleteMultiple/MediaDeleteMultipleConfirmForm/

    … as evidenced by the form ID.

  45. +++ b/core/modules/media/src/Form/DeleteMultiple.php
    @@ -0,0 +1,197 @@
    +  /**
    +   * The array of media entities to delete.
    +   *
    +   * @var string[][]
    +   */
    +  protected $entityInfo = [];
    

    The typehint seems wrong?

    Also, the ariable name doesn't seem to make a whole lot of sense?

  46. +++ b/core/modules/media/src/Form/DeleteMultiple.php
    @@ -0,0 +1,197 @@
    +  public function getCancelUrl() {
    +    return new Url('system.admin_content');
    +  }
    

    That seems like a non-helpful cancellation URL? Should this not redirect to the media overview?

  47. +++ b/core/modules/media/src/Form/DeleteMultiple.php
    @@ -0,0 +1,197 @@
    +    foreach ($this->entityInfo as $id => $langcodes) {
    +      foreach ($langcodes as $langcode) {
    +        $entity = $entities[$id]->getTranslation($langcode);
    +        $key = $id . ':' . $langcode;
    +        $default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
    +
    +        // If we have a translated entity we build a nested list of translations
    +        // that will be deleted.
    +        $languages = $entity->getTranslationLanguages();
    +        if (count($languages) > 1 && $entity->isDefaultTranslation()) {
    +          $names = [];
    +          foreach ($languages as $translation_langcode => $language) {
    +            $names[] = $language->getName();
    +            unset($items[$id . ':' . $translation_langcode]);
    +          }
    +          $items[$default_key] = [
    +            'label' => [
    +              '#markup' => $this->t('@label (Original translation) - <em>The following translations will be deleted:</em>', ['@label' => $entity->label()]),
    +            ],
    +            'deleted_translations' => [
    +              '#theme' => 'item_list',
    +              '#items' => $names,
    +            ],
    +          ];
    +        }
    +        elseif (!isset($items[$default_key])) {
    +          $items[$key] = $entity->label();
    +        }
    +      }
    +    }
    +
    

    Wow this is complex! I guess there is no code we can reuse?

    Should definitely have a follow-up to extract this to a trait or base class for other entity types to reuse.

    Also should have explicit test coverage.

  48. +++ b/core/modules/media/src/Form/DeleteMultiple.php
    @@ -0,0 +1,197 @@
    +  public function submitForm(array &$form, FormStateInterface $form_state) {
    

    Again crazy complexity here. Same remarks/concerns apply.

  49. +++ b/core/modules/media/src/Form/MediaTypeDeleteConfirmForm.php
    @@ -0,0 +1,68 @@
    +class MediaTypeDeleteConfirmForm extends EntityDeleteForm {
    

    Ah, this class name does make sense!

  50. +++ b/core/modules/media/src/Form/MediaTypeDeleteConfirmForm.php
    @@ -0,0 +1,68 @@
    +            '%type is used by @count piece of content on your site. You can not remove this content type until you have removed all of the %type content.',
    

    s/content type/media type/

  51. +++ b/core/modules/media/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    + * Defines an access controller for the media entity.
    ...
    +class MediaAccessController extends EntityAccessControlHandler {
    

    s/controller/control handler/

    Let's be consistent with the base class.

  52. +++ b/core/modules/media/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    +    $is_owner = ($account->id() && $account->id() == $entity->getOwnerId());
    

    ===

  53. +++ b/core/modules/media/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    +        return AccessResult::allowedIf(($account->hasPermission('update media') && $is_owner) || $account->hasPermission('update any media'))->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity);
    ...
    +        return AccessResult::allowedIf(($account->hasPermission('delete media') && $is_owner) ||  $account->hasPermission('delete any media'))->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity);
    

    These are wrong.

    They both make the same mistake. They both always vary by user, and always add the entity as a cacheable dependency.

    But this is not necessary! If the user has the "any" permission, then there's no need to check the owner and hence no need to vary by user nor entity.

    So, this needs to be split up into multiple step. The cacheability of the returned access result must reflect all data that was taken into account to reach that conclusion.

  54. +++ b/core/modules/media/src/MediaAccessController.php
    @@ -0,0 +1,46 @@
    +    // No opinion.
    +    return AccessResult::neutral()->cachePerPermissions();
    

    IMO it'd be clearer to list this as the default case in the switch statement.

    Then future refactoring to the other cases there could not cause this fallback statement to be reached, which would be incorrect.

  55. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,184 @@
    +      $this->entity->setOwnerId($this->currentUser()->id());
    +      $this->entity->setCreatedTime($this->time->getRequestTime());
    

    Why is this necessary? \Drupal\media\Entity\Media::baseFieldDefinitions() has a default value callback for the owner? (And probably should also have one for the created time.)

  56. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,184 @@
    +    if ($this->operation == 'edit') {
    

    ===

  57. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,184 @@
    +        '#weight' => 90,
    

    Woah, 90! Let's document why.

  58. +++ b/core/modules/media/src/MediaHandlerBase.php
    @@ -0,0 +1,354 @@
    +    // If we don't know the name of the source field, we definitely need to
    +    // create it.
    +    if (empty($this->configuration['source_field'])) {
    +      return $this->createSourceField($type);
    +    }
    

    This rings alarm bells for me. A getter that's modifying stuff.

    It seems like this should not be possible. Should this not happen when creating a media type?

  59. +++ b/core/modules/media/src/MediaHandlerBase.php
    @@ -0,0 +1,354 @@
    +    // Even if we do know the name of the source field, there is no guarantee
    +    // that it already exists. So check for the field and create it if needed.
    +    $field = $this->configuration['source_field'];
    +    $fields = $this->entityFieldManager->getFieldDefinitions('media', $type->id());
    +    return isset($fields[$field]) ? $fields[$field] : $this->createSourceField($type);
    

    Yet more creation stuff? This seems brittle.

  60. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,110 @@
    +   * Attaches handler-specific constraints to media.
    

    s/constraints/validation constraints/

  61. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,110 @@
    +   *   The media entity.
    ...
    +   *   The media entity.
    ...
    +   *   The media entity.
    ...
    +   *   The media entity.
    

    Supernit: s/The/A/

  62. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,110 @@
    +   * Media handler plugin is responsible for returning URI of the generic
    

    By "URI", does this mean "file URI using the public:// scheme"?

    Surely this means "URI as accepted by file_create_url()"?

    Just "URI" is kind of meaningless.

    Let's at least say "file URI".

  63. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -0,0 +1,110 @@
    +   * Provide a default name for the media.
    

    s/media/media item/

  64. +++ b/core/modules/media/src/MediaHandlerManager.php
    @@ -0,0 +1,33 @@
    + * Manages media handler plugins.
    ...
    +class MediaHandlerManager extends DefaultPluginManager {
    

    The clash between "handler" and "plugins" is again super obvious here.

  65. +++ b/core/modules/media/src/MediaInterface.php
    @@ -0,0 +1,43 @@
    +   * @return \Drupal\media\MediaInterface
    +   *   The called media entity.
    

    Shouldn't this be:

    @return $this
    

    ?

  66. +++ b/core/modules/media/src/MediaThumbnailHandlerInterface.php
    @@ -0,0 +1,18 @@
    +  /**
    +   * Sets the media thumbnail.
    +   *
    +   * @param \Drupal\media\MediaInterface $media
    +   *   The Media entity.
    +   */
    +  public function setThumbnail(MediaInterface $media);
    

    s/the/a/
    s/The media/A media/

    And should also do @return $this

  67. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,345 @@
    +      $form['handler_dependent']['handler_configuration'] = $handler->buildConfigurationForm($form['handler_dependent']['handler_configuration'], $this->getHandlerSubFormState($form, $form_state));
    

    This uses subform state, great!

  68. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,345 @@
    +        if (!($field instanceof BaseFieldDefinition) || $field_name == 'name') {
    

    ===

  69. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,345 @@
    +   * Gets sub-form state for the handler configuration sub-form.
    ...
    +   *   Sub-form state for the handler configuration form.
    

    s/sub-form/subform/

  70. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,345 @@
    +    if ($status == SAVED_UPDATED) {
    ...
    +    elseif ($status == SAVED_NEW) {
    

    ===

  71. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,76 @@
    +  public function getQueueThumbnailDownloadsStatus();
    ...
    +  public function setQueueThumbnailDownloadsStatus($queue_thumbnail_downloads);
    

    See earlier remark.

  72. +++ b/core/modules/media/src/MediaTypeListBuilder.php
    @@ -0,0 +1,50 @@
    +    $build['table']['#empty'] = $this->t('No media types available. <a href="@link">Add media type</a>.', [
    +      '@link' => Url::fromRoute('entity.media_type.add_form')->toString(),
    

    Don't URLs use :placeholder nowadays?

  73. +++ b/core/modules/media/src/Plugin/Action/DeleteMedia.php
    @@ -0,0 +1,98 @@
    +   * @var \Drupal\user\SharedTempStore
    ...
    +  public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
    

    Mismatch.

  74. +++ b/core/modules/media/src/Plugin/Action/PublishMedia.php
    @@ -0,0 +1,38 @@
    +    $result = $object->access('update', $account, TRUE)
    +      ->andIf($object->status->access('update', $account, TRUE));
    
    +++ b/core/modules/media/src/Plugin/Action/UnpublishMedia.php
    @@ -0,0 +1,38 @@
    +    $result = $object->access('update', $account, TRUE)
    +      ->andIf($object->status->access('update', $account, TRUE));
    

    This is tricky, but correct.

  75. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,190 @@
    +      // Collect cache tags to be added for each item in the field.
    

    s/Collect cache tags to be added for/Add cacheability of/

  76. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,190 @@
    +    // Collect cache tags related to the image style setting.
    

    s/Collect cache tags related to/Add cacheability of/

  77. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,190 @@
    +    return $target_type == 'media';
    

    ===

  78. +++ b/core/modules/media/src/Plugin/QueueWorker/ThumbnailDownloader.php
    @@ -0,0 +1,70 @@
    + * Download images.
    ...
    +class ThumbnailDownloader extends QueueWorkerBase implements ContainerFactoryPluginInterface {
    ...
    +   * Constructs a new SelectionBase object.
    

    Mismatch.

  79. +++ b/core/modules/media/src/Plugin/views/wizard/Media.php
    @@ -0,0 +1,88 @@
    +    /* Field: Media: Name */
    

    This is a strange comment.

  80. +++ b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,99 @@
    +   * Set the created column.
    ...
    +  protected $createdColumn = 'changed';
    

    "created" or "changed"?

  81. +++ b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,99 @@
    +    /* Field: Media revision: Created date */
    ...
    +    /* Field: Media revision: Name */
    

    More strange comments.

  82. +++ b/core/modules/media/tests/src/Functional/MediaAccessTest.php
    @@ -0,0 +1,100 @@
    +  public function testMediaAccess() {
    

    This is not asserting cache contexts.

  83. +++ b/core/modules/media/tests/src/Functional/MediaAccessTest.php
    @@ -0,0 +1,100 @@
    +    // We are logged-in as admin, so test 'administer media' permission.
    

    Supernit: s/logged-in/logged in/

  84. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestBase.php
    @@ -0,0 +1,96 @@
    +   * The storage service.
    

    s/service/handler/

  85. +++ b/core/modules/media/tests/src/FunctionalJavascript/BundleCreationTest.php
    @@ -0,0 +1,98 @@
    +class BundleCreationTest extends MediaJavascriptTestBase {
    

    Nice, JS test coverage!

  86. +++ b/core/themes/classy/templates/content/media.html.twig
    @@ -0,0 +1,27 @@
    +<article{{ attributes.addClass(classes) }}>
    +  {% if content %}
    +    {{ content }}
    +  {% endif %}
    +</article>
    

    Hm…

    This seems rather pointless. If content is empty, printing it would not print anything anyway.

    Also, can content ever be empty?

    If so, then you'll render an empty article tag… which I don't see the point of?

  87. Finally, I'm missing test coverage like \Drupal\node\Tests\NodeCacheTagsTest: every entity type should have a cache tags test, based on \Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase. That's how we ensure cache tag invalidation works correctly for every entity type. All the hard work is already done in there.
seanB’s picture

FileSize
187.21 KB
53.02 KB

The first bunch of comments in #191 are fixed. The tests are still broken from the patch in 189, but I think there are no new issues after the changes (still working on that). It would be nice to get some feedback on the todo's.

TODO:
3. Didn't touch it for now.
8. Since we depend on jQuery anyway, should we still switch to querySelector?
10. Related to #3. Skipped for now.
13. I think the module images should not be accessible. Didn't touch this for now.
16. There has been a lot of debate on this. Not sure what to do.
23. There is no test coverage for the theme suggestions. The node module doesn't have this either. Not sure if this is a blocker? Skipped for now.
26. Related to #16. Skipped for now.
31. I renamed this instance of plugin to handler. Handler was specifically chosen for all plugins handling media type logic. Not sure if it is a good idea to rename handler to plugin everywhere.
32. Related to #3. Skipped for now.
37. Related to #16. Skipped for now.
42. I think we can just remove this, not sure? Left it for now.
82. Didn't touch this for now.
87. Didn't touch this for now.

Done
1. Yay!
2. Done
3. Didn't touch it for now.
4. Done
5. This is actually in line with how the node module works. I think it should stay the same for a clear DX. Maybe fix this in all places through a followup?
6. Done
7. Done
8. Since we depend on jQuery anyway, should we still switch to querySelector?
9. Done
10. Related to #3. Skipped for now.
11. The 'on this site' probably was taken from the node module info file. But I agree and removed it.
12. Done
13. I think the module images should not be accessible. Didn't touch this for now.
14. Done
15. Done
16. There has been a lot of debate on this. Not sure what to do.
17. Probably taken from the node module. But done.
18. Again same as node module. But done.
19. Done
20. Done
21. Added documentation and @see.
22. Done
23. There is no test coverage for the theme suggestions. The node module doesn't have this either. Not sure if this is a blocker?
24. Fixed
25. Fixed
26. Related to #3. Skipped for now.
27. Yay!
28. Done
29. Done
30. Done
31. I renamed this instance of plugin to handler. Handler was specifically chosen for all plugins handling media type logic. Not sure if it is a good idea to rename handler to plugin everywhere.
32. Related to #3. Skipped for now.
33. Done
34. :)
35. Done
36. Done
37. Related to #16. Skipped for now.
38. Same issue in node module, but done.
39. Done
40. Done
41. Done
42. I think we can just remove this, not sure? Left it for now.
43. Done
44. Done
45. Done
46. Agreed, but since the media overview is not part of the MVP, this has to be done later.
47. This is basically the same as the node module. No tests there as well. Maybe this should be part of a follow up to fix it right everywhere. Didn't touch this for now.
48. This is basically the same as the node module. No tests there as well. Maybe this should be part of a follow up to fix it right everywhere. Didn't touch this for now.
49. Yay!
50. Done
51. Done
52. Done
53. Done
54. Done
55. Done
56. Done
57. Same as node module?
58. Removed getSourceFieldStorage() since it was called only once and added the code to createSourceField().
59. See #58.
60. Done
61. Done
62. Done
63. Done
64. Related to #3. Skipped for now.
65. Done
66. Done
67. Yay!
68. Done
69. Done
70. Done
71. Done, see #35
72. Done
73. Done
74. Yay!
75. Done
76. Done
77. Done
78. Done
79. The node module has the same thing, but removed it.
80. The node module has the same thing, but changed it.
81. The node module has the same thing, but changed it.
82. Didn't touch this for now.
83. Done
84. Done
85. Yay!
86. Done
87. Didn't touch this for now.

Items to check for handlers depending on the patch:
2.
3. And related
16. And related
35.

dawehner’s picture

For #192.47 we have #2670730: Provide a delete action for each content entity type already.

Hm…

This seems rather pointless. If content is empty, printing it would not print anything anyway.

Also, can content ever be empty?

If we care about the whitespace level details, we could use {{- content -}} here.

  1. +++ b/core/modules/media/media.module
    @@ -0,0 +1,117 @@
    +  if ($entity instanceof FieldConfig && $entity->getTargetEntityTypeId() === 'media') {
    ...
    +  if ($operation === 'delete' && $entity instanceof FieldConfig && $entity->getTargetEntityTypeId() === 'media') {
    

    Nitpick: Let's use the Interface instead of the concrete class.

  2. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,394 @@
    + *
    + * @todo Remove "default" form handler when https://www.drupal.org/node/2006348
    + * lands.
    ...
    +class Media extends ContentEntityBase implements MediaInterface {
    

    I'm wondering whether we could add a todo for d9 to extend from RevisionableContentEntityBase instead. This would simplify things a bit

  3. +++ b/core/modules/media/src/MediaInterface.php
    @@ -0,0 +1,43 @@
    +  /**
    +   * Returns the media creation timestamp.
    +   *
    +   * @return int
    +   *   Creation timestamp of the media.
    +   */
    +  public function getCreatedTime();
    +
    +  /**
    +   * Sets the media creation timestamp.
    +   *
    +   * @param int $timestamp
    +   *   The media creation timestamp.
    +   *
    +   * @return \Drupal\media\MediaInterface
    +   *   The called media entity.
    +   */
    +  public function setCreatedTime($timestamp);
    

    Its weird/funny that we don't have a EntityCreateInterface but a EntityChangedInterface

  4. +++ b/core/modules/media/src/MediaThumbnailHandlerInterface.php
    @@ -0,0 +1,23 @@
    +/**
    + * Provides an interface defining a media thumbnail service.
    + */
    +interface MediaThumbnailHandlerInterface {
    

    IMHO the doc here should describe what this is actually doing. This documentation tells simply nothing.

  5. +++ b/core/modules/media/src/MediaThumbnailHandlerInterface.php
    @@ -0,0 +1,23 @@
    +interface MediaThumbnailHandlerInterface {
    ...
    +  /**
    +   * Sets a media thumbnail.
    +   *
    

    According to the only method, it sets a thumbnail, so what about naming it MediaThumbnailSetterInterface ?

  6. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,345 @@
    +        ['\Drupal\language\Entity\ContentLanguageSettings', 'loadByEntityTypeBundle'],
    

    Nitpick: you could use ::class

  7. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,76 @@
    +  /**
    +   * Returns the label.
    +   *
    +   * @param \Drupal\media\MediaInterface $media
    +   *   The media entity.
    +   *
    +   * @return string|bool
    +   *   Returns the label of the bundle that entity belongs to.
    +   */
    +  public static function getLabel(MediaInterface $media);
    +
    

    This method name is less obvious IMHO. What about using getMediaLabel to make it clear that this is not the label of the media type?

  8. +++ b/core/modules/media/src/MediaTypeListBuilder.php
    @@ -0,0 +1,50 @@
    +    $build['table']['#empty'] = $this->t('No media types available. <a href=":link">Add media type</a>.', [
    +      ':link' => Url::fromRoute('entity.media_type.add_form')->toString(),
    +    ]);
    

    :link should be :url

  9. +++ b/core/modules/media/src/Plugin/Action/SaveMedia.php
    @@ -0,0 +1,37 @@
    +    $entity->changed = 0;
    

    How does this even work? Doesn't it have to be $entity->changed->value at least?

  10. +++ b/core/modules/media/src/Plugin/views/wizard/Media.php
    @@ -0,0 +1,87 @@
    +      'value' => TRUE,
    
    +++ b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,99 @@
    +      'value' => TRUE,
    

    Note: Value should be '1', as of #2459289: Boolean default values are not saved

  11. +++ b/core/modules/media/tests/src/Functional/MediaBulkFormTest.php
    @@ -0,0 +1,115 @@
    +    $this->assertEquals($view->total_rows, 5);
    
    +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestTrait.php
    @@ -0,0 +1,48 @@
    +    $this->assertEquals($status, SAVED_NEW, 'Media type was created successfully.');
    

    Note: In phpunit based tests the expected value is on the left side

  12. +++ b/core/modules/media/tests/src/Functional/MediaBulkFormTest.php
    @@ -0,0 +1,115 @@
    +    for ($i = 1; $i <= 3; $i++) {
    +      $this->assertFalse($this->storage->loadUnchanged($i)->isPublished(), 'The unpublish action failed in some of the media entities.');
    +    }
    ...
    +    for ($i = 1; $i <= 2; $i++) {
    +      $this->assertTrue($this->storage->loadUnchanged($i)->isPublished(), 'The publish action failed in some of the media entities.');
    +    }
    ...
    +    for ($i = 1; $i <= 2; $i++) {
    +      $this->assertNull($this->storage->loadUnchanged($i), 'Could not delete some of the media entities.');
    +    }
    

    Just an ubercomment: To be honest it feels more readable to just repeat the line multiple times.

  13. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestBase.php
    @@ -0,0 +1,96 @@
    +  use MediaFunctionalTestTrait {
    +    createMediaType as drupalCreateMediaType;
    +  }
    

    The drupal prefix is sort of a pure BC layer in core. As this are new tests we IMHO don't need that.

  14. +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,200 @@
    +    $this->assertEquals($media->getRevisionLogMessage(), $revision_log_message);
    ...
    +    $this->assertEquals($media_item->get('thumbnail')->entity->getFileUri(), $default_thumb_uri);
    ...
    +    $this->assertEquals($updated_media_item->get('thumbnail')->entity->getFileUri(), $handler_thumb_uri);
    

    Again ... the expected message is on the left side.

seanB’s picture

Status: Needs work » Needs review
FileSize
188.3 KB
19.66 KB

Looked at #193. Also think I found the issue for the tests. Let's see...

TODO/Discussion:
2. I'm not sure about this one. As pointed out by Berdir in https://www.drupal.org/node/2669802#comment-11817894, there could be multiple 'functional' base classes that you might want to extend. Revisionable, translatable, 'insert-random-feature'. I don't think there is a solution for this yet?
3. That would make a excellent followup :)
5. The setter was actually fetching a new thumbnail as well before setting it. I changed setThumbnail to updateThumbnail to reflect this. Getting the actual file URI was moved in a different function. I left the name MediaThumbnailHandler, not sure if this is a blocker.

Done:
1. Done
2. I'm not sure about this one. As pointed out by Berdir in https://www.drupal.org/node/2669802#comment-11817894, there could be multiple 'functional' base classes that you might want to extend. Revisionable, translatable, 'insert-random-feature'. I don't think there is a solution for this yet?
3. That would make a excellent followup :)
4. Done
5. The setter was actually fetching a new thumbnail as well before setting it. I changed setThumbnail to updateThumbnail to reflect this. Getting the actual file URI was moved in a different function. I left the name MediaThumbnailHandler, not sure if this is a blocker.
6. Done
7. It actually is returning the label of the media type. I changed the comment a little to make this more clear.
8. Done
9. The node module does it the same way?
10. Thanks, weird, but done.
11. Done
12. Done
13. Done
14. Done

slashrsm’s picture

#191-3 - I wouldn't change the config key. It appears as part of the media type which implies it is a media handler in my opinion. +1 to change the human-readable labels though.
#191-10 - Similar as above. Yes, let's update it.
#191-13 - Default icons will eventually become managed files. Managed files can't be in the module's folder, they need to be in public, private, hash or whatever wrapper site is using.
#191-16 - If I recall correctly @tkoleary suggested to use "Add media" (omiting "item"). Let's check this with UX people one more time, decide and fix all strings to consistently follow the decision.
#191-31 (and related) - Calling it plugin doesn't say anything about the purpose of it. It just tells that it is implemented using plugins, which is not relevant from the functionality point of view. Handler kind of implies that it "handles media type" or "handles business logic related to a media type". I am open to giving it a better name, but I'd refrain from calling it a plugin.
#191-42 - Yes, I think that we can remove it.

yoroy’s picture

re: #196-16 yes, leave out "item" in the human facing labels please.

EDIT: of course, do use "media item" where without "item" the wording becomes awkward.

Wim Leers’s picture

#192:
5. Hm. Probably. But doing so in a follow-up means you'll need update path tests for each of these. But yes, probably best to keep the same.
8. Indeed — I already said the same there :) I just kept this remark so you could see my reasoning, in case somebody else makes the same suggestion in the future.
16. Okay. But this should definitely be resolved before commit. Because it'd cause string translation changes afterwards. And it will undoubtedly confuse translators too. So, I think it's a hard requirement that this be made consistent. I'll leave it to others to determine what the right terminology is. The key point is that it must be consistent.
21. Great, thanks!
23. It probably is not a blocker, but it'd improve reliability + maintainability of the module for sure.
41. This was fixed by adding a single line worth of comments. The existence of this field is still highly questionable. I'm not at all convinced by this single comment that it's not just lingering technical debt.
42. I still am fairly confident this should be removed.
44. Tip for the future git diff -M — records moves, so that you don't see an entire file being added and another being deleted :) Makes the interdiff a lot easier to read.
46+47+48. That's okay, but then let's add a @todo that points to another Drupal core issue that contains that follow-up.
53+54: Explicitly confirming here that the fix looks correct. Thanks.
55. Explicitly saying here that the cleaned up version looks much better :)
58+59. That's entirely missing the point. It was protected. It's not about having an extra method. In fact, it's better to have it as a separate method, that makes it easier to understand. The point is that it's very scary that a getter is not just a getter, and it's in fact making big modifications in completely different places. This definitely still needs to be addressed.
78. Still wrong I'm afraid :) Easy fix though!
82. This must definitely still be tested. It would've caught the bugs in the MediaAccessControlHandler.
87. Definitely also still needed.

#195:
3. That's fair.
13. But why do they have to be managed files? Why can't the defaults be shipped files instead of managed files? That also addresses the "update" question: if you at some point change the default icons, then you'll need to somehow ensure that you overwrite the original icons. But only if the site hasn't customized them. But what if the file was just pushed through an image optimizer/minifier, and is in fact functionally equivalent? Having a system where the default icons are shipped files, and if a managed file with the same name exists (file_exists('public://somewhere/pdf.png")) you use that managed file instead, this would avoid that problem.
16. +1 for consistency. Like I said above, I don't care about which one per se, as long as it is consistent.
31. I'm sorry, I find your justification not convincing. "install the media plugin" can make just as much sense. Your argument could be made for "block handlers" instead of "block plugins", "filter handlers" instead of "filter plugins", etc. I do agree that we don't need to expose "plugin" in the UI. Nor do we need to expose "handler". It could just be "media type". Because we don't say "configure a filter plugin" or "configure a filter handler", we say "configure a filter". Similarly, it can be "configure a media type". Now, it's entirely possible that I'm just misunderstanding your justification. If others agree with calling "media type plugins" not plugins, but "media type handlers" or "media type handler plugins", then that should at least be very very clearly documented. For now, it's not clear why media is the only one with this special need to not call plugins plugins.
42. Excellent :)

seanB’s picture

Ok another round of changed based on #191.

Still TODO:
#191-41.
#191-82.
#191-87.

Discussed / done:
#191-3. So, we leave this as is I think.
#191-10. Done
#191-13. Since the icon files are (probably) made publicly available, I think it could be a security issue when everything in the folder is copied. Checking for images makes sense, I did extend the list to also move jpeg/svg/gif images. The reason why icons are managed files is because we currently use the thumbnail image field to store a preview of a media item. This could be a default icon, but also a actual thumbnail of a uploaded image. Always having something in the thumbnail field makes it less complex. I do see the issue with updating the icons and Wim is definitely not the first person to have doubts about this code, but I don't think this is a blocker.
#191-16. I've changed a whole bunch of places (mostly code comments). While doing this it seemed to me the best solution is to use just 'media' when it's related to actions. Like 'Delete media', 'Add media', 'View media' etc. In regular sentences using just 'Media' is weird. Eg. 'The thumbnail of the media item.'. There will probably be places where it still feels weird, and I think we should try to be flexible when things just look awkward or confusing. But as a general rule this could probably work.
#191-23. Since the node module doesn't have this as well it would be nice to do this in a follow up. Imho this is probably not a blocker.
#191-26. Done
#191-31 (and related). I Agree with slashrsm. Plugin is even more generic and not saying anything about it's function. There is actually a difference between media types and media handlers. Multiple types can implement the same handler (For example. Publication and Document media types implement File media handler). The handler part is only exposed when creating a new media type. Maybe the UX experts have a opinion on this? The correct technical term should be media handler plugin (in case we want to make a comparison with other plugins).
#191-32. Done, also renamed a lot of references in comments and labels to media handler.
#191-37. Done
#191-42. Removed it.
#191-46. Added todo, issue was already there.
#191-47+48. Added issue and todo's https://www.drupal.org/node/2843395.
#191-58+59. Sorry about that, I think I see what you mean now. I added back the getter and moved out the creation of the storage.
#191-78. Tried again..
Als got rid of all the references to bundle in comments and variable names (where possible).

Thanks Wim Leers for taking the time to discuss this :)

seanB’s picture

Fixed #191-82 and #191-87 by adding assertCacheContext to MediaAccessTest and a new MediaCacheTagsTest class.

Tried to remove the auto_create_source_field property as well, but after getting a bunch of test failures decided this needs a little more investigation. When adding a media type through the interface, the source field can never be empty. It makes sense that the source field should always be passes through handler_configuration or just create it automatically when that is not the case. Maybe slashrsm can provide some more info on this?

effulgentsia’s picture

Thanks, everyone, for all the great work getting this patch to where it is. I think it would be awesome for it to make it into 8.3. I haven't reviewed the code in depth enough to remove the "Needs framework manager review" tag from it yet, but I'm posting this comment with screenshots in order to cover what are currently my biggest UX concerns with the module. There are UX team meetings on Tuesdays, and I've already pinged @webchick to get this on the agenda for tomorrow.

I mentioned in #2825215-28: Media initiative: Essentials - first round of improvements in core that although that issue has been referred to as the minimally viable product (MVP) for core media, I don't see a way of getting all of those pieces into 8.3. Which means if we commit the patch here, then by definition we end up with something less than what is minimally viable. And in #139, @Berdir gives compelling reasons for why Media module needs to be released as non-experimental. Which puts us in a bit of bind: we've never yet released a brand new (to core) module in a minor release without it first existing for at least one minor release as experimental. And here, we want to do so for something we're saying is less than minimally viable.

Because of this, @slashrsm said in #2825215-29: Media initiative: Essentials - first round of improvements in core that he prefers punting this to 8.4. That's of course an option. However, if there's value (for contrib maintainers and/or for the core product) to getting this into 8.3 core, then I think it makes sense to do so as long as it:

  1. Solves some clear use-case on its own.
  2. Doesn't pollute the UI with unclear options for people when they're not trying to solve that use-case.
  3. Can be cleanly extended by contrib to solve that use-case better and/or solve additional use-cases.

I think the use-case that this patch (when combined with #2831936: Add "File" MediaSource plugin and nothing else) solves is separating the steps of uploading a file to the site from referencing that file from within content. Which allows for 3 nice things:

  • Someone who is not authorized to upload files can reference a media entity containing a file uploaded by someone else.
  • Multiple pieces of content (nodes) can reference the same media entity (and via that, the same file).
  • That media entity can be edited, and a newer version of the file uploaded. All content that references that media entity automatically implicitly references that newer file.

There are various situations where this is handy. For example, a school where only admissions administrators should be allowed to upload admissions forms (PDF files), but where it's useful to allow content editors to link to those documents. Plenty of other use cases too, but mostly I'm pointing out that there's a useful use case here, even if just limited to files and not images or remote media.

With that in mind, here's feedback for how we can solve that use-case cleanly without polluting the UI for others. Feedback for each screenshot is provided below the screenshot.


With just this patch alone, it's impossible to create a media type, because a required field can't be filled out. Normally in a situation like this, I would say that we need to merge #2831936: Add "File" MediaSource plugin into this patch, since this module can't stand on its own without it, so we shouldn't commit them separately. But, @catch and @xjm pointed out that alternatively we could set this module to hidden: true in the info.yml file. And remove that line in #2831936: Add "File" MediaSource plugin. I'm fine with that approach if people prefer reviewing them separately rather than together. The rest of this review is with #2831936: Add "File" MediaSource plugin applied.


Question for the UX team: are we sure we want "Media types" within "Structure"? Or should we move it to within Configuration -> Media?


This screenshot shows that when I add a new field somewhere (in this case to the Article content type), I'm presented a bunch of choices, and File and Media are two separate choices within the "Reference" section. How do I know how these differ? Some options for how to fix this:

  • Move both File and Image out of the Reference section and into the General section, since they don't really function like references from a content modeling perspective (you can't reference them from more than one place).
  • Rename Media (just within this screen, not necessarily other places) to "Reusable Media" or similar.
  • Remove "Media" from this screen entirely, and thereby require people to click on "Other..." in order to create a Media reference field.

My recommendation is #1.


This is the screenshot for what happens when you click "Other...". Again, both File and Media are options. How do I know which one I want? Here's some options for fixing this:

  • Remove File from this screen. It's actually broken. If you select it, you get an ER field to File entities (not a File field, like you do if you picked File from the previous screen), which then lets you reference files that were uploaded to other nodes. But usage from such a reference isn't tracked, so if you delete the original node, you lose the file from these other places. Now that we have Media as an option, there's no reason to choose File on this screen.
  • Rename Media (just within this screen, not necessarily other places) to "Reusable Media" or similar.

My recommendation is #1.


This screenshot shows the /admin/content page, which has a Files tab for listing the files on the site, but no Media tab for listing the Media entities. I think either in this patch, or in a followup, we should add a simple View to show the media entities. #2834729: Create an MVP for adding and re-using Media, whether in core as an experimental module, or in contrib, can replace the simple View with a much nicer library UI, but even without such a module installed, I think we need to have a simple listing.


This screenshot shows an "Add media" link in the Tools menu, just under "Add content". I think that's too prominent a link for a module with such a narrow use case. We could consider whether to add this link into 8.4, when the module does more, but for now, I think we should remove it from here. And instead, just have an "Add media" primary action on the /admin/content/media listing I recommended above.


This screenshot shows that the regular entity reference select widget works just fine for a simple use-case. People can install contrib modules for other widgets.


This screenshot shows that the regular "Rendered Entity" formatter already in core can successfully display what needs to be displayed for a simple use-case. People can install contrib modules for other formatters.

I'm curious to hear what the UX and Media teams think of this feedback. Thanks.

andypost’s picture

Some nits in code

  1. +++ b/core/modules/media/config/install/core.entity_view_mode.media.full.yml
    @@ -0,0 +1,9 @@
    +label: 'Full content'
    

    Naming is strange but looks separate task

  2. +++ b/core/modules/media/media.api.php
    @@ -0,0 +1,25 @@
    + * Alter the information provided in \Drupal\media\Annotation\MediaHandler.
    

    Alter[s]

  3. +++ b/core/modules/media/media.install
    @@ -0,0 +1,25 @@
    +function media_install() {
    +  $source = drupal_get_path('module', 'media') . '/images/icons';
    +  $destination = \Drupal::config('media.settings')->get('icon_base_uri');
    +  if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
    +    throw new \RuntimeException("Unable to create directory $destination.");
    

    this may lead to exception while installing and probably better to use state or hook_requirements() instead of exception

  4. +++ b/core/modules/media/media.module
    @@ -0,0 +1,117 @@
    +      'template' => 'media',
    

    you can remove it, theme hook will set it for you

  5. +++ b/core/modules/media/media.permissions.yml
    @@ -0,0 +1,26 @@
    +update any media:
    +  title: 'Update any media'
    +
    +delete media:
    +  title:  'Delete own media'
    +
    +delete any media:
    +  title:  'Delete any media'
    +  restrict access: TRUE
    

    updated any media is not restricted?

  6. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,171 @@
    +   * Default settings for this media type.
    +   *
    +   * @var array
    +   */
    +  protected $settings;
    

    is this used?

  7. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,341 @@
    +   * The entity being created or modified.
    +   *
    +   * @var \Drupal\media\MediaTypeInterface
    +   */
    +  protected $entity;
    

    defined already in parent class

seanB’s picture

Here are some thought on #200. Hopefully I addressed everything.

  • Change 'Media type entities' to 'Media type' in the breadcrumb in /admin/structure/media/add, I think this is just something we missed.
  • Add module as 'hidden' for now, and remove that again in #2831936: Add "File" MediaSource plugin. This patch is blocking progress on everything else. If we can avoid adding more features here by adding it as hidden that would help move things forward.
  • I think having 'Media types' in 'Structure' is more logical, since content types and comment type are there as well.
  • When adding a field move the 'File' and 'Image' options from 'Reference' to 'General' seems like the best choice to me as well. Maybe it is better to do this in a seperate issue since it is a good idea, even without getting media in.
  • When adding a ER field and clicking 'Other', removing 'File' from the list of types might be solved by the previous point. If not, it should be solved within the same, preferably seperate, issue.
  • Renaming 'Media' to 'Reusable media' when choosing a type for a ER field is inconsistent with all the other types. Technically it should then also be 'Reusable content' etc. Creating a reference field also implies that you are reusing something that already exists.
  • I agree we need to add a 'Media' tab to admin/content. In issue #2834729: Create an MVP for adding and re-using Media there is progress on this point. We could split that issue up to create the field widget seperate from the admin/content/media overview. The basic overview should be part of the main media module imho.
  • I agree we should remove 'Add media' from 'Tools'

This issue/patch should solve:
I hope we can agree to do the following to get this done, but please let me know if there are good reason to add or remove some of these points in this patch:

  1. Change 'Media type entities' to 'Media type' in the breadcrumb in /admin/structure/media/add, I think this is just something we missed.
  2. Add module as 'hidden' for now, and remove that again in #2831936: Add "File" MediaSource plugin. This patch is blocking progress on everything else. If we can avoid adding more features here by adding it as hidden that would help move things forward.
  3. I agree we should remove 'Add media' from 'Tools'

Other issues/followups should solve:

  • When adding a field move the 'File' and 'Image' options from 'Reference' to 'General' seems like the best choice to me as well. Maybe it is better to do this in a seperate issue since it is a good idea, even without getting media in.
  • When adding a ER field and clicking 'Other', removing 'File' from the list of types might be solved by the previous point. If not, it should be solved within the same, preferably seperate, issue.
  • I agree we need to add a 'Media' tab to admin/content. In issue #2834729: Create an MVP for adding and re-using Media there is progress on this point. We could split that issue up to create the field widget seperate from the admin/content/media overview. The basic overview should be part of the main media module imho.
naveenvalecha’s picture

Thanks for the reviews.Here's the attached patch.
Addressed #202
#202.1 Filed a follow-up #2844206: [PP-1] Naming is strange but looks separate task
#202.2 Done
#202.3 Done. Added a hook_requirement.
#202.4 Agreed. Done.
#202.5 Let's file a follow up to discuss. Node module also does not provide restriction for it. Let's file a followup issue to discuss how it should be handled for all content entities http://cgit.drupalcode.org/drupal/tree/core/modules/node/src/NodePermiss...
#202.6 Yup Removed.Also removed $entity which is already defined in the parent class.
#202.7 Done. Removed.


#203.3 Removed the 'Add media' from 'Tools'
TODO:
#203.1
#203.2
#203 : Amen for #200

// Naveen

naveenvalecha’s picture

Just adding hidden:true for the media module so that it will not visible to site owners until #2831936: Add "File" MediaSource plugin
// Naveen

Wim Leers’s picture

Having reviewed #2831936: Add "File" MediaSource plugin for how it integrates with this patch, and what problems it might have, I once again see further confirmation of my concerns about calling the @MediaHandler plugins "handlers". What they really are, are media types: image, file, youtube, instagram, et cetera. But this patch already reserves the concept of Media types as the name/label/concept/description of bundles of media entities, i.e. for the bundle config entity type.
That leaves me with two conclusions:

  1. remove the concept of media type config entities altogether: automatically create bundles for each @MediaHandler plugin. Because they always correspond. There are no two different bundles (Media Type config entities) both using the file @MediaHandler plugin. As @slashrsm's screencast even demonstrates at the 2:07 mark: https://youtu.be/4Q9fE043GIM?t=2m7s.
    I know you could create Patches and PDFs that would both use the file @MediaHandler plugin, and funny videos+Drupal screencasts both using the youtube @MediaHandler plugin, but is that really a common case? If not, why all this complexity?
  2. this then opens the door to renaming @MediaHandler to @MediaType. Or perhaps, @MediaTypeProvider.

I'm assuming my first point will elicit very strong negative reactions. Remember I'm just trying to do due diligence here, and playing devil's advocate. It is essential that we question every architectural decision that significantly increases complexity, hence worsening supportability/maintainability.

In fact, that second point is something I think we can do anyway: renaming @MediaHandler to @MediaTypeProvider, even if we keep these MediaType config entities.

(See #2831936-54: Add "File" MediaSource plugin and #2831936-56: Add "File" MediaSource plugin.)

phenaproxima’s picture

I am strongly against renaming the MediaType config entities. They are very similar in purpose and function to node types and block_content types, and therefore should follow similar nomenclature. Changing their name is asking for confusion and big trouble down the line. It will only exacerbate Drupal's existing naming problems. I am also against the idea of making media handlers directly responsible for bundles. Bundles are like stored configuration for the handlers. Even if it's not a common use case to have more than one bundle per handler, the flexibility should be there for a few reasons:

  • This is Drupal, monarch of flexibility. 2+ bundles for a handler is an edge case, but it's one the architecture already accounts for. And by Drupal standards, it's not a particularly exotic architecture, IMHO. Why rock the boat?
  • Changing it would be a disruption of seismic proportions. It was hard enough just to get handlers to create and be responsible for fields, never mind entire bundles.
  • Assigning too many responsibilities to media handlers invites trouble.

Regarding handlers -- I think the word "handler" perfectly describes what they do. They handle the business logic for a media entity type. "Provider", in my mind, does not imply that at all. However, because handlers are developer-facing and never really exposed to normal users, I'd be willing to compromise on renaming these. I'd much rather stick with "handler", but in the interest of moving forward, I won't push back as hard against changes in this area.

But I'm dead-set on referring to media types as "media types".

Wim Leers’s picture

I never suggested renaming MediaType entities. I say in #206.1 that Media Type config entities are mostly pointless, and in #206.2 I say that @MediaHandler plugins should be renamed.

phenaproxima’s picture

From #206.2:

this then opens the door to renaming @MediaHandler to @MediaType

If we renamed MediaHandler to MediaType, we'd likely have to rename MediaType to something else to avoid confusion. :)

Gábor Hojtsy’s picture

@wimleers: no, image is a kind of data a media entity type can hold. There may be media entity type for "ads", "illustrations", etc. that all use the image handler (are image based) but they have different uses, display mode configuration, administration UI, etc. The media types concept is separate from the media handler concept because these do not necessarily correspond 1-1 in ambitious digital experiences (to borrow Dries' wording). Yes, by default it makes sense to ship with a default type for them, but that does not mean there should only be one type for each handler used.

seanB’s picture

I think the case for having multiple media type with the same handler is presented very strong. So +1 for just leaving the media type vs media handler as is.

About renaming the concept of media handler to media provider: there was some discussion about this and the choice for handler was something we agreed on. It would be nice if more people could give their opinion on this.

Personally I think media handler describes best what it actually does. There are already a lot of different types of handlers for all kinds of things. Since it is not ambiguous the word handler should always contain more context about what kind of handler it is. So referring to it as media handlers solves this. The concept of providers or plugins have the same issue, you should always refer to media provider or media plugin, so renaming doesn't help all that much imho.

Gábor Hojtsy’s picture

Re two of Alex's review points:

Change 'Media type entities' to 'Media type' in the breadcrumb in /admin/structure/media/add, I think this is just something we missed.

No, this is the same problem as in #2811407: "Workflow entities" admin page title should be "Workflows" and requires either the same backend change / feature at #2767025: Add entity type label for a collection of entities or a stub route provider to fix the page title also. That will in itself fix the breadcrumb. I think this was put on hold in hopes of the backend change. I put the release manager review tag on it, so we can see if that is even feasible. We can add a stub route provider but then removing that later may or may not be considered a backwards compatibility / API change.

When adding a ER field and clicking 'Other', removing 'File' from the list of types

IMHO this could result in data problems for existing sites, if you go edit an existing ER field that has a file reference, would that show the file option still or would it break after this change? Either way this would need to be done very delicately. BTW overall the goal was to get rid of the file and image field eventually in favor of media yes.

Wim Leers’s picture

#193.10: wow, WTF!


#198:

  • 13: agreed on that being worse for security too. If we think it's not a blocker, then let's at least add a @todo pointing to a follow-up, and that follow-up's issue summary should list the concerns to be addressed
  • 16: I think a UX person needs to approve the use of "media" vs "media item" then.
  • 23: then let's create that follow-up. Pointing to node module not having specific test coverage is no excuse, node module long predates Drupal having any tests at all. Plus, we're doubling this sort of problematic surface area then, better to just avoid that becoming a BC nightmare in the future.
  • 31: see #206 for further thoughts about the naming of media module's plugin type. Your description only convinces me more that media handler is a poor plugin type name because it's overly generic. I think media type provider or even media backend, media storage, media type storage would make more sense. Because, what is a handler? file/image/youtube/instagram/… aren't "handlers", they're backends, storage, providers, that sort of thing. Hence I think it's quite obvious that handler is a bad name. Unclearly named concepts lead to unclear code and to poor maintainability. As I've pointed out in #206 and the comment that that points to repeatedly.
  • 42: yay!
  • 58+59: ok, so back to where it was. But the "still TODO" section doesn't mention this. It's STILL TODO for sure.
  1. +++ b/core/modules/media/media.install
    @@ -15,7 +15,7 @@
    +  $files = file_scan_directory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/');
    

    jpg+jpeg/gif should never be used for icons (because JPEG is lossy and GIF is less efficient than PNG-8). Only SVG or PNG.

  2. +++ b/core/modules/media/media.module
    @@ -2,7 +2,7 @@
    - * Provides media entities.
    + * Provides media items.
    

    Well thats' not all this module does, is it?

    Probably best to copy/paste what's in media.info.yml.

  3. +++ b/core/modules/media/src/Entity/Media.php
    @@ -137,12 +137,13 @@
    +    // Try to set fields provided by the media handler and mapped in
    +    // media type config.
    

    Nit: 80 cols.

  4. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -235,8 +226,9 @@
    +    // If the media handler uses a source field, we'll need to store
    +    // its name before saving. We'd need to double-save if we did
    +    // this in postSave().
    
    @@ -255,28 +247,28 @@
    +    // If the media handler is using a source field, we may need to save
    +    // it if it's new. The field storage is guaranteed to exist already
    +    // because preSave() took care of that.
    ...
    +      // If the field is new, save it and add it to this media type's view
    +      // and form displays.
    

    Nit: 80 cols.


#199:
Thanks for adding that test coverage!

I'm glad you tried to remove auto_create_source_field (which is what I was getting at in #191.41, .58 and .59. You got stuck, that's fine. This is now blocked on @slashrsm's feedback.

  1. +++ b/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php
    @@ -0,0 +1,86 @@
    +class MediaCacheTagsTest extends EntityWithUriCacheTagsTestBase {
    

    Hurray! :)

  2. +++ b/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php
    @@ -0,0 +1,86 @@
    +    // Give anonymous users permission to view media, so that we can
    +    // verify the cache tags of cached versions of media items.
    

    Nit: 80 cols.

  3. +++ b/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php
    @@ -0,0 +1,86 @@
    +      'label' => $id,
    

    Supernit: I'm slightly disappointed that this doesn't use the label 'Llama' like all other entity cache tags tests :P


Now reviewing #200#205.

Gábor Hojtsy’s picture

31: see #206 for further thoughts about the naming of media module's plugin type. Your description only convinces me more that media handler is a poor plugin type name because it's overly generic. I think media type provider or even media backend, media storage, media type storage would make more sense. Because, what is a handler? file/image/youtube/instagram/… aren't "handlers", they're backends, storage, providers, that sort of thing. Hence I think it's quite obvious that handler is a bad name. Unclearly named concepts lead to unclear code and to poor maintainability. As I've pointed out in #206 and the comment that that points to repeatedly.

So originally media types were media bundles and media handlers were media type plugins. That went all the way up to #79 onwards when in discussion with @bojanz, @tkoleary and several others the name handler was suggested both from a UX and a backend perspective. That followed by a team discussion where everyone on the media sprint agreed handler was the way to go. Reviewers like @tim.plunkett, etc. did not raise this concern before.

From my (user) point of view, image/file, etc. are not media providers, they are containers/storage for media, I provide the media (upload the file, provide the youtube URL for Drupal). They are also not storage from Drupal's perspective, handlers use a field to store the media data on the entity (eg. youtube URL for youtube or the file itself for local files). So the handler does not store anything. All it does is handles what the user provided and uses the field system and integrates with media entity to manage the kind of media it can handle.

slashrsm’s picture

Totally agree that we should not remove media types. We have a very clear use case for them in core. Take #2831944: Implement media type plugin for remote video for example. The current idea it to implement it through an oEmbed handler. oEmbed supports so many different things that it would be very hard to justify to throw all of them into one bundle. It is extremely likely that a YouTube video will require quite different set of fields than a Blackfire.io graph.

see #206 for further thoughts about the naming of media module's plugin type. Your description only convinces me more that "media handler" is a poor plugin type name because it's overly generic. I think "media type provider" or even "media backend", "media storage", "media type storage" would make more sense. Because, what is a "handler"? file/image/youtube/instagram/… aren't "handlers", they're backends, storage, providers, that sort of thing. Hence I think it's quite obvious that "handler" is a bad name. Unclearly named concepts lead to unclear code and to poor maintainability. As I've pointed out in #206 and the comment that that points to repeatedly.

"Backend" and "storage" are bad suggestions in my opinion since they *wrongly* describe what the plugin does. It does not store media but it works with it. It is aware about its specifics and translates them into a form that is understandable to the media entity. It *handles* them. "Type storage" is even worse as it incorrectly indicates that it handles storage of the type, which is absolutely not true. "Provider" is better, but not vastly superior than what we currently have in my opinion.

Confusion that @Wim Leers identified in #2831936-56: Add "File" MediaSource plugin was mainly caused by the rename from bundle/type to type/handler as the issue summary in the other issue wasn't correctly updated. We should have done better job keeping everything up to date, but the community also needs to understand that it is sometimes hard to do so when you're dealing with so many big changes in such a big ecosystem.

Tried to remove the auto_create_source_field property as well, but after getting a bunch of test failures decided this needs a little more investigation. When adding a media type through the interface, the source field can never be empty. It makes sense that the source field should always be passes through handler_configuration or just create it automatically when that is not the case. Maybe slashrsm can provide some more info on this?

This is essentially about detecting whether we're saving a type through the UI (in which case source field should be automatically created) or in some other way (config import, programmatically, ... - in which case it should not be created automatically). If we find a better way of reliably detecting that I'm totally in support of removing it.

Supernit: I'm slightly disappointed that this doesn't use the label 'Llama' like all other entity cache tags tests :P

I think that Llama definitely needs to be there! :)

Tests! Yay!

slashrsm’s picture

+++ b/core/modules/media/media.install
@@ -11,15 +11,47 @@
+    $source = drupal_get_path('module', 'media') . '/images/icons';
+    $destination = 'public://media-icons/generic';
+    file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);

Destination should not be hardcoded, since it is configurable. But... config is obviously not imported yet at this point.

I am not sure how to solve that.

andypost’s picture

My 5c for Media entity

- it always confusing "bundle" field (somehow not defined in baseFieldDefinitions() that ER to "media type"
- there was some limitations on "bundle" name for fields (just can't find issues) in entity api (at least at early days of d8)
- handler as plugin can have a fallback but that maybe not in scope so we can end-up with media types that have their "handlers" uninstalled... just not checked
- the most confusin is to write code - $media->bundle->... causes autocomplete with bundle() method and DX--

#210 greatly explains why there's no direct mapping of handler plugin to bundle (btw entity api allows dynamic bundles) but "handler" still confusing.

Why plugin instance using Handler name so I'm +1 on find better name!
Looking at MediaHandlerInterface it's about provide fields, mappings and manage thumbnails it does not handle anything it provides...

Wim Leers’s picture

#200:

  • +1 for all annotated screenshot concerns.
  • Move both File and Image out of the Reference section and into the General section, since they don't really function like references from a content modeling perspective (you can't reference them from more than one place).

    This is not true. It's totally possible to reference the same File or Image. It's just that with the default widgets, you can't do that. It's totally possible to create a widget that allows you to select an existing file/image entity. In fact, that's how we originally envisioned getting a MVP media library in, long before this Media inititiative.

  • But usage from such a reference isn't tracked, so if you delete the original node, you lose the file from these other places.

    This surely is just a bug that must be fixed, and not intentional?

  • Now that we have Media as an option, there's no reason to choose File on this screen.

    Why shouldn't I be able to still choose File? What if I don't use Media, because my site is already built? i.e. this must be implemented in the Media module altering the File/Image modules. EDIT: seems Gábor raises a similar point in #212.


#203:

  • I think having 'Media types' in 'Structure' is more logical, since content types and comment type are there as well.

    This is ignoring the fact that we already have a separate Media submenu. Which seems like a clearly better fit. The same does not apply to Comment Type. So +1 for #200's suggestion.

  • +1 for concerns about Reusable media

#209: Well, yes. But I gave multiple suggestions. Also see #213.198.31 for even more suggestions.


#211:

  • RE: media type config entities. I'm not convinced there is a strong case, but there definitely is a case. I'm fine with that. However, then we should update \Drupal\media\MediaHandlerInterface and \Drupal\media\MediaTypeInterface to have very clear explanations in their class docblocks to explain their responsibilities. And have them point to each other. This is a must-have for maintainability. And it'd have prevented me from even raising this point :)
  • RE: renaming the plugin type. See #213.198.31 for further argumentation/suggestions. Adding to what's there: "handler" is used in only two places:
    1. for entity types (access control handler, storage handler, et cetera)
    2. in Views, where to the best of my knowledge the intent was for it to be renamed to "plugins", but this wasn't done due to time constraints. Hence the only reason they're called "handlers" is because that's what it was called in the D7 Views contrib module

    So aside from the handler suffix being unnecessarily vague IMHO, it's also exceedingly strange to see: every single other plugin type has a more specific term than handler, even Views handlers (confusingly, Views handlers are a "category" of plugins if you will).

    So let me repeat what I wrote #213.198.31 to have it all together here:

    see #206 for further thoughts about the naming of media module's plugin type. Your description only convinces me more that media handler is a poor plugin type name because it's overly generic. I think media type provider or even media backend, media storage, media type storage would make more sense. Because, what is a handler? file/image/youtube/instagram/… aren't "handlers", they're backends, storage, providers, that sort of thing. Hence I think it's quite obvious that handler is a bad name. Unclearly named concepts lead to unclear code and to poor maintainability. As I've pointed out in #206 and the comment that that points to repeatedly.


#215:

Reviewers like @tim.plunkett, etc. did not raise this concern before.

Probably because there were bigger problems to address first then :)

From my (user) point of view, image/file, etc. are not media providers, they are containers/storage for media, I provide the media (upload the file, provide the youtube URL for Drupal).

Exactly! So then @MediaStorage plugins for file, image and youtube would make sense, wouldn't they?
By that same reasoning (I provide media), Media handler is a wrong name, because the user is what handles the media. The plugin is what stores the media.

They are also not storage from Drupal's perspective, handlers use a field to store the media data on the entity (eg. youtube URL for youtube or the file itself for local files). So the handler does not store anything.

AFAICT this is not entirely true: interface SourceFieldInterface extends MediaHandlerInterface — hence not every media handler must be storing a field.

(Thanks for bringing that to my attention, it made me see another problem. interface SourceFieldInterface extends MediaHandlerInterface strongly suggests \Drupal\media\MediaHandlerBase should not implement SourceFieldInterface, or it should be renamed. It also suggests that SourceFieldInterface should be renamed to MediaHandlerWithSourceFieldInterface.)

Your description makes it sound like all we do in @MediaHandler plugins is handling/interpreting metadata. Is it a proxy of some kind?

All this makes me realize a very simple thing: what is it actually that @MediaHandler plugins do? What are their responsibilities? They seem to be very glue-like, but glue between which things? What we're missing is documentation on \Drupal\media\MediaHandlerInterface that documents, explains and rationalizes all this. Doing that will help us determine if we should rename them, and if so, to what.

To be clear: I'm not 100% against @MediaHandler as a name. But I see dozens and dozens of alarmbells: from the vagueness of the name, to core not using this in any plugin name, to "handlers" being the old/original name of "plugins", to Media module developers themselves confusing this all of the place, to the responsibilities not being clearly documented, and so on. I just want to make sure we get the name right, because we'll be exposing this concept in the UI of every Drupal site.

Wim Leers’s picture

#216:

  • RE: Media Type config entities. Ah, that's one of the clearest justifications yet! So let's add that to \Drupal\media\Entity\MediaType :)
  • RE: "backend" and "storage" being bad suggestions etc: then please address #219.215: document the responsibilities of @MediaHandler plugins. That will make this discussion a lot more productive, and it might indeed just confirm the current name :)
  • RE: auto_create_source_field. Thanks for the explanation. But I can't help but wonder: when is auto_create_source_field set to FALSE? AFAICT it's always set to TRUE? Or perhaps it's only happening when saving a new MediaType config entity? If so, we should change if ($this->auto_create_source_field && $handler instanceof SourceFieldInterface) { to if ($this->isNew() && $handler instanceof SourceFieldInterface) {. Then we can simply drop auto_create_source_field?

#218

  • RE: handler uninstallation, I've been wondering about that too. We need a test that sets up a Media Type config entity with \Drupal\media_test_handler\Plugin\media\Handler\Test as the media handler, and then tries to uninstall it. That should be impossible because the Media Type config entity's config should be depending on the media_test_handler module.
  • RE: "handler" naming confusion: glad to hear I'm not alone!
slashrsm’s picture

RE: auto_create_source_field. Thanks for the explanation. But I can't help but wonder: when is auto_create_source_field set to FALSE? AFAICT it's always set to TRUE? Or perhaps it's only happening when saving a new MediaType config entity? If so, we should change if ($this->auto_create_source_field && $handler instanceof SourceFieldInterface) { to if ($this->isNew() && $handler instanceof SourceFieldInterface) {. Then we can simply drop auto_create_source_field?

It is set to FALSE by default:

+++ b/core/modules/media/src/Entity/MediaType.php
@@ -0,0 +1,295 @@
+  protected $auto_create_source_field = FALSE;

and changed to TRUE in MediaTypeForm::save():

+++ b/core/modules/media/src/MediaTypeForm.php
@@ -0,0 +1,334 @@
+  public function save(array $form, FormStateInterface $form_state) {
+    $this->entity->set('auto_create_source_field', TRUE);
+    $status = parent::save($form, $form_state);
RE: "backend" and "storage" being bad suggestions etc: then please address #219.215: document the responsibilities of @MediaHandler plugins. That will make this discussion a lot more productive, and it might indeed just confirm the current name :)

Working on this.

Gábor Hojtsy’s picture

@wimleers:

I think these are primarily the responsibility of handlers:

1. so handlers are dealing with a kind of data (eg. file handler deals with local files, image handler deals with local images, oembed handler deals with some kind of oembed source);
2. they have the responsibility to help media entity create the base field they need (eg. image field for image handler)
3. they deal with the metadata associated with the type of source data, eg. image handler can extract EXIF data from the file and may map it to (a subset of) fields created on the media type
4. they handle the thumbnail generation process for the media, eg. take a thumbnail from oembed and store locally or provide an icon for a local file type based on its file type (eg. pdf)

+ for future UX improvement handlers may provide the supporting data to make it easier to create the metadata (eg. EXIF) fields on the media type (or a subset of those fields) see #2836153: Improve metadata mapping UI on Media type form (because handlers know what kind of metadata can it extract/produce based on the provided media and the type of those metadata values)

Wim Leers’s picture

#221: RE: "set to FALSE by default" yes, I can see that, but it's set to TRUE in many places. I'm tempted to think all places. Hence my suggestion. Which you did not reply to.

#222 Let's see if @slashrsm also documents those 4 responsibilities (see the bottom of #221), or if there are more :)

slashrsm’s picture

First attempt to improve the docs of types and handlers.

#221: RE: "set to FALSE by default" yes, I can see that, but it's set to TRUE in many places. I'm tempted to think all places. Hence my suggestion. Which you did not reply to.

In "real" code it is set to TRUE only in the form. All other assignments belong to tests covering this functionality. As already mentioned in #216: this is not about new vs existing. It is about *how* media type is created (UI or some other way).

Wim Leers’s picture

Thanks, this is a solid step in the right direction :)

  1. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * Media handlers are the essential part of the media entity type as they
    

    "Media entity type" or "MediaType entity type"?

  2. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * implement the knowledge and business logic related to a given sort of media.
    

    s/sort/type/ ?

  3. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * Media entity doesn't have any knowledge anything about media it represents
    

    "Media entity" or "MediaType entity type"?

    This sentence is broken.

  4. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * Each Media type needs exactly one handler and a single handler can be used on
    

    s/and a/. A/

  5. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * many media types.
    

    Just now, you wrote "Media type", now it's "media types". Let's be consistent.

  6. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * Their main responsibilities are:
    

    Main? Doesn't this list all responsibilities?

  7. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * - defining how media is represented (stored),
    

    So it is about storage?

  8. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * - validating media before it is saved,
    

    s/media/a media entity/

  9. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * - providing default value for the media name field,
    

    s/default/a default/

  10. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * - providing metadata specific to the given media,
    

    s/media/media type/ ?

  11. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,9 +9,26 @@
    + * - handling mapping between metadata and entity fields.
    

    Which entity? The host entity, right? (I.e. the content entity on which a media field exists, hence updating fields that are siblings of a media field.)

  12. <code>
    +++ b/core/modules/media/src/MediaHandlerInterface.php
    

    I think the documentation here can be improved further. I'm missing two things:

    1. coherence (the overall explanation, but also definitely the list of "main responsibilities")
    2. concrete examples

    So far, I only see this confirming my concerns: it's very hard to clearly explain the concept, because the concept is not clear (IMO). This suggests the concept is flawed, or needs clarification.

  13. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -8,6 +8,17 @@
    + * with same semantics. Media types are not about where media comes from. They
    

    s/with same/with the same/

    What I'm still missing is one or two concrete examples. The examples mentioned in the last several comments seem valuable to state here, because concrete examples make it easier to understand an abstraction.

  14. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -8,6 +8,17 @@
    + * are about the meaning that media has in context of a given Drupal site.
    

    s/meaning/semantics/
    s/in context/in the context/

    Also: this makes a ton of sense, this is an excellent explanation :)

  15. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -8,6 +8,17 @@
    + * level details, while types don't care about them at all. That said, media
    

    s/types/media types/

In "real" code it is set to TRUE only in the form.

  1. Can you give an example of where it remains set to FALSE?
  2. Can you explain why my suggestion (using isNew()) wouldn't work?
slashrsm’s picture

#225-1: Used "Media type".
#225-2: Used this on purpose to prevent conflicts with the media type (as in bundle).
#225-3: This is about media, not media type. Fixed the sentence.
#225-4: Done
#225-5: Of course. It is plural.
#225-6: Removed.
#225-7: No. It is about defining *how* it is stored. It is not *actually* storing it.
#225-8: Done.
#225-9: Done
#225-10: Done.
#225-11: Fields on media entity. Tried to explain it in more detail.
#225-12: Tried to improve it.
#225-13: Done. Added examples.
#225-14: Done. Great!
#225-15: Done.

Can you give an example of where it remains set to FALSE?

See my comment from #216:

This is essentially about detecting whether we're saving a type through the UI (in which case source field should be automatically created) or in some other way (config import, programmatically, ... - in which case it should not be created automatically). If we find a better way of reliably detecting that I'm totally in support of removing it.

So... It is set to FALSE when created programmatically and during config imports.

Can you explain why my suggestion (using isNew()) wouldn't work?

See above.

Wim Leers’s picture

#226: Thanks, that's another big step in the right direction :)

  1. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * Media handlers are the essential part of the media type as they implement the
    + * knowledge and business logic related to a given sort of media. Media entity,
    + * without a handler, doesn't have any knowledge about media it represents.
    

    This still sounds strange. Should be verified by a native speaker.

    I personally think (as a non-native speaker myself) that the following changes would make it better:
    - s/the essential/an essential/
    - s/the media type/a media type/
    - s/sort of media/kind of media/
    - Media entities, without a media handler retrieved from their media type, don't have any knowledge about the media they represent.

  2. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * Each Media type needs exactly one handler. A single handler can be used on
    

    s/Media/media/, for consistency with the rest.

  3. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * - SoundCould: handles SoundCould audio,
    

    s/SoundCould/SoundCloud/

  4. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * - Defining how media is represented (stored). Handlers are not responsible to
    

    represented (stored) … not responsible to actually store

    This is still a big contradiction.

  5. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + *   actually store the media. They only define how it is represented on a media
    + *   entity (usually using some kind of a field).
    

    This is a lot clearer already. Especially this portion: They only define how it is represented on a media entity (usually using some kind of a field).

    However, isn't the part between parentheses only true for SourceFieldInterface? I also brought this up in the parenthetical in #219.

  6. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * - Providing thumbnails. Handlers that are responsible for remote media will
    

    This one is now clear.

  7. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * - Validating a media entity before it is saved. Entity constraint system will
    

    This is kind of clear. Where it's not clear is that media handlers are only (optionally) adding entity validation constraints.

    It doesn't help that File nor Image media handlers are actually using this API.

    In fact, doesn't that mean this should be split off to a separate interface?

  8. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * - Providing a default value for the media name field. This will save users
    

    Clear now.

  9. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * - Providing metadata specific to the given media type. Remote media handlers
    

    Well not specific to the media type, but specific to the handler, right?

    And this is referring to \Drupal\media\MediaHandlerInterface::getField(), right? So apparently it's not a field (in the Entity API "Field" sense), but it's metadata. It just happens to be that that metadata may be mapped to an actual Entity API Field. That mapping is explained in the next point.

    Doesn't that mean that \Drupal\media\MediaHandlerInterface::getField() should be renamed to ::getMetadata(), and \Drupal\media\MediaHandlerInterface::mapFieldValue() should be renamed to ::mapMetadataToField()?

  10. +++ b/core/modules/media/src/MediaHandlerInterface.php
    @@ -9,21 +9,49 @@
    + * - Handling mapping metadata to the media entity fields. Metadata that handler
    + *   exposes can be automatically mapped to the fields on the media entity.
    

    Also clear, but see previous point.

In other words: let's make the MediaHandlerInterface more clear. Then it'll become clear automatically whether @MediaHandler/MediaHandlerInterface is the right name.

I'm looking into the "auto create source field" stuff.

slashrsm’s picture

This patch was discussed on yesterday's UX meeting: https://www.youtube.com/watch?v=LzU97QLUnQU

To summarize:

  1. On field add page move "Image" and "File" from the "Reference" section to "General"
  2. When adding a reference field remove "File" from the offered entity types.
  3. Add media listing view back.
  4. It shouldn't be just "Media" when referring to one entity. It should be "Media item".

I'd create follow-ups for 1. and 2. Attached patch adds 3 as it currently is in contrib version of the module.

Any thoughts on #91?

With the current approach you don't need any conditional display logic if the thumbnail is missing. It is also relatively easy to override default thumbnails. If we'd go with a dedicated view mode we'd need to handle all this in display logic which is not nice in my opinion. This was one of the main reasons why we went down this path initially.

Wim Leers’s picture

To get this issue back on track/more focused again, here are my three major concerns:

  1. Lack of architectural clarity in general. Because responsibilities are spread across:
    1. MediaType config entity type
    2. @MediaHandler plugin
    3. Media content entity type

    and the responsibilities of each not being clearly defined (being address right now in the preceding comments). Plus, most having some weirdness: MediaType having thumbnail-related methods (I'd expect them to be @MediaHandler-specific); @MediaHandler has lots of weird things (see next point).

    It's easily dismissed as technical nitpicking. But remember that we have to maintain this for years to come. It's considerably more complex than any other entity in Drupal core.

    EDIT: Finally, while writing all this, I realized one other severe limitation of this MediaType config entity: what if I have videos on both Vimeo and YouTube? Or images both locally and on Flickr? What if I want to treat them the same in the UI, i.e. have the same "type or category" of media, but just have them be stored in different places? Then they'd have to have the same MediaType config entity in the current architecture, but that's impossible. Won't that cause big UX problems too? I fear this will become a major usability complaint in the future.

  2. The confusing nature of @MediaHandler plugins in specific (which is being addressed right now in the preceding comments):
    1. Its name is confusing.
    2. Its purpose is unclear.
    3. There's SourceFieldInterface extends MediaHandlerInterface, but it seems all media handlers are implementing that interface, since it's also implemented by MediaHandlerBase.
    4. It's unclear why thumbnail handling must be part of the "root" interface. Why can't that be split off, much like SourceFieldInterface also is a separate interface?
  3. the auto_create_source_field stuff, and the associated fact that a getter may end up creating stuff. This affects the following:
    1. \Drupal\media\Entity\MediaType::preSave()
    2. \Drupal\media\Entity\MediaType::postSave()
    3. \Drupal\media\MediaHandlerBase::getSourceField()
    4. plus it causes the addition/existence of the following methods: \Drupal\media\MediaHandlerBase::createSourceFieldStorage() + \Drupal\media\MediaHandlerBase::getSourceFieldStorage() + \Drupal\media\MediaHandlerBase::createSourceField()

    Having looked into this more, I think that all this "create source field" logic actually only belongs in MediaTypeForm, i.e. in the UI code. Any developer that wants to deploy media fields, would do so by deploying configuration. So then the "automatic creation" simply is not necessary.

    AFAICT the only reason to have it be there, is to simplify certain tests (\Drupal\Tests\media\FunctionalJavascript\MediaUiJavascriptTest::testMediaTypes() + \Drupal\Tests\media\Kernel\BasicCreationTest). But really, it's the job of those tests to create any necessary configuration. We should not be complicating the design of the MediaType config entity for that, nor should we be making MediaHandlerBase more complicated for it.

    I'd be happy to be wrong, of course! If I am, I'd like the rationale for this architecture to be thoroughly documented though, because it looks like it would be difficult to maintain.

Berdir’s picture

Having looked into this more, I think that all this "create source field"
logic actually only belongs in MediaTypeForm, i.e. in the UI code. Any
developer that wants to deploy media fields, would do so by deploying
configuration. So then the "automatic creation" simply is not necessary.

AFAICT the only reason to have it be there, is to simplify certain tests

I was wondering about that too. That's what we did a while ago with the body field for node types. Only the Form and some test helpers create a body field when you create a new node type, and it used to be in postSave() or so.

Wim Leers’s picture

While working on #230 I found another problem.

  1. Create Media type that uses the File @MediaHandler. You get to see this:

    The user can't do anything wrong. Yay :)
  2. Create new Media type that uses the Image @MediaHandler. Now you get to see this:

    Now the user must make a choice:

    Sadly, there's only one correct choice: - Create -. If you pick the existing field, you won't actually be able to upload images.

Yes, default configuration helps. But it's still very easy to get yourself in very weird situations.

I again point to my concerns about the MediaType config entity + @MediaHandler plugin.

tim.plunkett’s picture

#230.1 I agree with this 100%

#230.2 I can see this being confusing, it doesn't bother me quite as much. If the separation between MediaType/Media/MediaHandler was resolved, I imagine this would be less problematic.

#230.3 Also agreed 100%

Finally, this change made my IDE much less confused about what was happening in \Drupal\media\Entity\Media :)

diff --git a/core/modules/media/src/MediaInterface.php b/core/modules/media/src/MediaInterface.php
index 1055434..9765ddc 100644
--- a/core/modules/media/src/MediaInterface.php
+++ b/core/modules/media/src/MediaInterface.php
@@ -35,7 +35,7 @@ public function setCreatedTime($timestamp);
   /**
    * Returns the media handler.
    *
-   * @return $this
+   * @return \Drupal\media\MediaHandlerInterface
    *   The media handler.
    */
   public function getHandler();
jonathanshaw’s picture

@WimLeers asked for a native English speaker to look at the MediaHandlers help text. I did this, changing the introductory paragraph completely and making a few grammatical corrections and clarifications elsewhere. Sorry for not providing this as patch and interdiff:

Media handlers provide the critical link between Media entity items and the actual
media itself, which typically exists independently of Drupal. Each media handler
works with a certain kind of media. For example, local files and Youtube videos can
both be catalogued in a similar way as Media entity items, but they need very different handling to actually display them.

Each Media type needs exactly one handler. A single handler can be used on
many media types.

Some examples of possible handlers are:
- File: handles local files
- Image: handles local images
- oEmbed: handles resources that are exposed through the oEmbed standard
- YouTube: handles YouTube videos
- SoundCould: handles SoundCould audio
- Instagram: handles Instagram posts
- Twitter: handles Tweets
- ...

Their responsibilities are:
- Defining how an individual media is identified. Handlers are not responsible for
actually storing the media. They only define how it is represented on a media
entity (usually using a field of some type), for example as a URL field for Youtube
videos or a File field for local files.
- Providing thumbnails. Handlers that are responsible for remote media typically
fetch the image from the 3rd party API and make it available locally.
Handlers that represent local media (typically files) will usually generate an image.
Both also fall back to a default thumbnail if it cannot be otherwise obtained.
- Validating a media entity before it is saved. The entity constraint system is
used to ensure the media entity has a valid structure. For example, handlers that
represent remote media might check the URL or other identifier, while handlers
that represent local files might check the MIME type of the file.
- Providing a default value for the media name field. This saves users from manually
entering the name when it can be reliably set automatically. Handlers
for local files typically use the filename, while a handler for a remote media
might for example obtain a title attribute through the 3rd party API. The name can
always be overridden by the user.
- Providing metadata specific to the given media type. For example, remote media
handlers generally get information available through the 3rd party API and make
it available to Drupal, while local media handlers can expose things such as
EXIF or ID3.
- Mapping metadata to the media entity fields. Metadata that a handler
exposes can be automatically mapped to fields on a media entity. Handlers are able
to define how this should be done.

Wim Leers’s picture

Discussed #230 during the media meeting.


#230.3: Everybody agrees with this direction: @slashrsm, @seanB, @phenaproxima, @Berdir. @phenaproxima has volunteered to take this on. WOOOT! One major concern will likely evaporate :)


#230.1: Specifically, this portion:

EDIT: Finally, while writing all this, I realized one other severe limitation of this MediaType config entity: what if I have videos on both Vimeo and YouTube? Or images both locally and on Flickr? What if I want to treat them the same in the UI, i.e. have the same "type or category" of media, but just have them be stored in different places? Then they'd have to have the same MediaType config entity in the current architecture, but that's impossible. Won't that cause big UX problems too? I fear this will become a major usability complaint in the future.

This capability is supported, in the following way:

  1. responsibility of the user to split up "anything goes" media handlers like oEmbed to multiple media types
  2. so then the assumption is: "every media type contains one kind of media, so not e.g. mixing audio and video or video and images
  3. given that, if there is a need to list all videos, or all images, regardless of media type (e.g. across YouTube + Vimeo), then you can set up a vocabulary, and have a term reference field on a MediaType with a "video" term default value, and bam, you're done

Of course, this scenario/use case/solution needs to be documented. And the assumption that is associated with MediaType(Interface) must also be documented.

Also, it makes me wonder why not every media entity simply has a MIME type property?


#230.2: No actual progress on this yet. Here's the relevant portions from the IRC meeting. I can't really summarize this very well, because it doesn't have a conclusion yet.

16:00:38 <WimLeers> So I think that's the best angle to talk about this:
16:00:51 <WimLeers> - why is SourceFieldInterface separate, yet the thumbnail stuff is not?
16:01:13 <WimLeers> - why can't we split up MediaHandlerInterface so that all optional things (e.g. entity validation constraints) are in an optional interface?
16:01:28 <WimLeers> (btw the entity validation constraints stuff is completely unused by the base media entity patch + file plugin + image plugin)
16:01:33 <slashrsm> WimLeers: SourceFieldInterface is separate because we didn't want to assume that every handler relies on one field exactly for storage. There might be handlers that need more.
16:01:45 <WimLeers> Yep, and that's great
16:01:47 <slashrsm> WimLeers: However, it is also true that we didn't run into any that would need that yet.
16:01:53 <WimLeers> ok
16:02:02 <phenaproxima> WimLeers: As I understand it, thumbnails are a root part of media entities. They're a base field. And I think that is reasonable, from a UX standpoint. All media items should be visually representable.
16:02:10 <WimLeers> slashrsm: so can you describe the hypothetical case where SourceFieldInterface would not be necessary?
16:02:19 <WimLeers> phenaproxima: see Sam's remark at #91
16:02:30 <WimLeers> He wrote:
16:02:30 <WimLeers> Should thumbnails be part of the media type interface? It doesn't get satisfied for document, audio, tweet etc. Would a "preview" view mode be a more generic, non-assuming concept to use for the same purpose. It would take a lot of complexity out of the module.
16:03:16 <seanB> WimLeers: #1 The alternatives for naming are imho still unclear, because the whole concept of what a media handler does is new to core. That brings up the purpose in #2, which we should definitely make clear through documentation. I'm not sure if we can do more then explain the concept to make it more easy to use?
16:03:19 <slashrsm> WimLeers: I can come up with an example. But none of the examples became a reality yet.
16:03:45 <slashrsm> WimLeers: See my answer to Sam's comment one or two comments lower.
16:04:29 <slashrsm> WimLeers: So... it might be OK to kill that interface and just move it into main handler interface. We should think about it a bit, but real life kind if indicates that this might be OK.
16:07:03 <WimLeers> slashrsm: heh
16:07:09 <WimLeers> slashrsm: that's the opposite of what I expected
16:07:22 <WimLeers> slashrsm: my intent was that we should reduce MediaHandlerInterface even further
16:07:40 <WimLeers> slashrsm: so that we have multiple interfaces, and you just pick the ones you need for your usecase
16:07:53 → timplunkett joined (~timplunke@c-73-188-162-88.hsd1.pa.comcast.net)
16:08:06 <slashrsm> WimLeers: I can't really extract thumbnail and fields logic out of that. Media expects that to be there.
16:08:09 <WimLeers> slashrsm: i.e. thumbnail seems to belong in a separate interface, for the tweet use case for example
16:08:14 <WimLeers> slashrsm: hm
16:08:21 <WimLeers> slashrsm: so how would it handle tweets?
16:08:37 <WimLeers> slashrsm: its thumbnails must always be images
16:08:40 <WimLeers> so…
16:08:56 <slashrsm> WimLeers: The only thing that you could separate is name logic. But that is very very simple.
16:09:12 <slashrsm> WimLeers: In contrib you have options for the handler to build SVGs for you.
16:09:18 <slashrsm> WimLeers: Which displays the actual tweet
16:09:22 <GaborHojtsy> WimLeers: for any usable UI you would need some kind of unified representation of media visually
16:09:23 <WimLeers> woahhhhhhhh
16:09:46 <phenaproxima> WimLeers: ^^ That's why I think we should enforce that all media has thumbnails, personally.
16:09:52 <phenaproxima> WimLeers: Because that's how normals expect "media" to work
16:10:08 <WimLeers> yes, so, what I was getting at, was: EITHER image, OR sourceless iframe
16:10:18 <WimLeers> but I guess SVG could also work
16:10:23 <WimLeers> that's interesting
16:10:33 <phenaproxima> WimLeers: It's implemented in Lightning, feel free to give it a shot ;-)
16:10:39 <WimLeers> phenaproxima: demo URL?
16:10:39 <seanB> oEmbed supports returning thumbnails as well, so when you add a image to it tweet you might even want to fetch that?
16:10:41 <slashrsm> WimLeers: Core doesn't like SVGs on some parts, but there are issues to solve that AFAIK.
16:10:50 <phenaproxima> WimLeers: ...we don't have one yet =|
16:10:53 <WimLeers> slashrsm: yeah, anything lacking SVG support is simply broken
16:11:00 <WimLeers> phenaproxima: so can I has a screenshot then?
16:11:09 <WimLeers> or screencast
16:11:11 <phenaproxima> WimLeers: Yeah, I will create one for you.
16:11:15 <WimLeers> phenaproxima: thanks!
16:11:32 <WimLeers> slashrsm: so what about \Drupal\media\MediaHandlerInterface::attachConstraints
16:11:33 <phenaproxima> WimLeers, slashrsm: I have a long standing issue to bring SVG support to ImageFormatter. It's NR right now, I think
16:11:47 <WimLeers> slashrsm: does that only make sense for handlers implementing SourceFieldInterface?
16:11:58 <slashrsm> WimLeers: No, it makes sense for all.
16:12:00 <WimLeers> slashrsm: and if so, why don't Image and File media handlers need it?
16:12:34 <slashrsm> WimLeers: They probably should use it.
16:12:41 <WimLeers> slashrsm: ehhh
16:12:46 <WimLeers> that sounds scary/ominous :D
16:29:57 <slashrsm> WimLeers: :) For me personally it is simply the divide and conquer approach to solving problems.
16:29:57 <slashrsm> WimLeers: I need main entity in and then I can put my attention into other details.
16:29:57 <slashrsm> WimLeers: I can't keep general overview + all details of all parts in my head at the same time.
16:29:57 <slashrsm> WimLeers: But I agree with you. It is a valid comment. And having more validation is always better than les.
16:31:22 <WimLeers> I'd still love to hear from you later what you think about MediaHandlerInterface being split up into more interfaces. And e.g. attachConstraints() being on a separate interface. (And apparently File and Image should implement that, but don't?)
16:32:19 <slashrsm> WimLeers: I don't have strong opinions against it. I do thing that it would add some complexity and I am not sure if the benefits would outweigh that.
16:32:41 <WimLeers> slashrsm: so let me lay out the argument in an issue comment then
16:32:46 <WimLeers> let me present what it would look like
16:32:49 <slashrsm> (y)
16:32:51 <WimLeers> and then we'll continue from there

That chat log has me saying I'll make a concrete proposal, because @slashrsm said he's not opposed. Posting that in my next comment.

Wim Leers’s picture

Here's the proposal for #230.2 that I mentioned in my previous comment:

Today
interface MediaHandlerInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {
  public function getLabel();
  public function getProvidedFields();
  public function getField(MediaInterface $media, $name);
  public function attachConstraints(MediaInterface $media);
  public function shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE);
  public function getThumbnail(MediaInterface $media);
  public function getDefaultThumbnail();
  public function getDefaultName(MediaInterface $media);
  public function mapFieldValue(MediaInterface $media, $source_field, $destination_field);
}
interface SourceFieldInterface extends MediaHandlerInterface {
  public function getSourceField(MediaTypeInterface $type);
  public function getSourceValue(MediaInterface $media);
}
My concerns
Having MediaHandlerInterface and SourceFieldInterface extends MediaHandlerInterface, but also MediaHandlerBase implements SourceFieldInterface, which means the base class isn't really a base class. @slashrsm says SourceFieldInterface could be merged into MediaHandlerInterface, but I don't think that'd make things clearer.
interface MediaHandlerInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {
  // This is the media handler label. Which is already in the annotation.
  // Why not read it from there? AFAICT it's not even used anywhere.
  public function getLabel();

  // The name suggests Entity API fields. But it's really media handler-specific metadata
  // (e.g. YouTube video ID; camera model or exposure for the Image media handler.
  // So should be renamed.
  public function getProvidedFields();

  // Same as previous.
  public function getField(MediaInterface $media, $name);

  // Many concerns: no implementations (although apparently File and Image are supposed to implement it?),
  // dangerous design (called by \Drupal\media\Entity\Media::validate(), which means that each
  // implementation is supposed to know how to deal with Entity API implementation details. Should be
  // a getter instead. And in fact, why could a set of constraints not be added to the @MediaHandler plugin
  // annotation? Those could then be added automatically. Much like e.g. FileItem (also a plugin) is declaring
  // multiple constraints in its annotation.
  public function attachConstraints(MediaInterface $media);

  // This method is necessary, because for e.g. YouTube you could use a heuristic to determine whether an
  // update is necessary, or some explicit rebuilding mechanism. IOW: for local media this is knowable, for
  // remote media it isn't, hence the need for abstraction.
  public function shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE); 

  // Makes sense, except that this offers no control at all to the caller about the dimensions of the thumbnail.
  // The YouTube @MediaHandler would return actual thumbnails, but the Image one returns the actual
  // (source) image. So arguably "thumbnail" is a misnomer, because it has a specific meaning:
  // https://en.wikipedia.org/wiki/Thumbnail says "smaller copies of the original image".
  // I don't have good alternative suggestions though.
  public function getThumbnail(MediaInterface $media); 

  // Makes sense, but belongs on the same interface as the other thumbnail methods.
  public function getDefaultThumbnail(); 

  // Makes sense :)
  public function getDefaultName(MediaInterface $media);

  // Relates to getProvidedFields() and getField(). First: should be renamed accordingly. Second: why
  // is this even necessary? Is its logic not always the same? AFAICT getProvidedFields() and getField()
  // already provide the necessary information (which metadata exists and the concrete metadata to
  // map) and \Drupal\media\Entity\MediaType::getFieldMap() determines the destination fields to
  // store this metadata in. So AFAICT this method has no reason to exist. But if it exists, it belongs
  // on the same interface as getProvidedFields()
  public function mapFieldValue(MediaInterface $media, $source_field, $destination_field);
}
interface SourceFieldInterface extends MediaHandlerInterface {
  // This is where the ACTUAL storage happens!
  public function getSourceField(MediaTypeInterface $type);

  // Given the return value of getSourceField(), just reads that. And defaults to the main property.
  // This only exists to allow for the extreme edge case of it needing to return/read a property
  // other than the main one. If it's such an extreme edge case, then we should just omit this,
  // and instead add it in an optional interface to allow those edge cases (when they arise) to
  // override this. Final nail in the coffin: the only use of this method is in MediaHandlerBase…
  // at which point it can clearly just as well be a protected helper :)
  public function getSourceValue(MediaInterface $media);
}

// NOTE: 100% of media handlers implement this. @slashrsm is indicating this could be merged into the MediaHandlerInterface above.
Conclusion: IMHO at least getLabel(), mapFieldValue() and getSourceValue() are useless. That's 3/11 = 27% of the interface surface area! And quite possibly attachConstraints() too, then it's 4/11 = 36%.
My proposal, iteration 1
Partitioning the interfaces into describable responsibilities, with more precise concepts/naming/terminology. The end result is something more understandable.
Let's see if I can resolve my concerns above:
// The hard essentials: where data is stored, the additional entity constraints for validating said data, and a default name based on that.
interface MediaHandlerInterface {
  public function getSourceField(MediaTypeInterface $type); // Removed SourceFieldInterface, kept only this method.
  public function getAdditionalEntityConstraints(MediaInterface $media); // Assuming it won't be moved to the annotation.
  public function getDefaultName(MediaInterface $media);
}

// Without this, we'd lose metadata specific to YouTube/File/Image/SoundCloud/…
interface MediaHandlerMetadataInterface {
  public function getMetadataDefinition(); // keys => labels; @see \Drupal\Core\Entity\FieldableEntityInterface::baseFieldDefinitions()
  public function getMetadata(MediaInterface $media, $key); // returns metadata value for given key; @see \Drupal\Core\Entity\FieldableEntityInterface::get()
}

// Without this, we'd lose the ability to have thumbnails.
interface MediaHandlerThumbnailInterface {
  public function getDefaultThumbnail();
  public function getThumbnail(MediaInterface $media);
  public function shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE)
}
I think those are 3 clear areas of responsibility.
My proposal, iteration 2
Hrm, this doesn't really make sense. Those "metadata" and "thumbnail" interfaces are not meaningfully connected with the "handler" interface. Every media handler needs all three. But OTOH we want to make sure we can have more advanced media handlers in the future, so we do want interface composability. (For example for that edge case described for SourceFieldInterface::getSourceValue().)
But at the same time, they each have a clear responsibility.
We could introduce a new interface that implements all three, and designate that to be the MediaHandlerInterface. Then yes, MediaHandlerInterface will still have the exact same end result, but at least it's now clearly partitioned.
This forces me (as you will see) to rename MetadataHandlerInterface. @slashrsm ruled out storage, backend and type in #216.
So for now, I'm going with MediaStorageProxy.
interface MediaStorageProxyInterface {
  public function getSourceField(MediaTypeInterface $type); // Removed SourceFieldInterface, kept only this method.
  public function getAdditionalEntityConstraints(MediaInterface $media); // Assuming it won't be moved to the annotation.
  public function getDefaultName(MediaInterface $media);
}

interface MediaHandlerMetadataInterface {
  public function getMetadataDefinition(); // keys => labels; @see \Drupal\Core\Entity\FieldableEntityInterface::baseFieldDefinitions()
  public function getMetadata(MediaInterface $media, $key); // returns metadata value for given key; @see \Drupal\Core\Entity\FieldableEntityInterface::get()
}

interface MediaHandlerThumbnailInterface {
  public function getDefaultThumbnail();
  public function getThumbnail(MediaInterface $media);
  public function shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE)
}

interface MediaHandlerInterface implements MediaStorageProxyInterface, MediaHandlerMetadataInterface, MediaHandlerThumbnailInterface;

I'm starting to like this a lot better already.
My proposal, iteration 3
Observation 1: MediaHandlerMetadataInterface::getMetadata() needs the data stored in the source field (MediaStorageProxyInterface::getSourceField()) to return the correct value.
Observation 2: MediaHandlerThumbnailInterface::getThumbnail() and MediaHandlerThumbnailInterface::shouldUpdateThumbnail() both need the data stored in the source field (MediaHandlerStorageProxyInterface::getSourceField()) to return the correct value.
Therefore MediaHandlerMetadataInterface and MediaHandlerThumbnailInterface both depend on MediaStorageProxyInterface.
This confirms that they belong in separate interfaces.
More importantly, this suggests that it may be worthwhile to do implementations of just the MediaHandlerStorageProxyInterface interface as stand-alone things? That'd make it a lot easier to explain. Except that there would be a 1:1 relationship between an implementation of that interface, and the (required) implementations of the two other interfaces. Does that get us anywhere? Have we seen this elsewhere?
It turns out we have: entity types have "handlers" for storage, access, form, route provider, translation, and so on.
Conclusion: what I think makes sense is to thus have:
  1. @MediaStorageProxy plugins that implement MediaStorageProxyInterface
  2. require @MediaStorageProxy plugin annotations to have metadata (implementing MediaStorageProxyMetadataHandlerInterface) and thumbnail (implementing MediaStorageProxyThumbnailHandlerInterface) handlers (much like EntityType plugins have handlers)
  3. this then also opens the door for extensibility, for example a oembed_provider handler which would allow a Drupal site to become an oEmbed provider, allowing any Media to generate more advanced/detailed oEmbed data. Or for example an accessible_thumbnail handler which also provides alternative text (perhaps through image recognition, perhaps by fetching a remote description). Or a twitter_card handler.
  4. Yes, you could argue that you could just keep adding interfaces to the current patch's MediaHandlerInterface. But they're different areas of responsibility. This is what the entity system has developed the concept of "handlers" for. This is extremely similar AFAICT.
tim.plunkett’s picture

Using annotations similar to EntityType's handlers seems like a great approach to me.
It allows for composition and follows an existing pattern.

I would go one step further and suggest that plugins be allowed to opt-into acting as their own handler, see \Drupal\Core\Plugin\PluginWithFormsTrait::getFormClass for an example (used by blocks).

MediaHandlerBase is the place to compose all of the most common interfaces, but I agree that each interface should be clear and have a single well-defined responsibility.

EDIT: Clarified my last statement

andypost’s picture

@Wim #236 is great idea, lots of ++ for detailed research

The only question that raises here for me is do we need some method to detect applied "handlers" a-la \Drupal\Core\Entity\EntityType::getHandlerClass() and where it can live?

Tim also pointed interesting nuance about nesting for kind of thumbnail handlers like \Drupal\Core\Entity\EntityType::getFormClass($operation) to provide this extendability with oEmbed and twittercard

slashrsm’s picture

Already discussed this with @Wim Leers on IRC, but it is worth mentioning here that I really like and support his #236 proposal. Same goes with @tim.plunkett's and @andypost's suggestions.

Wim Leers’s picture

#238: So you're adding to what I said: you're saying it's possible for the default plugin base class to implement the different kinds of handlers in one class. That's great: it's great that we have that option.
In this particular case, I'm not sure how helpful it is. It could be great. I suspect it may be great. But it could be that we feel it's clearer to have it in separate classes. I think once we refactor it towards handlers, that it will automatically become clear whether it makes sense to have it together or separate.

#239: RE: EntityType::getHandlerClass(): indeed: we'd need to duplicate that from EntityType. Which is fine. In the future, we could even put it in a shared trait.

#240: WOOHOO!


Discussed with slashrsm in IRC. If we want to see #236 happen, somebody needs to do it. slashrsm and Berdir both already have obligations for most of today. My colleagues in the U.S. are still sound asleep right now. So we agreed the best course of action would be for me to take on this rerolling, and for slashrsm+Berdir+my colleagues to do reviewing. Because time is ticking.

So, assigning to myself.

Wim Leers’s picture

First, removing getLabel(), mapFieldValue() and getSourceValue(). This reduces the API surface, and hence reduces the amount of moving I'll have to do in subsequent patches.

(This made me notice that getSourceField() is also incorrectly named, it should be named getSourceFieldDefinition() because it returns a \Drupal\Core\Field\FieldDefinitionInterface.)

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
219.64 KB
869 bytes

All failures are due to Field field_media_test is unknown being thrown in SqlContentEntityStorage.

+++ b/core/modules/media/src/MediaHandlerBase.php
@@ -124,10 +124,11 @@ public function defaultConfiguration() {
-    $source_field = $this->configuration['source_field'];
-    if (!$media->get('thumbnail')->entity || $is_new || (!empty($source_field) && isset($media->original) && $this->getSourceValue($media)->getValue() != $this->getSourceValue($media->original)->getValue())) {
+    if (!$media->get('thumbnail')->entity || $is_new || (isset($media->original) && $media->get($source_field_name)->getValue() != $media->original->get($source_field_name)->getValue())) {

The only actual logic change in #243's interdiff is here, the !empty($source_field) was removed. Because it should not be necessary.

Turns out it is, and it's related to the auto_create_source_field magic.

This now reintroduces this, but keeps it separate, with an explicit @todo so that @phenaproxima can remove it as part of his work addressing #230.3 (see #235).

Wim Leers’s picture

FileSize
15.96 KB
220.14 KB

Now a pure renaming patch:

  1. MediaHandlerInterface to MediaStorageProxyInterface
  2. MediaHandlerBase to MediaProxyStorageBase
  3. @MediaHandler to @MediaStorageProxy (the plugin annotation)
  4. Plugin\media\Handler to Plugin\MediaStorageProxy (the plugin namespace) — if people prefer Plugin\media\MediaStorageProxy, that can be easily arranged.
  5. MediaHandlerManager to MediaStorageProxyManager
  6. media_handler_info_alter() to hook_media_storage_proxies_alter()

… plus updated docs everywhere.

This means that the following have not yet been updated:

  • MediaType(Interface) (plus associated configuration)
  • Media(Interface)
Berdir’s picture

Some notes on the removed methods below, other than that I think I'm OK with the direction, not sure I fully see yet how the handler stuff will look like exactly. Worth mentioning that some also critized that pattern, e.g. because they are not services. But we have plugins either way. We do have a few examples where we actually return service names instead of class names, for example for entity query factory in \Drupal\Core\Entity\EntityStorageBase::getQueryServiceName().

You do need to solve the problem problem of creating those handlers and dependency injection, and since they are not plugins, you can not use the ContainerFactoryPluginInterface, you can also not use EntityHandlerInterface as this is not an entity type, you can also not use the plain ContainerInjectionInterface as we do need to provide those handlers some context about what they are working with. Or maybe not, if we always pass them everything they need through their methods. If not, then we need Yet-Another-Injection-Create-Method-Interface. Yay ;)

In TMGMT, we do have something similar, mostly for historical reasons, where we have translation provider plugins and they have a separate class for their UI-related methods, basically a left-over of the 7.x architecture and while it is kinda nice to have all that clunky UI code somewhere else, interacting with it is actually pretty bad DX, for example because we never solved the dependency injection problem. What I'm basically trying to say is that while it sounds nice and clean on paper, you might end up with more (glue) code than you realize now.

  1. +++ b/core/modules/media/src/Entity/Media.php
    @@ -140,7 +140,11 @@ public function preSave(EntityStorageInterface $storage) {
         // media type config.
         foreach ($this->bundle->entity->getFieldMap() as $source_field => $destination_field) {
    -      $this->getHandler()->mapFieldValue($this, $source_field, $destination_field);
    +      // Only save value in entity field if empty. Do not overwrite existing
    +      // data.
    +      if ($this->hasField($destination_field) && $this->get($destination_field)->isEmpty() && ($value = $this->getHandler()->getField($this, $source_field))) {
    +        $this->set($destination_field, $value);
    +      }
    

    Example use case for "mapping": You could map eg. the category of a youtube video to a term reference field, so you don't want to store the plain string, you want to look look up a matching term, auto-create if not existing and then assign the term object/ID.

    That is still possible, you can implement that logic in getField(), it is a bit weird though, and we need to very clearly document what the return value is exactly (anything that can be assigned to a field.. including multi-property and multi-item array structures).

    One thing that's no longer possible is doing anything dynamic based on the destination field. You couldn't support both a text field and an entity reference as destination, for example. And if you do anything like that, you also can't really validate the destination in any way (for example check the max length of text fields.. we frequently have troubles with imports that write too long node titles, or descriptions or so). Or check the target type of an entity reference field, because it's not going to go well if you save a term into a user reference field :)

    At least parts of this can be done on other ways, we could give them a way to validate the field map or so. And we could maybe still pass $destination_field to getField(), then they could do basically the same in getField() as would have been possible in mapFieldValue().

    That said definitely +1 to renaming source fields.

  2. +++ b/core/modules/media/src/MediaHandlerBase.php
    @@ -124,10 +124,11 @@ public function defaultConfiguration() {
         // or if the value of the source field changed.
    -    $source_field = $this->configuration['source_field'];
    -    if (!$media->get('thumbnail')->entity || $is_new || (!empty($source_field) && isset($media->original) && $this->getSourceValue($media)->getValue() != $this->getSourceValue($media->original)->getValue())) {
    +    if (!$media->get('thumbnail')->entity || $is_new || (isset($media->original) && $media->get($source_field_name)->getValue() != $media->original->get($source_field_name)->getValue())) {
           return TRUE;
         }
         return FALSE;
    

    Note that this is not the same as before. Before you compared the property value (e.g. "foo".), now you compare the value array for the whole field (e.g. [0 => ['value' => 'foo']]). should still work if we never need this as an actual plain value.

    I also agree with removing from the interface, makes sense, but maybe keeping as a protected method might make sense? This line is pretty long and complicated enough. that said, maybe let it return a plain value (so just "foo", and not a String typed data class with the value foo) as I see no benefit of going through typed data.

  3. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -99,7 +99,8 @@ public function testMediaEntityCreation() {
         $this->assertEquals('Unnamed', $media->label(), 'The media item was not created with the correct name.');
    -    $this->assertEquals('Nation of sheep, ruled by wolves, owned by pigs.', $media->bundle->entity->getHandler()->getSourceValue($media)->getValue(), 'Handler returns correct source value.');
    +    $source_field_name = $media->bundle->entity->getHandler()->getSourceField($media->bundle->entity)->getName();
    +    $this->assertEquals('Nation of sheep, ruled by wolves, owned by pigs.', $media->get($source_field_name)->first()->getValue()['value'], 'Handler returns correct source field.');
    

    so here we need the text, and you see the difference here now nicely.

    This can't use a protected helper of course, which is fine, but use $media->get($source_field_name)->value. Yes, magic, but actually faster and more readable.

Wim Leers’s picture

FileSize
11.09 KB
221.89 KB

This splits off metadata-related methods from MediaStorageProxyInterface to MediaStorageProxyMetadataHandlerInterface. This does not yet introduce a handlers annotation on MediaStorageProxy plugins. That's for a next step.

(What's written here expands on my analysis of metadata-related methods that I wrote in #236.)

In doing so, I noticed that the existing method returned both metadata labels (using the 'label' key) and optionally anything else: an ArrayPI. The only other thing besides 'label' that is documented is 'field_type'… but nothing uses that, nor is there test coverage.

Furthermore, returning 'field_type' there is also needlessly tying this interface to Entity Fields. It should be solely about media metadata. If in the future you want to have suggestions for particular field types, add a metadata_field_suggestion handler.
If we wouldn't remove 'field_type', then we would also have to document the boatload of assumptions inherently made and not mentioned anywhere: media metadata can only ever have cardinality 1, behavior in case you only set up a metadata<>field mapping after you've created metadata is undefined, behavior in case of optional metadata is undefined, behavior in case of required fields is undefined, and so on.

We can avoid all that by focusing this handler narrowly on one thing: documenting which metadata attributes exist, their labels, and the ability to retrieve them.

Which brings me to the final point: field (which was in the patch so far) is a terrible name because it's so easily confused with Entity Fields — even more so because these (metadata) "fields" were being mapped to Entity Fields. I also ruled out "properties", because that's another term we use in Entity Field API, i.e. for fields that contain multiple properties (e.g. text + text format). That's why I went with attributes, which seems the best fit, also considering it's the most "web like" (because HTML elements have attributes).

Of course, this new interface has comprehensive documentation, so that hopefully it's crystal clear to everybody what its responsibilities are.

Wim Leers’s picture

#247:

Yes, there are definitely downsides to having "handlers" the way entity types do them. But at the same time, can you imagine what it'd have been like if the Node class included all the code in all of its handlers? Tagged services may be a possibility, but is entirely new ground: a plugin type with associated service tags, and hence plugin instances with associated tagged services is not a pattern that exists in core.

RE: dependency injection: that may very well become a problem. But the first one (see #248) didn't need anything to be injected at all. Thanks for your advance warning — I really appreciate that!

Overall, I agree with your concerns/warnings for carefulness. The fact remains though that MediaHandlerInterface was really just a dumping ground for lots of responsibilities. Which explains why it's so darn hard to understand, describe and document.

  1. Hah, funny that you bring up the need for clearly documenting the structure of the returned metadata. That's exactly the sort of thing I was getting at in #248 :)

    RE: advanced use cases: like I explained already, when those use cases arise, we can add additional handlers to support those advanced capabilities.

    I hope you like what I did in #248.

  2. Yep, I'm aware that we're now comparing the full field item (i.e. potentially multiple values, and each value with multiple properties), not just the main property of the first value. AFAICT that's even better though: we're no longer comparing subsets, we're actually comparing that nothing changed in the field at all. So it gives us a stronger guarantee, and for all code in this patch + file + image plugins, it is functionally equivalent.

    I considered keeping getSourceValue() as a protected method. But the fact is that it's duplicating part of the logic in getSourceField(): it also reads $this->configuration['source_field'] and it also throws a RuntimeException. Plus given what I wrote above in response to this point: it's only comparing a subset, which is not quite correct.

  3. Will do that, thanks for the tip! (Would you believe me if I said I googled for 10 minutes without finding a helpful response on how the hell to get that value?)

    Although I have to say that this particular assertion is rather pointless. It used to test getSourceValue(). IMO it can be removed. Or we could just test that getSourceField() works correctly. I figured I'd keep it just to ensure we keep the test coverage equivalent.

tedbow’s picture

Just a couple nits for now

  1. +++ b/core/modules/media/media.install
    @@ -0,0 +1,57 @@
    +    $result = file_unmanaged_copy($file->uri, $destination, FILE_EXISTS_REPLACE);
    

    Unused local var

  2. +++ b/core/modules/media/media.install
    @@ -0,0 +1,57 @@
    +    $source = drupal_get_path('module', 'media') . '/images/icons';
    

    Unused local var

  3. +++ b/core/modules/media/media.install
    @@ -0,0 +1,57 @@
    +    $error = '';
    

    $error doesn't need to be declared here. Will always be set in the else clause if used.

  4. +++ b/core/modules/media/src/MediaStorageProxyMetadataHandlerInterface.php
    @@ -0,0 +1,63 @@
    +use Drupal\Component\Plugin\ConfigurablePluginInterface;
    +use Drupal\Component\Plugin\PluginInspectionInterface;
    +use Drupal\Core\Plugin\PluginFormInterface;
    

    Unsued imports

Berdir’s picture

> Would you believe me if I said I googled for 10 minutes without finding a helpful response on how the hell to get that value?

I totally do :(

Entity API documentation--
Berdir--

That said, it's a somewhat tricky term to search for, there *is* some documentation
* https://wizzlern.nl/drupal/drupal-8-entity-cheat-sheet
* http://drupal.stackexchange.com/questions/221103/access-entity-reference... (And a ton of other similar questions/answers)
* https://www.drupal.org/docs/8/api/entity-api/entity-api-implements-typed...