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: Roadmap 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: Roadmap.

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

Comments

slashrsm created an issue. See original summary.

slashrsm’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new199.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
StatusFileSize
new193.89 KB
new34.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
StatusFileSize
new3.08 KB
new196 KB

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

slashrsm’s picture

Status: Needs work » Needs review
StatusFileSize
new196.65 KB
new78.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
StatusFileSize
new171.16 KB
new33.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
StatusFileSize
new171.23 KB
new1.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

StatusFileSize
new5.76 KB
new170 KB

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

StatusFileSize
new3.72 KB
new171.35 KB

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: 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: [META] Expose Title and other base fields 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: 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

StatusFileSize
new171.36 KB
new10.21 KB

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 source plugin for remote video via oEmbed.

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

StatusFileSize
new170.36 KB
new6.74 KB

#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

StatusFileSize
new170.63 KB
new13.19 KB

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
StatusFileSize
new170.54 KB
new2.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

StatusFileSize
new14.17 KB
new169.26 KB

#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

StatusFileSize
new169.49 KB
new654 bytes

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

StatusFileSize
new168.9 KB
new18.27 KB

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
StatusFileSize
new168.87 KB
new630 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
StatusFileSize
new168.88 KB
new675 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

StatusFileSize
new168.57 KB
new41.92 KB

- #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

StatusFileSize
new169.52 KB
new4.24 KB

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

StatusFileSize
new184.32 KB
new25.1 KB

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
StatusFileSize
new181.53 KB
new9.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

StatusFileSize
new176.23 KB
new118.32 KB

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

Issue summary: View changes
StatusFileSize
new267.07 KB

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

StatusFileSize
new177.7 KB
new3.65 KB

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
StatusFileSize
new177.86 KB
new17.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

Issue summary: View changes
StatusFileSize
new179.08 KB
new8.42 KB

#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

StatusFileSize
new3.68 KB
new179.09 KB

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: Roadmap 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
StatusFileSize
new179.24 KB
new43.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
StatusFileSize
new179.36 KB
new9.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: Roadmap 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
StatusFileSize
new178.96 KB
new37.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

StatusFileSize
new178.7 KB
new3.52 KB

#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

StatusFileSize
new178.79 KB
new1.15 KB

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: Roadmap 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
StatusFileSize
new179.26 KB
new3.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
StatusFileSize
new14.06 KB
new180.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

StatusFileSize
new184.41 KB
new11.89 KB

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 source plugin for remote video via oEmbed).

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

StatusFileSize
new183.95 KB
new5.88 KB

#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

StatusFileSize
new185.58 KB
new3.68 KB
new2.82 KB

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

StatusFileSize
new187.01 KB
new2.61 KB

Added a test for #174.

seanb’s picture

StatusFileSize
new188.21 KB
new4.56 KB

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 an 'instant' queue runner - 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
StatusFileSize
new188.37 KB
new4.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
StatusFileSize
new188.19 KB
new783 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

StatusFileSize
new187.21 KB
new53.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
StatusFileSize
new188.3 KB
new19.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

StatusFileSize
new189.1 KB
new73.71 KB

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

StatusFileSize
new192.66 KB
new8.92 KB

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

StatusFileSize
new58.16 KB
new37.52 KB
new57.51 KB
new59.58 KB
new29.77 KB
new25.81 KB
new35.05 KB
new45.81 KB

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: Roadmap 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: Roadmap 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: [META] Roadmap to stabilize Media Library, 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: [META] Roadmap to stabilize Media Library 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: [META] Roadmap to stabilize Media Library 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

StatusFileSize
new194 KB
new5.03 KB

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

StatusFileSize
new194.01 KB
new322 bytes

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 source plugin for remote video via oEmbed 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

StatusFileSize
new195.3 KB
new2.33 KB

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

StatusFileSize
new197.68 KB
new5.28 KB

#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

StatusFileSize
new221.64 KB
new24.29 KB

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

StatusFileSize
new219.43 KB
new7.02 KB

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
StatusFileSize
new219.64 KB
new869 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

StatusFileSize
new15.96 KB
new220.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

StatusFileSize
new11.09 KB
new221.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...

wim leers’s picture

StatusFileSize
new5.27 KB
new222.95 KB

This moves the thumbnail-related methods into a separate interface. No big explanation here like for #248, because it's a simple move. The only noteworthy thing is that I added extensive docs to this new interface.

(And fixes #250.4. The other points in #250 I'll leave to somebody from the media team to fix — I'm not touching that file.)

wim leers’s picture

StatusFileSize
new3.32 KB
new223.29 KB

This patch:

  1. renames attachConstraints() to getConstraints() and updates the documentation to be far more specific (looking at \Drupal\Component\Plugin\Context\ContextDefinitionInterface::getConstraints() as an example)
  2. adds an assertion in its sole call location to ensure constraints are indeed returned
  3. and adds a @todo to ensure this patch will not be committed without test coverage, because the patch so far has failed to provide that — File and Image plugins should be updated so they add constraints!
  4. removes the default no-op implementation from MediaStorageProxyBase (previously MediaHandlerBase), to force developers to think about the constraints!
wim leers’s picture

StatusFileSize
new10.68 KB
new223.87 KB

This patch:

  1. removes SourceFieldInterface and moves its sole method into MediaStorageProxyInterface
  2. more importantly, it completely revamps the documentation on MediaStorageProxyInterface: it documents the complete architecture. That will be the test: does this actually make sense?

    Of course, there's one critical remaining step before that documentation makes complete sense: we still need to actually make @MediaStorageProxy plugins use handlers. In any case, even just having separate interfaces already makes this a lot more understandable, in my opinion. I'd love to hear if others agree.

Finally, I think that getSourceField() is itself also still fairly confusing, but I think that once @phenaproxima addresses #230.3, it'll become possible to clarify/simplify it further.

wim leers’s picture

Issue summary: View changes

We now have both \Drupal\media\MediaStorageProxyThumbnailHandlerInterface and \Drupal\media\MediaThumbnailHandlerInterface.

That's confusing.

\Drupal\media\MediaThumbnailHandlerInterface didn't stand out before, but now it does. NOFI, but it's incredibly terribly named. Again that "handler" suffix! I didn't even notice before because it wasn't as important, but now that @MediaHandler are more understandable, this really stands out. It's called by the ThumbnailDownloader @QueueWorker plugin.

I wanted to make renaming suggestions, but then I realized… why is this even a separate interface+service? There doesn't seem to be any reason to have different logic? And if there is, this seems like a premature abstraction.

So it is a separate class (and hence interface+service) because it's called both from \Drupal\media\Entity\Media::preSave() and \Drupal\media\Plugin\QueueWorker\ThumbnailDownloader::processItem().

\Drupal\media\MediaThumbnailHandler(Interface) can be removed quite easily AFAICT:

  1. move the $media->thumbnail->… setter logic from \Drupal\media\MediaThumbnailHandler::updateThumbnail() into Media::preSave()
  2. add a setThumbnail() method to MediaInterface, and move the "create File if it does not exist" logic from \Drupal\media\MediaThumbnailHandler::updateThumbnail() in there
  3. call the @MediaStorageProxy plugin's getThumbnail() method from \Drupal\media\Plugin\QueueWorker\ThumbnailDownloader::processItem() and let it pass the result of that to MediaInterface::setThumbnail()

And voila, one service less, again less API surface, and code that is much easier to understand.

wim leers’s picture

Assigned: wim leers » Unassigned
StatusFileSize
new6.49 KB
new224.94 KB

We just had a meeting with @phenaproxima, @effulgentsia, @Gábor Hojtsy, @tedbow, @tim.plunkett, @sam.mortenson and I. In other words: a subset of the folks working on Media.

We reviewed the progress made above today. We reached a few conclusions, which we'd like to see confirmed or rejected:

  1. getDefaultName() can be moved into the metadata handler
  2. getConstraints() needs to receive MediaType config entity (because e.g. oEmbed @MediaStorageProxy, but restricting to just YouTube for this particular MediaType)
  3. getConstraints() is always applied to the source field, see http://cgit.drupalcode.org/media_entity_instagram/tree/src/Plugin/MediaE... + http://cgit.drupalcode.org/media_entity_twitter/tree/src/Plugin/MediaEnt... — therefore it should be renamed to getSourceFieldConstraints()
  4. we now have getSourceField() and getSourceFieldConstraints() and that's all that's left on the plugin interface. It makes sense to move it to a separate interface (yes, again), and then have an "empty" plugin interface, and have everything be composable via handlers.
  5. rename plugin from @MediaStorageProxy to @MediaSource

Everything except the last point is done, except for docs updates.

Unassigning. Hopefully Tim can push the handler stuff forward.

wim leers’s picture

Assigned: Unassigned » wim leers

Unfortunate that nobody was able to push it forward while I was asleep. But ah well.

Pushing it further. @seanB +1'd this direction, and also #256's suggestions:

09:59:28 <seanB> WimLeers: I see there was a lot of progress yesterday, that's awesome. Overall I'm totally on board with the direction this is heading. I think the MediaSource term is a lot clearer than MediaStorageProxy or MediaHandler, so talking about Media items > Media types > Media sources is starting to look like something that's easy to explain to end users. I can
09:59:28 <seanB> look at that tonight I think. I didn't have time yet to really dive in to everything, but I will also do that tonight.

@slashrsm will be reviewing it in a few hours.

Progress!

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new857 bytes
new224.94 KB

First making the patch green again.

I forgot to say that #256 would fail because I rolled that patch during the meeting it documented, and I was unable to run tests before having to leave. I'm surprised I managed to only break one test, instead of many.

wim leers’s picture

I forgot to write in #256 that we also discussed \Drupal\media\MediaStorageProxyThumbnailHandlerInterface and \Drupal\media\MediaThumbnailHandlerInterface, i.e. the point I raised in #255.

We:

  1. agreed that media.thumbnail_handler service is kind of pointless: i.e. we agreed we'd refactor that service away
  2. but @effulgentsia noted that it's wrong/incomplete to have just a thumbnail URL be returned, we at minimum also need alt, but it should also be possible to send title, width and height. Because the current "media thumbnail handler service" is hardcoding alt = $this-t('Thumbnail') and title = $media->label(). In other words: we either need a Thumbnail value object that requires src and alt, and allows all other attributes to be optionally set, or we need to rename the existing method from getThumbnail() to getThumbnailUri(), because today it only returns an URI. We may want to choose the latter approach if we want to keep the alt optional, i.e. if we want to move that to a separate handler.

(I hope that mostly captures it, @effulgentsia and others, please correct if wrong/incomplete.)

EDIT: this was not discussed in that meeting, but AFAICT the shouldUpdateThumbnail() method is also pretty pointless. It's only called from both Media::preSave() and Media::postSave(), always has the same logic, and could therefore easily be moved into the Media entity. For local media, pre-save calls $plugin->getThumbnail(), post-save does nothing. For remote media, pre-save always calls $plugin->getDefaultThumbnail() and in post-save it queues the proper thumbnail to be fetched.

slashrsm’s picture

Thank you @Wim Leers! I like the direction this is going to. Architecture is much clearer now. There will be some more work for contrib plugins to catch-up, but I think that it is worth the effort.

Re dependency injection on handlers: I think that we'll need to solve that as there are definitely dependencies needed on them. The simplest example I can come up with is \Drupal\media\MediaStorageProxyThumbnailHandlerInterface. Most (if not all) of them rely on a configuration object to figure out where thumbnails should be saved locally - config factory is needed. There are many more examples like this. Could we use Drupal\Core\DependencyInjection\ContainerInjectionInterface?

+++ b/core/modules/media/src/Entity/Media.php
@@ -194,7 +194,9 @@ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $reco
-    $this->getHandler()->attachConstraints($this);
+    // @todo Make this work, and add test coverage!
+    $constraints = $this->getHandler()->getConstraints();
+    assert('\Drupal\Component\Assertion\Inspector::assertAllObjects($constraints, \'\Symfony\Component\Validator\Constraint\')');

This changes the behavior as the old function expected handlers to attach constraints to the entity or any of its fields. Then Entity::validate() automatically picked them up and validated them.

New version expects handler to return the list of constraints and currently we don't do anything with them. Old approach was a bit more flexible since handlers were able to use entity-level or field-level constraints. It seems that the new approach only supports the latter.

+++ b/core/modules/media/src/MediaStorageProxyMetadataHandlerInterface.php
@@ -0,0 +1,63 @@
+  /**
+   * Gets the value for a metadata attribute for a given media item.
+   *
+   * @param \Drupal\media\MediaInterface $media
+   *   A media item.
+   * @param string $attribute_name
+   *   Name of attribute to fetch.
+   *
+   * @return mixed|false
+   *   Metadata attribute value or FALSE if unavailable.
+   */
+  public function getMetadata(MediaInterface $media, $attribute_name);

@dawehner pointed at that we might have a problem if the actual metadata value is FALSE. Since we're already revamping this it might be worth looking into that and finding a better way to indicate that a given metadata field is not available.

...we either need a Thumbnail value object that requires src and alt, and allows all other attributes to be optionally set...

I think that this would make sense and would also help us remove the alt = $this-t('Thumbnail') and title = $media->label() nonsense. Could we do this through Typed Data?

agreed that media.thumbnail_handler service is kind of pointless: i.e. we agreed we'd refactor that service away

Agreed. The main reason for this service was to be able to use this code on both the entity class and queue processor. I am not entirely sure where to move the logic. Currently we only set the thumbnail if the a) field is empty or b) it is updated from a queue. This is fairly limiting and there were reports already complaining about thumbnail not being updated etc. I can foresee this becoming smarter in the future. We could move responsibility for the decision when to update to the thumbnail handler, which would allow for smarter logic than the current "only if emtpy". Another thing that I can see being added is something on the media form, which would allow content editors to force thumbnail update.

We need to structure this in a way that will allow us to achieve this kind of things in a nice and meaningful way.

wim leers’s picture

TL;DR
I'm proposing a @MediaSource plugin (name suggested in #256), without handlers, with only 3 methods, and many prior methods' responsibilities being implemented in the form of annotation keys that point to particular metadata attributes.


@Berdir was prescient in #247 when he said:

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 ;)

The choice in #256 to move the "source field" stuff to a separate handler too only makes this harder, because both other handlers (metadata and thumbnail) need the source field. Because that means that the metadata and thumbnail handlers both need to receive (get injected) the source field handler… or at least be able to access the source_field configuration.

Contrast this with entity handlers; they only are able to access what's in the plugin definition (i.e. in the plugin annotation).

See the attached do-not-test interdiff to see what that code started to look like… even before I was passing in the necessary context (at minimum, the source field configuration) or providing dependency injection. It'll start to look significantly more complex once all handlers are receiving that too.


This got me thinking.

As a reference: the structure of the current patch (#259)
interface MediaStorageProxyInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {}

interface MediaStorageProxyMetadataHandlerInterface extends MediaStorageProxyInterface {
  public function getDefaultName(MediaInterface $media);
  public function getMetadataLabels();
  public function getMetadata(MediaInterface $media, $attribute_name);
}

interface MediaStorageProxyMetadataSourceFieldHandlerInterface extends MediaStorageProxyInterface {
  public function getSourceField(MediaTypeInterface $type);
  public function getSourceFieldConstraints(MediaTypeInterface $media_type);
}

interface MediaStorageProxyThumbnailHandlerInterface extends MediaStorageProxyInterface {
  public function shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE);
  public function getThumbnail(MediaInterface $media);
  public function getDefaultThumbnail();
}
Iteration 1
The above, combined with @MediaSource as the new proposed plugin name makes me think that perhaps having all three responsibilities (source field, metadata and thumbnail) perhaps can live in the same plugin and the same plugin interface. Until #236, what these plugins were doing was a lot more vague:
  1. @MediaHandler as the plugin name is as vague as is possible
  2. the number of methods was far larger: 11 then, 8 since #245 and if we remove shouldUpdateThumbnail() like I suggest in my last comment, it'll be 7
  3. On top of that, I think that we can actually also get rid of getDefaultName() and getThumbnail(). Because… they're actually metadata too!. For example, look at the Instagram API response, and how the Instagram plugin for Media handles this: http://cgit.drupalcode.org/media_entity_instagram/tree/src/Plugin/MediaE... + http://cgit.drupalcode.org/media_entity_instagram/tree/src/Plugin/MediaE.... It's literally reusing metadata. So then why not get rid of those methods, and specify in the plugin annotation which metadata attribute to use? Now we're down to 5 methods.
Then the end result would be an interface that looks like this:
/**
  * @MediaType(
  *   id= "file"
  *   allowed_source_field_types = {"file"},
  *   required_metadata = {
  *     default_name = 'filename',
  *     thumbnail_uri = 'uri',
  *     thumbnail_alt = 'description',
  *   }
  */
interface MediaSourceInterface {
  // @MediaSource-level logic
  function getMetadataLabels();
  function getDefaultThumbnail();

  // MediaType-level logic (i.e. depends on media type config entity)
  function getSourceField();
  function getSourceFieldConstraints();

  // Media-level logic (i.e. depends on media content entity)
  function getMetadata(MediaInterface $media);
Iteration 2
And in fact, we could quite easily move getMetadataLabels() into the annotation as well, since it really is just a hardcoded key-value mapping anyway! Now down to 4 methods! Let's turn it into two examples: Image + Instagram:
/**
  * @MediaSource(
  *   id= "image"
  *   allowed_source_field_types = {"file", "image"},
  *   metadata = {
  *     mime = @Translation("MIME type"),
  *     size = @Translation("Size"),
  *     filename = @Translation("File name"),
  *     uri = @Translation("File URI"),
  *     width = @Translation("Width"),
  *     height = @Translation("Width"),
  *   } 
  *   default_name = 'filename',
  *   thumbnail_uri = 'uri',
  */
/**
  * @MediaSource(
  *   id= "instagram"
  *   allowed_source_field_types = {"link"},
  *   metadata = {
  *     size = @Translation("Size"),
  *     title = @Translation("Title"),
  *     width = @Translation("Width"),
  *     height = @Translation("Width"),
  *     thumbnail_url = @Translation("Thumbnail"),
  *     thumbnail_width = @Translation("Thumbnail width"),
  *     thumbnail_height = @Translation("Thumbnail Height"),
  *   } 
  *   default_name = 'filename',
  *   thumbnail_uri = 'thumbnail_url',
  *   thumbnail_alt = 'title',
  *   thumbnail_uri = 'thumbnail_width',
  *   thumbnail_uri = 'thumbnail_height',
  */
interface MediaSourceInterface {
  // @MediaSource-level logic
  function getDefaultThumbnail();

  // MediaType-level logic (i.e. depends on media type config entity)
  function getSourceField();
  function getSourceFieldConstraints();

  // Media-level logic (i.e. depends on media content entity)
  function getMetadata(MediaInterface $media);
Iteration 3
And maybe we can also remove getDefaultThumbnail(), because it is really just returning the path/URI to one particular image file. Further indication that this makes sense: it receives no parameters.The tricky thing there is that we have no precedent of referencing
/**
  * @MediaSource(
  *   id= "image"
  *   default_thumbnail = "media_icons://generic.png",
  *   allowed_source_field_types = {"file", "image"},
  *   metadata = {
  *     mime = @Translation("MIME type"),
  *     size = @Translation("Size"),
  *     filename = @Translation("File name"),
  *     uri = @Translation("File URI"),
  *     width = @Translation("Width"),
  *     height = @Translation("Width"),
  *   } 
  *   default_name = 'filename',
  *   thumbnail_uri = 'uri',
  */
interface MediaSourceInterface {
  // MediaType-level logic (i.e. depends on media type config entity)
  function getSourceField();
  function getSourceFieldConstraints();

  // Media-level logic (i.e. depends on media content entity)
  function getMetadata(MediaInterface $media);

or perhaps:

/**
  * @MediaSource(
  *   id= "image"
  *   default_thumbnail_file_uri = "media_icons://generic.png",
  *   allowed_source_field_types = {"file", "image"},
  *   metadata_labels = {
  *     mime = @Translation("MIME type"),
  *     size = @Translation("Size"),
  *     filename = @Translation("File name"),
  *     uri = @Translation("File URI"),
  *     width = @Translation("Width"),
  *     height = @Translation("Width"),
  *   } 
  *   required_metadata = {
  *     default_name = 'filename',
  *     thumbnail_uri = 'uri',
  *   }
  */

I hear some of you say: Wim, you just removed methods, that doesn't make it conceptually simpler!, but I don't think that's true. Thanks to all this refactoring, I think we have come to an important realization: these plugins really are only about two things:

  1. a source field to store a reference to a media item in some media source
  2. metadata about that referenced media item, with some of that metadata being important for our UX: default name and thumbnail

I came to this realization thanks to two big shifts from #236 until this point:

  1. refactoring several methods away, which made the plugin type more focused
  2. in this very comment coming to the realization that many of the remaining methods are really just

Framed in this way, I can explain what the responsibilities of a @MediaSource plugin are (this is what could go in the interface classdoc):

Media source plugins make it possible to use media in Drupal, regardless of source (local or remote). Drupal is then able to treat Media entities as any other entity.

Media source plugins have three tasks:

  1. specify which field types are able to store a reference to a media item on this media source — this is what it calls "the source field"
  2. making metadata about the referenced media item available to Drupal
  3. identify which metadata fields provide the necessary information for the 3 required pieces of metadata: default_filename, thumbnail_url, thumbnail_alt.

Site builders can optionally configure a Media Type to store (mirror) some or all of the metadata in Entity Fields, which makes it possible to for example complex queries and listings with the Views module.

Thoughts?

wim leers’s picture

We've been discussing #262 in IRC.

Takeaways:

  1. yay for the overall proposal
  2. extra yay because it brings it back to where @MediaHandler was before, without handlers, so less divergence from the existing contrib API. We're "just" refactoring a bunch of methods away.
  3. BUT all of this (i.e. basically what I've been doing since #236) is effectively rearchitecting the module, so how could it be argued that it's "stable"?
  4. (note that the flexibility promised by handlers can also be achieved with plugin interfaces, see e.g. interface CKEditorPluginButtonsInterface extends CKEditorPluginInterface — this is feasible once we no longer have 11 methods)
  5. BUT it removes too much flexibility, for example there are needs for metadata providing field types, dynamic metadata, entity constraints, and so on — despite NONE of those having A) any implementations in this patch, B) any test coverage whatsoever. We really really need this. See e.g. \Drupal\ckeditor_test\Plugin\CKEditorPlugin\LlamaContextualAndButton + \Drupal\ckeditor_test\Plugin\CKEditorPlugin\LlamaContextual + …. (The remark was made that many experimental modules landed with more test coverage than this stable module would have had.)

So at this point, it's clear that my reviews have been incomplete/inaccurate, because I didn't look at the ways that every existing @MediaHandler plugin in contrib has been exercising this API. So I'm not aware of all the ways that may be possible. So I drew overly simplistic conclusions.

To fix that, we need to add that missing test coverage proving that all those advanced use cases work. And then we can have actually informed reviews, not too simplistic ones.

In other words: this is not feasible to do before 8.3, I think.

slashrsm’s picture

Status: Needs review » Needs work

Before proceeding with this patch we need to decide which overall architectural approach we're taking (a) initial MediaHandlerInterface, b) StorageProxyInterface with handlers or c) the last one with heavy use of annotations). It is not worth spending any time on adding additional tests before this decision is made. Adding more code to this patch will made future rewrites even harder and we should avoid that.

c) is not feasible in my opinion since it removes a lot of flexibility which essentially media entity is all about. b) is nice but we have knowledge sharing problems between storage proxy and handlers. We could solve that by sharing all the information between them. Since that would make them mostly inter-dependent we lose the main benefit of this approach - clear separation of concerns. a) is also not ideal, but we have to be aware that is the only approach that is tested in real life. Thunder, Lightning and many other productions projects are using it. It is stable (as in 1.0) since spring 2016. There was no reports in last 2 years about any major flaws. Just minor things that we were able to easily resolve. If we fix minor problems in it and update documentation it could be the best option that we have.

wim leers’s picture

Assigned: wim leers » Unassigned

It is not worth spending any time on adding additional tests before this decision is made.

How are we going to land on the right architecture if we don't know which use cases we need to support?

I'd be fine with throwing away all the work I did, going back to @MediaHandler (a) and add the test coverage to it. My work may or may not be a dead end. That is fine :)

(Also forgot to unassign myself in #262.)

slashrsm’s picture

How are we going to land on the right architecture if we don't know which use cases we need to support?

We tried, came up with two alternative solutions and realized at the end that they are at least as bad as the original one. That says something too, right?

wim leers’s picture

Indeed! Which is why I said:

I'd almost suggest adding the media_entity module as-is to Drupal core, with only adding test coverage and improving documentation. Without renaming anything. Without any other changes. Because then we would be actually putting a stable module into core, without changing any API.

https://www.drupal.org/node/2825215#comment-11878451

Or, alternatively, starting from before my changes in #236, and just renaming the plugin type to MediaSource, renaming the methods (e.g. "Field" → "Metadata") and adding docs and tests.

Also, please know I did this with the best of intentions. The pain we are going through with the rest and serialization modules and their APIs that seemed solid and inspired on contrib modules is something I'd like to spare you. Those modules also worked fine in the seemingly broad use cases that we tested in core.
Furthermore, I am reading and hearing +1s for the concerns I raised, so likely core committers would raise similar concerns.

Also, I almost only had remarks on the plugin type, most other things I didn't say anything about — so it's not like I'm shooting the entire patch down!

The best course of action here is *adding tests*. I can't stress enough that if there were tests exercising the advanced use cases that my proposals are making impossible, that I'd never have made those proposals! So, really, ignore the majority of what I said. Add tests. Then ask for reviews again.

This patch would never have been committed to core with the current test suite.

P.S.: wrote this from my phone, forgive typos and lack of formatting.

dawehner’s picture

Thank you @Wim Leers for your hard effort to try to simplify things.

Media already has a long history in Drupal. People have been working on media related functionality since basically forever. This resulted in the amazing work of the media team in Drupal 8, with all of the contrib modules out there. The result is a huge range of fundamentals, like inline entity form/entity browser, but also more important here integrations into all systems.

I can't stress enough that if there were tests exercising the advanced use cases that my proposals are making impossible

This here is for me the major difference to REST and serialization. Unlike REST and serialization, the media suite, with all its implementations (from twitter, facebook, soundcloud) already have running solutions on live sites, while these modules got developed. This proves that the given usecases of those modules haven't been just academical. On the other hand we had REST/serialization which came from a high theoretical background and then have been thrown onto people, who struggled with them. IMHO the verbosity of HAL for example would have been uncovered way earlier, when people would have used it on a day to day base.

gábor hojtsy’s picture

Issue summary: View changes
StatusFileSize
new119.31 KB

Let's start with drawing up what we have then. This is not all the classes, there are lots of form handlers and actions and views integration, etc. but those were not in question. So focused on the main architectural pieces. We probably need to improve this picture to be more expressive of the interactions between how responsibilities are shared/delegated, or we may want to draw another figure for that that does not involve all the class hierarchy.

Google doc at https://docs.google.com/drawings/d/15U9pdSSSUjLlOshpvKf_i5PVVhlFK7_xTKbR...

wim leers’s picture

To clarify, as far as I'm concerned, the amount of work or change does not need to be big at all (or not as big as it's been sounding lately):

  1. we can go back to what it looked like before I explored "handlers" starting in #236, and do:
    • some renaming (@MediaHandler -> @MediaSource, getField() -> getMetadataAttribute() etc.)
    • some already agreed upon simplification (removing auto_create_source_field and merging SourceFieldInterface)

    Much of point this is trivial. Much of it can be lifted from the interdiffs I posted.

  2. Test coverage, test coverage, test coverage. I'm 95% certain that even if I had said zero architectural concerns!, core committers would not have committed this for lack of test coverage.

    If you write Functional tests then those tests won't need to change at all when architectural details change.

Functional tests can be written regardless of any architectural change we make. That's what needs to happen now to move this issue forward IMHO. I can't do that.

gábor hojtsy’s picture

I agree that the field vs. metadata problem is pretty real in the API, and needs to be rectified. I had the same complications understanding it due to clashes with Drupal terminology.

Also wanted to point out that @Wim Leers is not a framework manager, so framework managers could still raise further concerns or disagree with Wim (or agree with Wim). I think @effulgentsia took this on himself to provide that kind of feedback so far and will still need to follow up on the latest discussions. For now he is as far as I know focusing on things that do have a deadline this week to get in before the 8.3 alpha release.

slashrsm’s picture

#270 sounds good to me.

I am off and mostly offline this week. I will be back and focusing on this after I am back. However, I do not want to block anyone so feel to continue if anyone has spare resources to dedicate.

gábor hojtsy’s picture

#2835861: Discussion: Expose a path field for media entities concluded with adding the path field. I would love to add it here and close that (it is a one line change), but since its not clear which direction to work off of yet, holding off on it for now.

Here are relevant meeting snippets from the media meeting today:

15:45 <WimLeers> My update is very simple: I was asked to do an architectural review. That I did. And then, to hopefully make it still possible to land in time, I worked on my proposal that slashrsm and others +1'd, but then turned out to be a dead end. I've articulated my key concerns, and the absolute hard blockers (a few renames, auto_create_source_field, and most
15:45 <WimLeers> importantly: test coverage). Now it's out of my hands :)
15:46 <seanB> I haven't had much time since last friday, also following the discussion on the main media patch. I though I could work on Wims last comments (renaming MediaHandler and adding test coverage)
15:47 <seanB> It would help if we could make a list of thing we want to add test coverage for.
15:47 <GaborHojtsy> seanB: superb, let's define with effulgentsia then who arrived in the meantime if he agreed with some part of the discussion so we can pick a direction and prioritise steps
15:47 <effulgentsia> hi
15:49 <seanB> GaborHojtsy: That would be great!
15:49 <effulgentsia> From comment #264 (https://www.drupal.org/node/2831274#comment-11878270):
15:49 <Druplicon> https://www.drupal.org/node/2831274 => Bring Media entity module to core as Media module #2831274: Bring Media entity module to core as Media module => 273 comments, 55 IRC mentions
15:49 <effulgentsia> “c) is not feasible in my opinion since it removes a lot of flexibility which essentially media entity is all about"
15:49 <effulgentsia> What is the flexibility that we need?
15:50 <effulgentsia> That could be expressed in test coverage. Or in comments/docs. Or in pointing to which contrib modules exercise some interesting flexibility.
15:53 <effulgentsia> I would suggest either implementing #262 iteration 3, and then writing test coverage that demonstrates its inadequacy, so we can one by one look at which things require methods rather than annotations, or other things like how to get entity constraints in addition to source field constraints.
15:55 <GaborHojtsy> seanB, chrfritsch, mtodor, phenaproxima: do you have enough experience with those advanced modules that need that flexibility?
15:55 <effulgentsia> In particular, I think identifying the use cases where the annotation structure proposed by Wim is insufficient.
15:56 <WimLeers> effulgentsia: just docs/comments is insufficient. Tests for those use cases are a HARD requirement IMO, because how else can you prove they actually work?
15:57 <phenaproxima> GaborHojtsy: No, I do not
15:57 <effulgentsia> I mean, we could start with comments that say “default_thumbnail_file_uri” needs to be a method because here’s an example of a handler where it needs to be dynamic. then, if we agree with that assessment, we could implement it as a method and write a test for it.
15:57 <phenaproxima> GaborHojtsy: I think relative inflexibility will suffice in 90% of use cases. But this is Drupal, and you never know what kind of crazy shit someone wants to do. So usually it's good to back stuff up with logic.
15:58 <GaborHojtsy> phenaproxima: I think slashrsm had concerete use cases, so would be good to dig those up :)
15:59 <GaborHojtsy> if we have implementers of media type modules around that do advanced things
15:59 <seanB> GaborHojtsy, effulhentsia: While I don't directly know which methods we can or can not remove, I could take a look at what contrib is doing and find out? We are talking mostly about the MediaHandlerInterface right?
16:00 <effulgentsia> WimLeers: in #262 iteration 3, for getSourceField() and getSourceFieldConstraints(), were you intending to pass a parameter to them? E.g., $media_type?
16:02 <effulgentsia> seanB: yeah, I think in particular, comparing MediaHandlerInterface from before Wim began his large changes to it to what is proposed in #262 iteration 3.
16:04 <effulgentsia> For example, are there use cases where contrib MediaHandlerInterface::getThumbnail() needs to return something other than what is available as a value within getMetadata() within a constant key that can be statically identified in an annotation.
16:06 <effulgentsia> Similarly, #263.5 says “metadata providing field types”: what does that mean? And “dynamic metadata” (meaning, a set of key names not definable in an annotation: what’s an example?)
16:08 <effulgentsia> seanB: in other words, we may end up with something in between a MediaHandlerInterface with 11 methods and #262’s proposal of a MediaSourceInterface with 3 methods. But at the end of it, for whatever number of methods we end up with, we’ll have more clarity around why they’re needed.
16:09 — GaborHojtsy is going to post this last part of the meeting on the issue verbatim :)
16:12 <GaborHojtsy> is that a good direction seanB you can run with or more clarification needed?
16:12 <GaborHojtsy> let's make sure we unblocked something :D
16:12 <seanB> effulgentsia: This is something we can work on. Basically we remove methods like proposed by Wim. For the methods we decide to keep we should add tests and documentation providing an example.
16:13 <effulgentsia> yep. thanks!
16:13 <GaborHojtsy> seanB: as per slashrsm the contrib modules provide those examples / use cases, so one would need to look around and identify those
16:14 <seanB> effulgentsia: Are there other parts missing test coverage?
16:14 <seanB> GaborHojtsy: I will take a look at what contrib is doing whil evaluating the methods!
16:15 <effulgentsia> seanB: I don’t know. But I think getting MediaSourceInterface nailed down (which I like as a name over MediaHandlerInterface, even if it ends up looking a lot like the former MediaHandlerInterface) is the main thing.
16:16 <seanB> WimLeers: Do you have any thought on the test coverage question? You know the patch a little better by now I think :)
16:21 <naveenvalecha> seanB: I'll try to work on media patch tomorrow. leave a comment before you left the issue.
16:24 <seanB> naveenvalecha: Yes, I will make a start tonight. I will assign/unassign and post a patch when I have something.
16:24 <naveenvalecha> seanB cool :)
16:25 <seanB> phenaproxima: When do you plan working on the auto-create source field stuff?
16:25 <phenaproxima> seanB: Working on it right now
16:26 <seanB> phenaproxima: That's awesome, just post whatever you have when you're done and I'll pick it up from there tonight.
16:26 <phenaproxima> seanB: I'll do my best
phenaproxima’s picture

StatusFileSize
new22.52 KB

Here's an interdiff that removes auto_create_source_field entirely. I can post a full patch if needed, but I'm not sure if I should, considering how much major refactoring is going on. With these changes applied, all media tests are passing on my localhost.

I had to axe a bunch of test coverage that no longer makes sense in the absence of auto_create_source_field. I added a bit of new coverage to the Media UI functional test to compensate, but we could probably add more.

The crux of the change(s) here is this: media types are no longer responsible for ensuring the existence of the source field. The handlers are responsible for loading the source field, or defining it if it doesn't exist; the MediaTypeForm, or other calling code, is responsible for saving the field and field storage definitions.

EDIT: I apologize for the use of warts like method_exists($handler, 'getSourceField'). For some reason, I was having test failures when I tried to do $handler instanceof MediaStorageProxyInsanelyLongWhateverSourceFieldSupercalafragilisticexpialidociousInterface (somehow, that interface was not being loaded), so in light of the other refactoring that's happening, I took a shortcut.

seanb’s picture

Here is a list of all the MediaHandlerInterface/SourceFieldInterface methods and the contrib modules implementing them. I basically came to the same conclusion as #2831274-236: Bring Media entity module to core as Media module.

Summary:

  • getLabel() - remove
  • getProvidedFields() - rename, expand documentation if needed and add tests
  • getField() - rename, expand documentation if needed and add tests
  • attachConstraints() - keep, expand documentation if needed and add tests
  • shouldUpdateThumbnail() - keep, expand documentation if needed and add tests
  • getThumbnail() - keep, expand documentation if needed and add tests
  • getDefaultThumbnail() - keep, expand documentation if needed and add tests
  • getDefaultName() - keep, expand documentation if needed and add tests
  • mapFieldValue() - remove
  • getSourceField() - keep, expand documentation if needed and add tests
  • getSourceValue() - remove

The remaining work I see when using the patch in #245.

  1. Change documentation as suggested in #234
  2. Address #247-3
  3. Rename methods getProvidedFields(), getField(), getSourceField()
  4. Rename MediaHandler to MediaSource
  5. Merge SourceFieldInterface into MediaSourceInterface
  6. Changes mentioned in #250
  7. Move MediaThumbnailHandler code to Media entity and removed the service
  8. Improve documentation and add test coverage for remaining methods in MediaSourceInterface.

Some more details:

getLabel()
None of the contrib modules providing MediaHandlers/MediaSources implement this. This basically means that the annotation should be enough and you can fetch it from there as suggested in #2831274-236: Bring Media entity module to core as Media module.

getProvidedFields()
Basically all contrib modules implement this. Most of the times it is empty or a static list. Some modules have conditions though, eg media_entity_instagram, media_entity_twitter provide a extra option on the configuration form to use the instragram/twitter API. When using the API you get more fields to configure. We could expand documentation and provide a test for this. Renaming to getMetadataDefinition or getMetadataLabels makes sense.

getField(MediaInterface $media, $name)
Same as previous. Renaming to getMetadata makes sense.

attachConstraints(MediaInterface $media)
This is implemented (at least) by media_entity_instagram, media_entity_slideshare, media_entity_slideshow and media_entity_twitter. As slashrsm suggested in #2831274-261: Bring Media entity module to core as Media module it is good to be able to add constraints on entity and field level. For example media_entity_slideshow adds a contraint on entity level (although I think there is room for improvement in this example). Some concerns were raised in #2831274-236: Bring Media entity module to core as Media module, but I think it safe to assume that developers that create constraints need to know something about the Entity API. My suggestion would be to expand documentation and provide a test for this.

shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE)
This method was discussed between #180 and #187. Some media thumbnails will never change, some will change more often, most will depend on the source field value. Specially when dealing with external media sources it is good to be able to influance this. Since it is introduced in this patch, no contrib modules implement this yet. We could move this to the Media entity? My suggestion would be to expand documentation and provide a test for this.

getThumbnail(MediaInterface $media)
All contrib modules implement this in various ways. Some use a metadata field, some use mime types or the source field. I think this one should stay for sure.

getDefaultThumbnail()
Most contrib modules implement this, but it could be moved to a annotation. It basically returns $this->config->get('icon_base') . '/[source].png'; all the time. There could be a case where you want to fetch an image from another location, not the icon base dir. My suggestion would be to expand documentation and provide a test for this.

getDefaultName(MediaInterface $media)
All contrib modules implement this in various ways. Some use a metadata field, some use the source field or a value of the entity related in the source field. I think this one should stay for sure.

mapFieldValue(MediaInterface $media, $source_field, $destination_field)
No contrib modules implement this. It is only used in 1 place. I think it's safe to just move the code there and remove this method.

getSourceField(MediaTypeInterface $type)
No contrib modules implement this, but this method is actually important. I think this one should stay for sure. Renaming to getSourceFieldDefinition makes sense.

getSourceValue(MediaInterface $media)
No contrib modules implement this. It is only used in 1 place. The function docs speaks about possibilities for edge cases, but contrib doesn't provide any examples of this. I think it's safe to just move the code there and remove this method.

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new215.29 KB
new84.37 KB

In the end I based the patch on #229 and tried to fix everything that was mentioned after that. Applied (parts of) the patches posted and fixed some left over comments.

  • Changed documentation as suggested in #234 (not everything, took the best of both)
  • Removed methods getLabel(), mapFieldValue(), getSourceValue() as done in #243 and #245
  • Addressed #247-3
  • Renamed methods getProvidedFields(), getField(), getSourceField() as mentioned in #248 / #243
  • Merged SourceFieldInterface into MediaSourceInterface
  • Removed auto create source field with code from #275
  • Changes mentioned in #250
  • Renamed MediaHandler to MediaSource

Still to do:

  1. MediaCacheTagsTest fails with a 'Field unknown' error (probably because of the auto create source field changes)
  2. Move MediaThumbnailHandler code to Media entity and removed the service
  3. Improve documentation and add test coverage for remaining methods in MediaSourceInterface.

Not sure where to move MediaThumbnailHandler, the Media entity might not be the best place. Suggestions are welcome. Also adding test coverage should be done once we agree that the MediaSourceInterface is correct now. Hope to get some feedback on this, I can move forward tomorrow.

Setting to 'Needs review' to get some feedback.

wim leers’s picture

#275: that looks splendid :) Thanks!


#276: It's interesting that you arrived at the same (or at least similar) conclusion even after inspecting all media contrib modules. Because @slashrsm has been saying that my conclusions were incorrect, because more flexibility was necessary? Of special interest:

  • I do see you concluded attachConstraints() is necessary, and needs to be able to add constraints to both entity and field (happy to be wrong there!).
  • It's not clear to me why you say that getDefaultThumbnail() always uses a static (hardcoded) return value, yet you don't suggest moving it to the annotation?

Overall, I'm glad to see that you're validating my analysis, but then also refining it further. It's always reassuring when two people independently arrive at very similar conclusions :)

Also: impressive that you analyzed all media contrib modules! Thank you so much, it's a huge contribution to this issue and initiative!


#277:

If #276 was impressive, this one is even more impressive. I think the correct technical term is: HOLY SHIT. This is one hell of a way to get this back on track!

Basically +1 to #277 — it's a huge step in the right direction, basically incorporating EVERYTHING since #229! Once again: holy shit-level impressive work. Thanks so much.

I also agree that the most likely cause for the test not passing (but wow, only a single fail!) is a small problem in @phenaproxima's interdiff in #275. Pinging @phenaproxima to hopefully fix that :)

Finally, a review of #277:

  1. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,196 @@
    +  public function getMetadata(MediaInterface $media, $name);
    

    s/$name/$attribute_name/ for slightly better clarity.

  2. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,196 @@
    +  public function attachConstraints(MediaInterface $media);
    

    I'm still concerned about this actually attaching constraints. Would it not be better to return both entity-level and field-level constraints, and then let the Media module take care of the actual attaching?

  3. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,196 @@
    +  public function shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE);
    

    Regarding this and the associated todo you mentioned (Move MediaThumbnailHandler code to Media entity and removed the service): I think comment #260 — which is posting the conclusion of a discussion — is still a sane way to go with this.

  4. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,196 @@
    +  public function getThumbnail(MediaInterface $media);
    ...
    +  public function getDefaultName(MediaInterface $media);
    

    I still think we can remove this in favor of moving this into getMetadata(Attributes). Usually this is literally returning some metadata attribute. Where that's not the case, this would be a computed metadata attribute.

    (I wrote about this before in #262.)

    Either way, just allowing the annotation to specify which metadata attribute to use for the thumbnail + default name would reduce the API surface.

    I don't feel strongly about this — just making sure that is considered.

  5. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,196 @@
    +  public function getDefaultThumbnail();
    

    See earlier in this comment: why can't this be an annotation?

  6. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,196 @@
    +   * @return \Drupal\field\FieldConfigInterface|null
    +   *   The source field definition, or NULL if it doesn't exists yet
    +   *   or has not been set in the $source configuration.
    +   */
    +  public function getSourceFieldDefinition(MediaTypeInterface $type);
    

    This is not actually a field definition. Because that would be a \Drupal\Core\Field\FieldDefinitionInterface instance.

    So this name still needs some refinement.

webflo’s picture

Status: Needs work » Needs review
StatusFileSize
new618 bytes
new215.24 KB

I tried to debug Drupal\Tests\media\Functional\MediaCacheTagsTest and i could not run it via phpunits, it is actually a simpletest in the wrong namespace.

webflo’s picture

Issue tags: +SprintWeekendBerlin
StatusFileSize
new2.03 KB
new215.14 KB
seanb’s picture

StatusFileSize
new216.27 KB
new14.96 KB

Thanks webflo, for fixing the tests! Just finished working on 279.

279-1. Done

279-2. Still todo

279-3. Done. Not every media source needs to update the thumbnail all the time. Probably only media sources that use images as a thumbnail (image/youtube/instagram) will need this. When we move this to the Media entity as suggested in #260, we lose to possiblity for Media Sources to decide when to update the thumbnail. I tried to solve this with a new annotation. I think this is all the flexibity we actually need. Media entity in contrib never updated the thumbnail anyway.

279-4. Done. After thinking about this a bit more, I started implementing this and now see changing this doesn't remove flexibility, it just moves some logic to getMetaData. I like it!

279-5. Done, while having the ability to use another folder is flexible, it is not used in contrib at the moment. So it's an edge case we probably won't need. Another option is to also move this to a meta data attribute, but I think it's ok now.

279-6. It returns a configurable field definition (which is still a field definition). So the exact name would then be getSourceFieldConfigFieldDefinition or getConfigSourceFieldDefinition. Which also means we might have to think about renaming createSourceField to createConfigSourceField. I prefer to keep it as it is, the current situation is easier to read and understand. But if we must change it, getConfigSourceFieldDefinition and createConfigSourceField are my second choice. Thoughts?

To sum up, still to do:

  1. Move MediaThumbnailHandler code and remove the service (still not yet sure where to put this?)
  2. Improve documentation and add test coverage for remaining methods in MediaSourceInterface. Add extra tests for some of the new annotations.
  3. 279-2: Use annotation to attach constraints to entity/field level.
seanb’s picture

StatusFileSize
new213.1 KB
new13.85 KB

Also removed the MediaThumbnailHandler and moved the code to the Media entity. That seemed to be the most logical place to do this.
BTW I moved the MediaCacheTagsTest back to it's original location since the namespace was not the problem. All the tests are now in 1 place again.

gábor hojtsy’s picture

Status: Needs work » Needs review
StatusFileSize
new213.81 KB
new1.18 KB

Adding collection label as per #212 above, resolving one of @effulgentsia's concerns.
Adding path field on media entity as per #2835861: Discussion: Expose a path field for media entities.

slashrsm’s picture

Impressive work @seanB! Thank you (and all others!) for all your help pushing this forward. Just a few comments:

shouldUpdateThumbnail()

I actually think that we should update the thumbnail every time when source field value changes. I don't think that we need annotation value to enable that. If source field changes it is extremely likely that the asset changed significantly which will result in a new thumbnail.

There is one case that is not covered now when we removed this function. Example: YouTube video, thumbnail was fetched from their servers, it changes upstream. This won't result in the thumbnail being updated. However, to cover this case we'd need to issue an API call whenever a given media entity is updated which we wanted to avoid.

I think that current approach solves 80% case and that there are still ways to solve problems like the one described above in contrib.

getDefaultThumbnail()

This is always static and can be an annotation. It is already done in the patch so basically +1-ing it.

mapFieldValue()

The reason this is not used in existing contrib modules is that it was added in this patch. Yes, we didn't add tests. Yes, I take responsibility for this. We should have done it better.

UX team agreed that we improve mapping UI in a follow-up. This function would give us enough flexibility to be able to do that properly.

The reason to add this was the fact that we have some limitations in the current approach. They were identified based on the contrib experience with the module and we wanted to solve that:

  • It doesn't handle complex field types: currently the code takes metadata value and assigns it to the main property of the field. It doesn't handle fields that might rely on more than one property. An example of that would be the Link field with "uri" and "title" properties. Currently we can only set uri. There is a workaround for that: if getMetadata() is aware of this it can prepare metadata value in a right way. I don't think that getMetadata() should care what the destination of the value will be.
  • Pre-processing of the metadata value is not possible: Example are tags. They usually come in form of a comma-separated list. If you throw that into an entity reference field nothing useful will happen. You would generally want to preprocess that to convert that comma-separated list to a list of entities and use their IDs. Same workaround as in the first item applies.
  • Multi-value fields: Only single value fields are supported. Same workaround as in the first item applies.
  • Updating mapped values: Currently we map values on initial save only. If metadata changes upstream at some later time we won't update. There is currently no workaround for that (except custom code).

I was thinking about this a bit more and it seems that we could use a separate plugin type to solve this problem (MediaMetadataMapper or something along those lines). It is true that it isn't the job of the MediaSource to solve mapping. By introducing a new plugin type we separate responsibilities and clearly document them.

It is also not needed to introduce this new plugin type in this patch. We could do it as part of the mapping UI follow-up or even in a separate issue that would be a requirement for UI one.

Would love to hear thoughts about this.

attachConstraints(MediaInterface $media)

I agree with @Wim Leers that it is not very nice that developers need to add constraints. It would be much nicer if they would only return them. However, I'd like to keep the possibility to attach to both levels (field and entity).

Two solutions come to my mind:

  • Have two functions: getEntityConstraints() and getSourceFieldConstraints(). Since we're trying to lower the number of functions this isn't the best solution I guess.
  • Return an array with two values on the first level: one for each type of constraints (or any other similar data type that would support the use case). I have concerns about the DX in this case. It is also prone to errors.

Any other ideas about this? Thoughts?

MediaSource testing

Yup, we need to improve this. We need to cover more complex use-cases related to it. I can volunteer to look into his in the next few days. I will assign the issue to me when I find time for that. Feel free to grab it if anyone finds time sooner.

seanb’s picture

shouldUpdateThumbnail()
Removing the annotation sounds fine. If a MediaSource has a hardcoded value the overhead is minimal.

getDefaultThumbnail()
Yay!

mapFieldValue()
Thanks for your input. Sounds like good arguments to keep/improve it. About the extra plugin type for metadata mapping, should we create mapping plugins for source field types (for example taxonomy fields)? It would help if we can come up with a list of mapping plugins and what they are supposed to do.

attachConstraints(MediaInterface $media)
How about moving this to the annotation?

boobaa’s picture

Tried to jump on this train early by adding a MediaSource plugin to our Brightcove module. We'll need a source field with the entity_reference type (brightcove_video is an entity type in Drupal that stores info about the videos hosted on Brightcove). So I added allowed_field_types = {"entity_reference"} which is the only thing that makes sense here, I guess – but I cannot find any way to provide additional required info: the entity type this field must reference.

So I think there should be a way to provide this additional info, probably in the annotation, but as an optional thing. Something like settings = { "target_type" = "brightcove_video" } would do in our case, which would mean "field configuration needs to include these configuration values" (per IRC discussion with @slashrsm). Rationale: there are some field types which have required settings, so the MediaSource plugins should be able to provide those settings.

Not sure if this warrants a "Needs work" status, tho.

seanb’s picture

So after discussing this on IRC it seems the following is the case (@boobaa/@slashrsm please correct me if I'm wrong)

  • MediaSources are responsible for create a source field through configuration and providing a Media Type using this, the settings should not be a problem there.
  • When create a new Media type, users can choose to use a source field of their own. This is where we might want to validate some settings on the source field as mentioned by boobaa.
  • Add validation for source field settings. At a minimum there should be validation when saving the Media type, but since you can edit the source field you might want to validate every time the source field is saved, or prevent changing some settings on known source fields.

This makes the list of things to fix:

  • Improve documentation and add test coverage for remaining methods in MediaSourceInterface. Add extra tests for some of the new annotations.
  • Discuss and eventually replace attachConstraits().
  • Remove shouldUpdateThumbnail()
  • Discuss and bring back mapFieldValue()
  • Add validation for source field settings.

I think this example shows we need to start porting other media source modules as well. This might bring up more issues?

seanb’s picture

We just discussed the issue of boobaa in #291 on IRC. The following is a summary of what was discussed. Please correct me if I'm wrong!

  1. Creating a source field for a default MediaType in a module can be done by providing default config. This is already possible.
  2. Auto creating a source field for a manually created MediaType can be done by implementing createSourceField() in the MediaSource plugin. This is already possible.
  3. Reusing existing source field for an manually created MediaType currently allows all fields for a type provided in the annotation of the MediaSource plugin. It would help to allow something like getSourceFieldCandidates() on the MediaSource plugin, to allow plugins to limit the source fields a user can select. Maybe there are alternatives since we want to reduce the surface of MediaSourceInterface?
  4. We need to lock source fields to make sure users can't change the settings afterwards. I think this can already be done through createSourceField()?

Boobaa suggested adding settings through annotations. Allowing field settings for the source field through an annotation could remove the need to override createSourceField() in #2. The settings could at the same time be used to filter the list of source fields and remove the need for #3. Locking auto created source fields by default could remove the need for #4.

The annotation's look like the easier solution for module developers. Any thoughts?

seanb’s picture

Status: Needs work » Needs review
Related issues: +#2824979: The DataDefinitionInterface is missing setConstraints method
StatusFileSize
new215.08 KB
new6.47 KB

Just had time to take another look at this. Attached is a patch with the following changes:

  • Replaced attachConstraits() with getEntityConstraints() and getSourceFieldConstraints() as suggested by slashrsm in #289. Of all options this one seemed to be the best. To fix this as clean and consistent as possible, I depend on a patch proposed in #2824979: The DataDefinitionInterface is missing setConstraints method. We can change this if we need to, but it makes sense to do it like this.
  • Removed the update_thumbnail_on_source_field_change annotation as suggested in #289
  • Looked at mapFieldValue(). Since we want to improve mapping anyway, it seems best to keep this method out of MediaSourceInterface. We can always add it back later if this is the best way to solve mapping, but removing it later is probably worse. I did add a check for a changed source field value to update metadata, since you probably want to update the metadata when the source field value changes (also mentioned in #289).
  • Introduced a protected method getSourceFieldOptions() to MediaSourceBase. This can be used to change the list of source field options for the media type form as mentioned in #294-3. This means we can fix the issues mentioned by boobaa in #291 by overriding createSourceField() and getSourceFieldOptions() of MediaSourceBase. Since we allow multiple field types, allowing settings for these through annotations would require a nested array which is hard to validate and could lead to errors. So this seemed like the best option to me.

Please review! We haven't discussed everything in depth, but I hoped this would help move the issue forward. I think we should start writing tests when we are sure what the MediaSourceInterface would look like.

Bojhan’s picture

I am wondering here - do we really need to have a top level link next to 'add content' to "add media" ? I dont think the 80% case will be to separately add it, most people will do it through the content creation screen and those that really do media management will be pointed to content > media.

gábor hojtsy’s picture

I am wondering here - do we really need to have a top level link next to 'add content' to "add media" ? I dont think the 80% case will be to separately add it, most people will do it through the content creation screen and those that really do media management will be pointed to content > media.

Bojhan I think you mean shortcuts in the menu? I don't think this patch is adding that (after searching through the patch again). The patch *does* add an Add media link on the media admin page itself (similar to admin/content's Add content link).

seanb’s picture

StatusFileSize
new215.11 KB
new425 bytes

am wondering here - do we really need to have a top level link next to 'add content' to "add media" ? I dont think the 80% case will be to separately add it, most people will do it through the content creation screen and those that really do media management will be pointed to content > media.

In #200 effulgentsia mentioned that one of the nice things provided by the media module is the separation of adding and referencing media and also provides a use case for this scenario.

Also a new patch because I broke the tests...

wim leers’s picture

#285:

  1. Yay!
  2. Ok.
  3. That sounds entirely sensible! :)
  4. Yay!
  5. Hm… not yet sure what's best here.

Review:

  1. +++ b/core/modules/media/src/MediaSourceBase.php
    @@ -123,22 +123,17 @@
    +  public function getMetadata(MediaInterface $media, $attribute_name) {
    

    This is not yet handling attributes other than 'default_name' or 'thumbnail_uri'.

  2. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -119,57 +119,6 @@
    -  public function shouldUpdateThumbnail(MediaInterface $media, $is_new = FALSE);
    ...
    -  public function getThumbnail(MediaInterface $media);
    ...
    -  public function getDefaultThumbnail();
    ...
    -  public function getDefaultName(MediaInterface $media);
    

    So much simpler! :)


Nice work!

Review:

+++ b/core/modules/media/src/Entity/Media.php
@@ -132,6 +134,65 @@
+  public function updateThumbnail($from_queue = FALSE) {

Why does this need to be public?

EDIT: oh because it needs to be called from ThumbnailDownloader.

I'm wondering if we should make updateThumbnail() protected and add a separate public method without this $from_queue parameter?

Because the only reason to call the method with $from_queue = TRUE is if it's a call from outside the Media entity class, which means that that is what we actually need to live on the interface.


#287 + #288: nice!


#289:

shouldUpdateThumbnail()

I actually think that we should update the thumbnail every time when source field value changes.
[…]
There is one case that is not covered now when we removed this function. Example: YouTube video, thumbnail was fetched from their servers, it changes upstream.
[…]
I think that current approach solves 80% case and that there are still ways to solve problems like the one described above in contrib.

This sounds great!

getDefaultThunbnail()
Yay, glad to have your +1 :)
mapFieldValue()
All of your use cases sound plausible. I agree that getMetadata() should not not care what the destination of the value is. Updating mapped values seems quite important — although to solve this, it seems we also need to track some "remote timestamp" or "remote version", which we don't do currently.

All of this is arguably advanced functionality. Can we keep the current architecture with its simplistic assumptions, and allow a @MediaSource plugin to optionally implement additional interfaces to support those more advanced use cases? Or is that not desirable?

My point is: I'm not so sure we need a new plugin type for this. We can solve this by allowing our existing plugin type (@MediaSource) to have a base interface, and allowing specific plugin implementations to choose to opt in to advanced functionality. This is what the @CKEditorPlugin plugin type already does: by modeling CKEditor plugins onto Drupal @CKEditor plugins, we can reason about them inside Drupal. The base plugin interface doesn't do much, but not all CKEditor plugins need the same integration. CKEditorPluginInterface covers the basics/essentials. \Drupal\ckeditor\CKEditorPluginButtonsInterface must also be implemented if it provides buttons that user can enable for their toolbar. CKEditorPluginConfigurableInterface must also be implemented if it provides a configuration UI in Drupal. Both of those interfaces extend the basic plugin interface.

I think a similar approach can work here?

But regardless of approach/architecture, what is most needed for this is test coverage to prove that the architecture works. As a side effect, we also have at least one implementation, which means you can get at least a basic feel for how it ends up working/looking code-wise.

attachConstraints()
A similar solution to what I proposed above can work: MediaSourceWithEntityConstraintsInterface extends MediaSourceInterface and MediaSourceWithFieldConstraintsInterface extends MediaSourceInterface. That makes both methods optional, and any @MediaSource plugin can opt in to provide them.
Testing
Yay :)

#293:

but since you can edit the source field

Is it necessary that the source field is editable?


#295: see the suggestions I made in my reply to #289.

seanb’s picture

StatusFileSize
new214.89 KB
new7.44 KB

#300

This is not yet handling attributes other than 'default_name' or 'thumbnail_uri'.

Not exactly sure what you mean? Should it handle more by default? You are supposed to override this right?

Is it necessary that the source field is editable?

We can lock all source fields storage configuration the module creates, but you can re-use existing fields as well. Maybe we can lock storage config for all fields selected as a source field when saving a media type? Since not locking fields could cause issues we should probably do this.

  • Changed updateThumbnail() to protected and added updateQueuedThumbnail() public method.
  • Solving mapping is a little more complex. It would be great to solve this in a followup. Basic mapping is implemented and more complex mapping can be discussed separately.
  • Added separate interfaces MediaSourceFieldConstraintsInterface extends MediaSourceInterface and MediaSourceEntityConstraintsInterface extends MediaSourceInterface
  • Locked storage config for all fields selected as a source field when saving a media type.

If we can agree on fixing the more complex mapping in a followup, I think the last thing to do is tests.

slashrsm’s picture

Status: Needs work » Needs review
StatusFileSize
new215.37 KB
new987 bytes

This should fix the test fail that happened in #302. It was caused by #2851635: DefaultSingleLazyPluginCollection retains stale instance IDs. Before that patch DefaultSingleLazyPluginCollection didn't initialize the plugin when you initially created it. Now it does here:

  /**
   * {@inheritdoc}
   */
  public function addInstanceId($id, $configuration = NULL) {
    $this->instanceId = $id;
    // Reset the list of instance IDs since there can be only one.
    $this->instanceIDs = [];
    parent::addInstanceId($id, $configuration);
    if ($configuration !== NULL) {
      $this->setConfiguration($configuration);
    }
  }

$configuration can't be NULL since constructor type-hints an array. I am not sure if this should be considered a bug or not since the lazy collection doesn't seem to be lazy any more.

However, it is probably also a good idea to not initialize the collection on our side if the plugin ID was not set yet. The patch takes care of this.

In case you are wondering why it can even happen for plugin to be unset: it happens in the media type add form when the plugin wasn't selected yet.

tim.plunkett’s picture

#2851635: DefaultSingleLazyPluginCollection retains stale instance IDs is a standalone bug that was surfaced by the above problem. It should fix it for this case.
But that interdiff also seems like a reasonable optimization.

slashrsm’s picture

StatusFileSize
new228.11 KB
new25.31 KB

Added some MediaSource tests. Covered a lot, but I still need to add few things so consider this a WIP.

Also added annotation support for thumbnail alt and title fields. This was suggested at some point but never done and while I was looking at that part of the code already I did it.

I'll continue tomorrow morning and submit a final patch during the day.

slashrsm’s picture

StatusFileSize
new242.6 KB
new17.73 KB

I think that we're now testing all MediaSource functionality which means that this should be ready for the review.

I also added interfaces that @seanB forgot in #302 and he sent them to me over email. Field storage is now marked as locked as discussed in the recent comments.

wim leers’s picture

#302:

Not exactly sure what you mean? Should it handle more by default? You are supposed to override this right?

Oops, you're right, you are supposed to override this. Never mind me :)

Since not locking fields could cause issues we should probably do this.

Sounds good!

  1. +++ b/core/modules/media/src/Entity/Media.php
    @@ -134,9 +134,15 @@
    +   *   Specifies whether the thumbnail is being fetched from the queue.
    

    Specifies whether the thumbnail update is triggered from the queue.

  2. +++ b/core/modules/media/src/Entity/Media.php
    @@ -164,6 +170,14 @@
    +  public function updateQueuedThumbnail() {
    

    I don't quite like this name but can't think of anything better. Hopefully somebody else sees a way to make this clearer.

  3. +++ b/core/modules/media/src/Entity/Media.php
    @@ -290,12 +304,18 @@
    +    if ($media_source instanceof MediaSourceEntityConstraintsInterface) {
    +      $entity_constraints = $media_source->getEntityConstraints();
    +      $this->getTypedData()->getDataDefinition()->setConstraints($entity_constraints);
    +    }
    +
    +    if ($media_source instanceof MediaSourceFieldConstraintsInterface) {
    +      $source_field_name = $media_source->getConfiguration()['source_field'];
    +      $source_field_constraints = $media_source->getSourceFieldConstraints();
    +      $this->get($source_field_name)->getDataDefinition()->setConstraints($source_field_constraints);
    +    }
    

    So much better! :)


#307:

Also added annotation support for thumbnail alt and title fields. This was suggested at some point but never done and while I was looking at that part of the code already I did it.

Yay!

  1. +++ b/core/modules/media/src/Annotation/MediaSource.php
    @@ -80,6 +80,20 @@ class MediaSource extends Plugin {
    +   * @var string|null
    ...
    +   * @var string|null
    

    Why can these be NULL? Because they're optional?

    If so, we should document that.

  2. +++ b/core/modules/media/src/Entity/Media.php
    @@ -163,8 +163,22 @@ protected function updateThumbnail($from_queue = FALSE) {
    +    // Set the image alt.
    ...
    +    // Set the image title.
    

    s/image/thumbnail/

  3. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -83,4 +83,15 @@ public function setNewRevision($new_revision);
    +  public function setFieldMap($map);
    

    Probably a stupid question, but need to ask it anyway: Why did we not need this before yet need it now?

  4. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    \Drupal::state()->set('media_source_test_attributes', ['alternative_name' => ['title' => 'Alternative name', 'value' => $name]]);
    ...
    +    $this->assertEquals($name, $media->label(), 'Default name was set correctly.');
    

    How does this figure out automatically to use the ['value'] key of the metadata attribute?

    Ah, because \Drupal\media_test_source\Plugin\media\Source\Test::getMetadata()'s logic looks at ['value'].

    All good :)

  5. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    $storage = $this->container->get('entity_type.manager')
    +      ->getStorage('field_storage_config')
    

    Can be simplified to FieldStorageConfig::create()

  6. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    $this->container->get('entity_type.manager')
    +      ->getStorage('field_config')
    +      ->create([
    

    Can be simplified to FieldConfig::create()

  7. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Save the entity without defining the metadata mapping and check that the
    ...
    +    // Define mapping and make sure that the value was stored in the field.
    ...
    +    $this->assertEquals('Snowball', $media->getSource()->getMetadata($media, $attribute_name), 'Value of the metadata attribute is correct.');
    ...
    +    $this->assertEquals('Snowball', $media->get($field_name)->value, 'Metadata attribute was correctly mapped to the field.');
    

    YAY for testing the basics here, and doing that so thoroughly!

  8. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Change the metadata attribute value and re-save the entity. Field value
    +    // should stay the same.
    ...
    +    $this->assertEquals('Pinkeye', $media->getSource()->getMetadata($media, $attribute_name), 'Value of the metadata attribute is correct.');
    ...
    +    $this->assertEquals('Snowball', $media->get($field_name)->value, 'Metadata attribute was correctly mapped to the field.');
    

    Yay for testing this edge case!

  9. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Now change the value of the source field and make sure that the mapped
    +    // values update too.
    

    And yay for testing this edge case!

  10. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +   * Tests the default thumbnail functionality.
    

    s/default//

  11. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Save a media entity and make sure thumbnail was added.
    

    I'd have thought we'd start with the default thumbnail, i.e. with the thumbnail not originating from metadata. But starting with this is fine too of course.

    … reviewing rest of this test …

    Oh! We don't test non-queued thumbnails plus default thumbnails? Isn't that a valid use case too?

  12. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Now change the metadata attribute and make sure that the thumbnail stays
    +    // the same.
    

    This is essentially already tested in testMetadataMapping() but that's okay.

  13. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Remove the thumbnail and make sure that it is auto-updated on save.
    +    $media->thumbnail->target_id = NULL;
    +    $this->assertEquals('public://thumbnail2.jpg', $media->getSource()->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is correct.');
    +    $media->save();
    +    $this->assertEquals('public://thumbnail2.jpg', $media->thumbnail->entity->getFileUri(), 'New thumbnail was added to the media entity.');
    

    Interesting: deleting a Media entity field value and then saving that Media entity causes empty fields to be repopulated with current metadata?

    If so, then we should also test this in testMetadataMapping().

  14. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Change the metadata attribute again, change the source field value and
    

    /change the source field value/change the source field value too/

  15. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Enable queued thumbnails and make sure that the entity gets the default
    +    // thumbnail initially.
    ...
    +    $this->assertEquals('public://thumbnail1.jpg', $media->getSource()->getMetadata($media, 'thumbnail_uri'), 'Value of the metadata attribute is correct.');
    +    $media->save();
    +    $this->assertEquals('public://media-icons/generic/generic.png', $media->thumbnail->entity->getFileUri(), 'Default thumbnail was set initially.');
    ...
    +    // Process the queue item and make sure that the thumbnail was updated too.
    ...
    +    $media = Media::load($media->id());
    +    $this->assertEquals('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was updated by the queue.');
    

    Yay!

  16. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,231 @@
    +    // Set alt and title metadata attributes and make sure they are used for the
    +    // thumbnail.
    

    Great! But this made me realize that all prior assertions should also be asserting the value of $media->thumbnail->title and $media->thumbnail->alt.


#308:

  1. Thanks for adding those missing interfaces. Can you also point to them from the docs in MediaSourceInterface?
  2. +++ b/core/modules/media/src/Entity/Media.php
    @@ -255,6 +257,11 @@ protected function shouldUpdateThumbnail($is_new = FALSE) {
    +    // If the source plugin defines any constraints we enforce the validation.
    +    if ($this->getSource() instanceof MediaSourceEntityConstraintsInterface || $this->getSource() instanceof MediaSourceFieldConstraintsInterface) {
    +      $this->setValidationRequired(TRUE);
    +    }
    

    Looks like a sane addition.

  3. +++ b/core/modules/media/src/MediaSourceBase.php
    @@ -239,6 +239,7 @@ protected function createSourceFieldStorage() {
    +        'locked' => TRUE,
    

    Great! Although somebody with deep Entity Field API knowledge should confirm this is correct, I don't know this well enough.

  4. +++ b/core/modules/media/src/MediaSourceEntityConstraintsInterface.php
    @@ -0,0 +1,29 @@
    +interface MediaSourceEntityConstraintsInterface extends MediaSourceInterface {
    
    +++ b/core/modules/media/src/MediaSourceFieldConstraintsInterface.php
    @@ -0,0 +1,29 @@
    +interface MediaSourceFieldConstraintsInterface extends MediaSourceInterface {
    

    These look great!

  5. +++ b/core/modules/media/tests/modules/media_test_source/src/Plugin/Validation/Constraint/MediaTestConstraint.php
    @@ -0,0 +1,25 @@
    +  public $message = 'Inappropriate text.';
    

    :D

  6. +++ b/core/modules/media/tests/modules/media_test_source/src/Plugin/Validation/Constraint/MediaTestConstraintValidator.php
    @@ -0,0 +1,34 @@
    +    if (strpos($string_to_test, 'love Drupal') === FALSE) {
    +      $this->context->addViolation($constraint->message);
    +    }
    

    :D

  7. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -228,4 +230,154 @@ public function testThumbnail() {
    +    // Create media that uses source with constraints and make sure it can't
    +    // be saved without validating them.
    ...
    +    try {
    +      $media->save();
    +      $this->fail('Save was allowed without validation.');
    +    }
    +    catch (EntityStorageException $exception) {
    +      $this->assertTrue(TRUE, 'Validation was enforced before save.');
    +    }
    

    GREAT to see that this edge case is tested!

  8. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -228,4 +230,154 @@ public function testThumbnail() {
    +    // Create media that uses source with constraints and make sure it can't
    

    s/source/source field/

  9. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -228,4 +230,154 @@ public function testThumbnail() {
    +  /**
    +   * Tests logic related to the automated source field creation.
    +   */
    

    WOOOOT

  10. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -228,4 +230,154 @@ public function testThumbnail() {
    +    // Fields should be automatically saved only when using a form.
    

    Hm, I'm not sure what this means. Using a form? Just about any form?

    Don't you mean something like "when using the media type creation form"?

    (And to other reviewers: no, this does not cause problems when doing config deployments: when doing config deployments, you need to deploy _all_ depended config anyway, which would include that source field config. So this is correct.)


This is such a huge leap forward. I mostly have nitpicky remarks and enthusiastic remarks as you can see :)

The key thing that I'm still missing is test coverage for the "map metadata to complex fields" functionality.

slashrsm’s picture

The key thing that I'm still missing is test coverage for the "map metadata to complex fields" functionality.

In current patch we removed that. We only have simple mapping in it at the moment. In #289 I proposed a possible way forward for that part. I also think that it could be done in a follow-up in a BC way.

My idea was to take this hunk:

+++ b/core/modules/media/src/Entity/Media.php
@@ -0,0 +1,539 @@
+    // Try to set fields provided by the media source and mapped in
+    // media type config.
+    foreach ($this->bundle->entity->getFieldMap() as $metadata_attribute_name => $entity_field_name) {
+      // Only save value in entity field if empty. Do not overwrite existing
+      // data.
+      if ($this->hasField($entity_field_name) && ($this->get($entity_field_name)->isEmpty() || $this->sourceFieldChanged()) && ($value = $this->getSource()->getMetadata($this, $metadata_attribute_name))) {
+        $this->set($entity_field_name, $value);
+      }
+    }

and convert the logic into a plugin. Default plugin implementation would have the exact same logic. Plugin would probably do 2 things a) check if the fields should be updated and b) do the actual mapping. I don't see any BC problems with this approach, but I am not sure how to prove that with a test in a current patch.

I am working on the other things that were brought up in #309.

wim leers’s picture

I find #310 sufficiently convincing that I'd be in favor of postponing "complex field mapping" functionality to a follow-up.

It'd change this:

function preSave() {
  …
   // FIELD MAPPING LOGIC
   …
}

to:

function preSave() {
  …
  if ($this->getSource() instanceof ComplexFieldMappingInterface) {
    $this->getSource()->doComplexfieldMapping();
  }
  else {
    // FIELD MAPPING LOGIC
  }
   …
}

That's a trivial change, which does not require API changes.

slashrsm’s picture

StatusFileSize
new244.57 KB
new14.08 KB

This is addressing issues that were brought up in #309. Everything mentioned by @Wim Leers in that comment should be fixed with this patch.

#302
#1 - Done
#2 - Not sure either. updateThumbnailFromQueue()? Or maybe even saveFromQueue() which would then update the thumbnail and save the entity?

#307
#1 - Done
#2 - Done
#3 - When writing the tests it turned out that there is not way to set this programatically (outside of the media type form) since the map is protected. Tests++
#5 - Done
#6 - Done
#10 - Done
#11 - By default no. Plugins can use default thumbnail if they are unable to get the proper one for whatever reason, but this is implementation dependent. Not sure if there is much benefit in doing that just for testing purposes.
#13 - Done
#14 - Done
#16 - Done

#308
#1 - Done.
#8 - Done. Did s/source/source plugin/ since I felt it better describes the situation.
#10 - Done.

Also ran the code through the CodeSniffer one more time and fixed that too.

chr.fritsch’s picture

Status: Needs review » Needs work
Related issues: +#2855630: Field mapping not working properly

I discovered #2855630: Field mapping not working properly in the contrib media_entity, that should be addressed here, too.

slashrsm’s picture

Re #315: @phenaproxima mentioned the same problem in #67-46 already. We had few discussions about that after it but never come to a conclusion.

The problem is that we return boolean FALSE from MediaSourceInterface::getMetadata() if a metadata or its value is not available. This becomes problematic in cases where FALSE represents an actual valid value.

So far I imagine two possible solutions:

  1. Return NULL instead: Keeps things similar to how they stand right now but it should be more robust since we use something that actually means "no value".
  2. Throw exceptions: This is more powerful since it would allow is to differentiate different kinds of situations (metadata is not provided at all vs. given metadata value is not available for this specific asset). On the other hand it results in worse DX since the calling code needs to do proper exception handling. If it does not do that we risk WSODs and user frustration.
mtodor’s picture

@slashrsm - I think that using of exceptions here is wrong, exceptions should be used when something unexpected occurs. For me it's normal behaviour that handler/source/plugin (or however it's called now), cannot retrieve value for some metadata (fe. EXIF for image) - for me that's not exceptional situation but normal common case.

So I would just stick to NULL because it's simple and everyone knows what that means -> "no data". Possible problem that can appear with that is: what if NULL is for plugin "valid data" and not "no data" -> and it's something plugin want to set? In that case you can go with NoData class or some value wrapper class with state "no data", but all that is to much overhead in my opinion - it's better to keep it simple as long as it covers majority of use cases.

slashrsm’s picture

Status: Needs work » Needs review
StatusFileSize
new245.17 KB
new3.67 KB

OK. Let's do that then.

phenaproxima’s picture

Welp, it took like 5 hours, but I read this entire patch from top to bottom. All of it. Every line. Even the tests.

Overall, I think it looks effing fantastic! I wish all of core looked like this. The effort and care that everyone has put in shines through each and every line, every single class, every single test. I can only squeak a humble bravo to all who helped make this happen. Every day I am privileged to code with you all.

That said, I found some stuff, which is inevitable for a 7,000 line patch. Most of it is nitpicky doc comment stuff; some is just confusion. But none of it is major. Frankly, I'm tempted to RTBC right now (I know I have contributed to this patch, but it was long enough ago that most of what I wrote has been significantly changed and refactored), but we should probably answer at least some of the questions that came up before it's time to do that. Also, we'll probably need to bribe a committer to sit down and review this patchzilla. Or maybe a committee of committers, 'cause alliteration is fun!

Without further ado:

  1. +++ b/core/modules/media/js/media_type_form.js
    @@ -0,0 +1,46 @@
    + * Javascript for the media type form.
    

    Should say "Defines JavaScript behaviors for the media type form."

  2. +++ b/core/modules/media/media.install
    @@ -0,0 +1,55 @@
    +  file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
    

    If this returns FALSE (i.e., an error condition), should we handle that case? Or are we just relying on hook_requirements()?

  3. +++ b/core/modules/media/media.install
    @@ -0,0 +1,55 @@
    +        $description = [
    +          '#type' => 'inline_template',
    +          '#template' => '{{ error }} {{ description }}',
    +          '#context' => [
    +            'error' => $error,
    +            'description' => $description,
    +          ],
    +        ];
    

    What is the benefit of using an inline_template here?

  4. +++ b/core/modules/media/media.module
    @@ -0,0 +1,116 @@
    +      $output .= '<p>' . t('The Media module manages the creation, editing, deletion, settings and display of media. Items are typically images, documents, slideshows, YouTube videos, Tweets, Instagram photos, etc. You can reference media items from any other content on your site. For more information, see the <a href=":media">online documentation for the Media module</a>.', [':media' => 'https://www.drupal.org/docs/8/core/modules/media']) . '</p>';
    

    Correct me if I'm wrong, but I believe "tweet" is a normal noun and therefore should not be capitalized.

  5. +++ b/core/modules/media/media.module
    @@ -0,0 +1,116 @@
    +      $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' => Url::fromRoute('entity.media_type.collection')->toString()]) . '</dd>';
    

    I don't see why the "Media type" link should be capitalized. Can this just be "media type"? Also, I don't see any reason to link to the same page twice -- in fact, it might be confusing. I would keep the first link and remove the second.

  6. +++ b/core/modules/media/media.routing.yml
    @@ -0,0 +1,6 @@
    +entity.media.multiple_delete_confirm:
    +  path: '/admin/content/media/delete'
    +  defaults:
    +    _form: '\Drupal\media\Form\MediaDeleteMultipleConfirmForm'
    +  requirements:
    +    _permission: 'delete any media'
    

    Will people with the 'administer media' permission be able to access this route?

  7. +++ b/core/modules/media/media.tokens.inc
    @@ -0,0 +1,175 @@
    +    'description' => t("'The unique ID of the media item's latest revision."),
    

    There is an errant apostrophe at the beginning of this string :)

  8. +++ b/core/modules/media/src/Annotation/MediaSource.php
    @@ -0,0 +1,108 @@
    +   * The thumbnails are placed in the directory defined in
    +   * media.settings.icon_base_uri. When using custom icons, make sure the
    

    Can this first sentence make it clearer that media.settings.icon_base_uri is a config setting?

  9. +++ b/core/modules/media/src/Annotation/MediaSource.php
    @@ -0,0 +1,108 @@
    +   * on how to do this.
    

    Should say "of how to do to this".

  10. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +  public function setCreatedTime($timestamp) {
    +    $this->set('created', $timestamp);
    +    return $this;
    

    Total nitpick, but $this->set() already returns $this, so we could remove the additional return here and elsewhere, if we want to knock out a few extraneous lines.

  11. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +    $plugin_definition = $this->getSource()->getPluginDefinition();
    +    if (!empty($plugin_definition['thumbnail_alt_metadata_attribute'])) {
    +      $this->thumbnail->alt = $this->getSource()->getMetadata($this, $plugin_definition['thumbnail_alt_metadata_attribute']);
    +    }
    +    else {
    +      $this->thumbnail->alt = $this->t('Thumbnail');
    +    }
    +
    +    // Set the thumbnail title.
    +    if (!empty($plugin_definition['thumbnail_title_metadata_attribute'])) {
    +      $this->thumbnail->title = $this->getSource()->getMetadata($this, $plugin_definition['thumbnail_title_metadata_attribute']);
    +    }
    

    I'm not a huge fan of the way getSource() is repeatedly called. Can we just call it once before we do all this stuff? It will make things a little easier to read and be a nice little micro-optimization.

  12. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +    if ($this->bundle->entity->thumbnailDownloadsAreQueued() && $this->isNew()) {
    +      $default_thumbnail_filename = $this->getSource()->getPluginDefinition()['default_thumbnail_filename'];
    +      $thumbnail_uri = \Drupal::service('config.factory')->get('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename;
    +    }
    +    elseif ($this->bundle->entity->thumbnailDownloadsAreQueued() && !$from_queue) {
    +      $thumbnail_uri = $this->get('thumbnail')->entity->getFileUri();
    +    }
    

    Nitpick, but we could reorganize this so that we only need to call thumbnailDownloadsAreQueued() once. Might make this a little easier to read.

  13. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +    if (isset($this->original) && $this->get($source_field_name)->getValue() != $this->original->get($source_field_name)->getValue()) {
    +      return TRUE;
    +    }
    +
    +    return FALSE;
    

    Nit: If I'm grokking this correctly, we could simply return the result of the if statement directly. No need to explicitly return TRUE or FALSE.

  14. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +    // If the source plugin defines any constraints we enforce the validation.
    +    if ($this->getSource() instanceof MediaSourceEntityConstraintsInterface || $this->getSource() instanceof MediaSourceFieldConstraintsInterface) {
    +      $this->setValidationRequired(TRUE);
    +    }
    

    Again, this method is repeatedly and needlessly calling getSource(). Can we call it once at the top of the method to make it a little cleaner?

  15. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +      $queue = \Drupal::queue('media_entity_thumbnail');
    +      $queue->createItem(['id' => $this->id()]);
    

    Nit: $queue is never used, so we could just chain these calls together.

  16. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +    if (!$this->isNewRevision() && isset($this->original) && empty($record->revision_log)) {
    +      // If we are updating an existing media item 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;
    +    }
    +
    +    if ($this->isNewRevision()) {
    +      $record->revision_timestamp = self::getRequestTime();
    +    }
    

    Nit: We could reorganize this a little bit so that it's only necessary to call $this->isIsNewRevision() once.

  17. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,539 @@
    +  public function getRevisionLogMessage() {
    +    return $this->revision_log->value;
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function setRevisionLogMessage($revision_log_message) {
    +    $this->revision_log->value = $revision_log_message;
    +    return $this;
    +  }
    

    It's too bad there's no trait for this yet (is there?)

  18. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,229 @@
    +    $this->description = $description;
    +    return $this;
    

    Nit: We could call $this->set('description', $description) and return the result directly (which will be $this). Same applies to other property setters.

  19. +++ b/core/modules/media/src/Entity/MediaType.php
    @@ -0,0 +1,229 @@
    +  public function setFieldMap($map) {
    

    $map should have the array type hint, here and on the interface.

  20. +++ b/core/modules/media/src/Form/MediaDeleteMultipleConfirmForm.php
    @@ -0,0 +1,207 @@
    + * Provides a confirmation form to delete media items.
    

    Can this clarify by saying "Provides a confirmation form to delete multiple media items at once"?

  21. +++ b/core/modules/media/src/Form/MediaDeleteMultipleConfirmForm.php
    @@ -0,0 +1,207 @@
    +  /**
    +   * The array of media items to delete.
    +   *
    +   * @var array
    +   */
    +  protected $mediaItems = [];
    

    I'd like the type hint to be more specific about what this array contains. Is it an array of IDs? Fully loaded entities?

  22. +++ b/core/modules/media/src/Form/MediaDeleteMultipleConfirmForm.php
    @@ -0,0 +1,207 @@
    +   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $manager
    +   *   The entity manager.
    +   */
    +  public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $manager) {
    

    $manager description should say "The entity type manager".

  23. +++ b/core/modules/media/src/Form/MediaTypeDeleteConfirmForm.php
    @@ -0,0 +1,68 @@
    +  /**
    +   * Constructs a new MediaTypeDeleteConfirm object.
    +   *
    +   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
    +   *   The entity query object.
    +   */
    +  public function __construct(QueryFactory $query_factory) {
    

    The $query_factory description should say "The entity query factory".

  24. +++ b/core/modules/media/src/Form/MediaTypeDeleteConfirmForm.php
    @@ -0,0 +1,68 @@
    +      $form['description'] = [
    +        '#type' => 'inline_template',
    +        '#template' => '<p>{{ message }}</p>',
    +        '#context' => [
    +          'message' => $this->formatPlural($num_entities,
    +            '%type is used by @count media item on your site. You can not remove this media type until you have removed all of the %type media items.',
    +            '%type is used by @count media items on your site. You can not remove this media type until you have removed all of the %type media items.',
    +            ['%type' => $this->entity->label()]),
    +        ],
    +      ];
    

    Again, I don't quite understand what benefit we get from inline_template. What's wrong with just #markup?

  25. +++ b/core/modules/media/src/MediaAccessControlHandler.php
    @@ -0,0 +1,52 @@
    +        return AccessResult::allowedIf($account->hasPermission('view media') && $entity->isPublished())->cachePerPermissions()->addCacheableDependency($entity);
    

    Can these chained calls be on their own lines, for clarity?

  26. +++ b/core/modules/media/src/MediaAccessControlHandler.php
    @@ -0,0 +1,52 @@
    +        return AccessResult::allowedIf($account->hasPermission('update media') && $is_owner)->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity);
    

    Ditto here.

  27. +++ b/core/modules/media/src/MediaAccessControlHandler.php
    @@ -0,0 +1,52 @@
    +        return AccessResult::allowedIf($account->hasPermission('delete media') && $is_owner)->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity);
    

    And here.

  28. +++ b/core/modules/media/src/MediaAccessControlHandler.php
    @@ -0,0 +1,52 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
    +    return AccessResult::allowedIfHasPermission($account, 'create media');
    +  }
    

    Do we want per-bundle creation permissions? If so, should we introduce those now or open a follow-up issue?

  29. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,157 @@
    +    // 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;
    +
    +    // Add a "Unpublish" button.
    +    $element['unpublish'] = $element['submit'];
    +    // If the "Unpublish" button is clicked, we want to update the status to
    +    // "unpublished".
    +    $element['unpublish']['#published_status'] = FALSE;
    +    $element['unpublish']['#dropbutton'] = 'save';
    +    if ($media->isNew()) {
    +      $element['unpublish']['#value'] = $this->t('Save as unpublished');
    +    }
    +    else {
    +      $element['unpublish']['#value'] = !$media->isPublished() ? $this->t('Save and keep unpublished') : $this->t('Save and unpublish');
    +    }
    +    $element['unpublish']['#weight'] = 10;
    

    It'd be nice if there were a trait or base class for this :) Follow-up issue, perhaps? If one is already open, maybe we should add a @todo that refers to it?

  30. +++ b/core/modules/media/src/MediaForm.php
    @@ -0,0 +1,157 @@
    +    // If already published, the 'publish' button is primary.
    +    if ($media->isPublished()) {
    +      unset($element['unpublish']['#button_type']);
    +    }
    

    What does it mean for a button to become "primary", and how will unsetting #button_type accomplish that?

  31. +++ b/core/modules/media/src/MediaInterface.php
    @@ -0,0 +1,51 @@
    + * Provides an interface defining a entity for media items.
    

    Should say "interface defining an entity..."

  32. +++ b/core/modules/media/src/MediaInterface.php
    @@ -0,0 +1,51 @@
    +  /**
    +   * Returns the media item creation timestamp.
    +   *
    +   * @return int
    +   *   Creation timestamp of the media item.
    +   */
    +  public function getCreatedTime();
    +
    +  /**
    +   * Sets the media item creation timestamp.
    +   *
    +   * @param int $timestamp
    +   *   The media creation timestamp.
    +   *
    +   * @return \Drupal\media\MediaInterface
    +   *   The called media item.
    +   */
    +  public function setCreatedTime($timestamp);
    

    Is there an issue for EntityCreatedInterface and an accompanying trait? If so, can we reference it in a @todo?

  33. +++ b/core/modules/media/src/MediaSourceBase.php
    @@ -0,0 +1,321 @@
    +    $this->setConfiguration($configuration);
    

    We should probably explain why we do this, rather than let the parent constructor do it (I think it's to merge in the default config, but it's still not too obvious).

  34. +++ b/core/modules/media/src/MediaSourceBase.php
    @@ -0,0 +1,321 @@
    +   * This returns all fields related to media entities, filtered by the
    +   * allowed types in the media source annotation.
    

    Should say "filtered by the allowed field types..."

  35. +++ b/core/modules/media/src/MediaSourceBase.php
    @@ -0,0 +1,321 @@
    +   * @return array
    +   *   A list of source field options for the media type form.
    +   */
    +  protected function getSourceFieldOptions() {
    

    Can the return type be string[] for greater specificity?

  36. +++ b/core/modules/media/src/MediaSourceBase.php
    @@ -0,0 +1,321 @@
    +    foreach ($form_state->getValues() as $config_key => $config_value) {
    +      if (isset($this->configuration[$config_key])) {
    +        $this->configuration[$config_key] = $config_value;
    +      }
    +    }
    

    So, uh...won't this only overwrite non-NULL values in $this->configuration? That seems like it might have strange side effects. If it's the intended behavior, I think we should explain in a comment why we're only overwriting non-NULLs.

  37. +++ b/core/modules/media/src/MediaSourceEntityConstraintsInterface.php
    @@ -0,0 +1,29 @@
    + * Defines a interface for a media source with entity constraints.
    

    Should say "defines an interface..."

  38. +++ b/core/modules/media/src/MediaSourceEntityConstraintsInterface.php
    @@ -0,0 +1,29 @@
    + * for media items. To add contraints at source field level a media source
    

    Should say "To add constraints at the source field level..."

  39. +++ b/core/modules/media/src/MediaSourceFieldConstraintsInterface.php
    @@ -0,0 +1,29 @@
    + * Defines a interface for a media source with source field constraints.
    

    Should say "defines an interface..."

  40. +++ b/core/modules/media/src/MediaSourceFieldConstraintsInterface.php
    @@ -0,0 +1,29 @@
    + * constraints for media items. To add contraints at entity level a
    

    Should say "To add constraints at the entity level..."

  41. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + * local files and Youtube videos can both be catalogued in a similar way
    

    s/Youtube/YouTube

  42. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + * as Media entity items, but they need very different handling to actually
    

    Can this say "media items" for consistency, instead of "Media entity items"?

  43. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + * Each Media type needs exactly one source. A single source can be used on
    

    "Media" does not need to be capitalized.

  44. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + * - Twitter: handles Tweets,
    

    I'm not sure "tweets" is supposed to be capitalized.

  45. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + *   responsible to actually store the media. They only define how it is
    

    Should say "responsible for actually storing..."

  46. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + *   media will generally fetch the image from the 3rd party API and make
    

    Should say "from a third-party API..."

  47. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + *   Both will also fall back to a pre-defined default thumbnail if
    + *   everything else fails.
    

    Should say "Media sources should fall back..."

  48. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + * - Providing a default value for the media name field. This will save users
    

    Should say "Providing a default name for a media item".

  49. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + *   attribute through the 3rd party API. The name can always be overridden
    

    Should say "a third-party API".

  50. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    + *   media sources generally get information available through the
    + *   3rd party API and make it available to Drupal, while local media sources
    

    Should say "...available through a third-party API..."

  51. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    +   * Most media sources have associated metadata, describing attributes
    +   * such as:
    +   * - dimensions
    +   * - duration
    +   * - encoding
    +   * - date
    +   * - location
    +   * - permalink
    +   * - licensing information
    +   * - …
    

    Unless mine eyes deceive me, that looks like a so-called smart ellipsis at the end. Should probably be replaced with three periods for, I dunno, ASCII friendliness or whatever.

  52. +++ b/core/modules/media/src/MediaSourceInterface.php
    @@ -0,0 +1,139 @@
    +   *   The source field definition, or NULL if it doesn't exists yet
    

    Should say "...or NULL if it doesn't exist or has not been configured yet."

  53. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +    $response->addCommand(new ReplaceCommand('#source-dependent', $form['source_dependent(']));
    

    Er...why is there a parenthesis at the end of $form['source_dependent(']? That looks like a typo, but it's used elsewhere in the form, so I figure it's intentional. But I've never seen that in Drupal before. What gives?

  54. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +    $source = $this->entity->get('source') ? $this->entity->getSource() : NULL;
    

    No need to do the ternary logic here; $this->entity->getSource() will be NULL if the source is not configured yet.

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

    Can we use $field->isBaseField() rather than the instanceof check?

  56. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +          '#default_value' => isset($field_map[$metadata_attribute_name]) ? $field_map[$metadata_attribute_name] : '_none',
    

    I feel like '_none' should be a constant on MediaSourceInterface.

  57. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +    $form['workflow']['options']['new_revision']['#description'] = $this->t('Automatically create new revisions. Users with the Administer media permission will be able to override this option.');
    

    "Administer media" should be italicized or otherwise differentiated.

  58. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +    $form['workflow']['options']['queue_thumbnail_downloads']['#description'] = $this->t('Download thumbnails via a queue.');
    

    This description is pretty useless. Can it be explained a little more? What is the advantage of queueing thumbnail downloads? Is it for performance? If so, we should mention that.

  59. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +   * @return \Drupal\Core\Form\SubFormStateInterface
    +   *   Sub-form state for the media source configuration form.
    +   */
    +  protected function getSourceSubFormState(array $form, FormStateInterface $form_state) {
    

    I could be wrong, but I believe it is SubformStateInterface, not SubFormStateInterface.

  60. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +    $this->entity->setQueueThumbnailDownloadsStatus((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']));
    

    Can these calls be chained?

  61. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +    if ($form['source_dependent(']['source_configuration']) {
    

    Where is $form['source_dependent(']['source_configuration'] defined?

  62. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +    $source_field = $source->getSourceFieldDefinition($media_type);
    +    if (!$source_field) {
    +      $source_field = $source->createSourceField($media_type);
    

    We could flatten the method a little bit if we replaced this with an elvis operator.

  63. +++ b/core/modules/media/src/MediaTypeForm.php
    @@ -0,0 +1,373 @@
    +      $this->logger('media')->notice('Added type %name.', $t_args);
    

    Should say "Added media type %name."

  64. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,97 @@
    + * Media types are bundles for the media entity. They are used to group media
    

    Should say "...bundles for media items".

  65. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,97 @@
    + *   some kind of storage, but their meaning is different.
    

    Should say "...but their meaning and uses in a Drupal site are different".

  66. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,97 @@
    +   * Returns the media source.
    

    Can this say "Returns the media source plugin"?

  67. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,97 @@
    +   * Field mapping allows site builders to map media item related metadata to
    

    Should say "media item-related metadata..."

  68. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,97 @@
    +   *   Field mapping array with fields provided by the type plugin as keys and
    +   *   Drupal Entity fields as values.
    

    Type plugins are no longer a thing, and "Entity" does not need to be capitalized.

  69. +++ b/core/modules/media/src/MediaTypeInterface.php
    @@ -0,0 +1,97 @@
    +  public function setFieldMap($map);
    

    $map should be type hinted.

  70. +++ b/core/modules/media/src/MediaTypeListBuilder.php
    @@ -0,0 +1,50 @@
    +class MediaTypeListBuilder extends ConfigEntityListBuilder implements EntityHandlerInterface {
    

    ConfigEntityListBuilder already implements EntityHandlerInterface.

  71. +++ b/core/modules/media/src/MediaViewsData.php
    @@ -0,0 +1,24 @@
    + * Provides the views data for the media entity type.
    

    Should be "Provides the Views data..."

  72. +++ b/core/modules/media/src/Plugin/Action/PublishMedia.php
    @@ -0,0 +1,38 @@
    +  public function execute(Media $entity = NULL) {
    +    $entity->setPublished()->save();
    +  }
    

    $entity can be NULL but we're blithely calling methods on it? We should add an if ($entity) check here. Also, why is $entity not type hinted to MediaInterface?

  73. +++ 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));
    +
    +    return $return_as_object ? $result : $result->isAllowed();
    

    Why not simply pass $return_as_object to the andIf() call and return the result directly?

  74. +++ b/core/modules/media/src/Plugin/Action/SaveMedia.php
    @@ -0,0 +1,37 @@
    + * Provides an action that can save any entity.
    

    Should use the active voice, like the other actions: "Saves a media item".

  75. +++ b/core/modules/media/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();
    +  }
    

    We need to type hint $entity, and check if it's set in the method.

  76. +++ b/core/modules/media/src/Plugin/Action/UnpublishMedia.php
    @@ -0,0 +1,38 @@
    +  public function execute(Media $entity = NULL) {
    +    $entity->setUnpublished()->save();
    +  }
    

    Again, $entity needs to be type hinted to MediaInterface and checked for null-ness in the method.

  77. +++ 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));
    +
    +    return $return_as_object ? $result : $result->isAllowed();
    

    Once again, this could be simplified by not passing TRUE to andIf() and directly returning the result of the call chain.

  78. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,190 @@
    +    $url = NULL;
    +    $image_link_setting = $this->getSetting('image_link');
    +    // Check if the formatter involves a link.
    +    if ($image_link_setting == 'content') {
    +      $entity = $items->getEntity();
    +      if (!$entity->isNew()) {
    +        $url = $entity->toUrl();
    +      }
    +    }
    +    elseif ($image_link_setting === 'media') {
    +      $link_media = TRUE;
    +    }
    

    I kinda find myself wishing that this were a protected helper method called for each media item inside the foreach loop; I feel like it'd be a little cleaner that way. Not married to the idea, just a preference.

  79. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,190 @@
    +        '#item' => $media_item->get('thumbnail'),
    

    Forgive me if I'm wrong, but doesn't calling a content entity's get() normally return a FieldItemListInterface...?

  80. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -0,0 +1,190 @@
    +    $target_type = $field_definition->getFieldStorageDefinition()->getSetting('target_type');
    +    return $target_type == 'media';
    

    Nit: Could all be one line.

  81. +++ b/core/modules/media/src/Plugin/QueueWorker/ThumbnailDownloader.php
    @@ -0,0 +1,49 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    +    parent::__construct($configuration, $plugin_id, $plugin_definition);
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    +    return new static(
    +      $configuration,
    +      $plugin_id,
    +      $plugin_definition
    +    );
    +  }
    

    All this, and ContainerFactoryPluginInterface, can be removed. It doesn't look like it's doing anything for us...

  82. +++ b/core/modules/media/src/Plugin/QueueWorker/ThumbnailDownloader.php
    @@ -0,0 +1,49 @@
    +    if ($media = Media::load($data['id'])) {
    

    ...unless we inject the media storage handler here and use it to load the thing.

  83. +++ b/core/modules/media/src/Plugin/views/wizard/Media.php
    @@ -0,0 +1,87 @@
    + * Provides views creation wizard for Media.
    

    "views" should be capitalized.

  84. +++ b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,99 @@
    + * Provides views creation wizard for Media revisions.
    

    Once again, "Views" should be capitalized.

  85. +++ b/core/modules/media/src/Plugin/views/wizard/MediaRevision.php
    @@ -0,0 +1,99 @@
    +  /**
    +   * Set the created column.
    +   *
    +   * @var string
    +   */
    +  protected $createdColumn = 'created';
    

    Why is this different from the same constant in the Media wizard?

  86. +++ b/core/modules/media/templates/media.html.twig
    @@ -0,0 +1,48 @@
    + * - media: The media item with limited access to object properties and
    

    Nit: Should have a comma after "item".

  87. +++ b/core/modules/media/tests/modules/media_test_source/src/Plugin/Validation/Constraint/MediaTestConstraint.php
    @@ -0,0 +1,25 @@
    + * Checks if a value is a valid Tweet embed code/URL.
    

    Doesn't seem accurate :) Copypasta?

  88. +++ b/core/modules/media/tests/modules/media_test_source/src/Plugin/media/Source/Test.php
    @@ -0,0 +1,85 @@
    + * Provides generic media type.
    

    s/generic/test

  89. +++ b/core/modules/media/tests/modules/media_test_source/src/Plugin/media/Source/Test.php
    @@ -0,0 +1,85 @@
    +    $attributes = \Drupal::state()->get('media_source_test_attributes', [
    +      'attribute_1' => ['label' => $this->t('Attribute 1'), 'value' => 'Value 1'],
    +      'attribute_2' => ['label' => $this->t('Attribute 2'), 'value' => 'Value 1'],
    +    ]);
    

    Why is this stuff coming from state?

  90. +++ b/core/modules/media/tests/modules/media_test_source/src/Plugin/media/Source/Test.php
    @@ -0,0 +1,85 @@
    +      '#default_value' => empty($this->configuration['test_config_value']) ? NULL : $this->configuration['test_config_value'],
    

    Seeing as how defaultConfiguration() is implemented, $this->configuration['test_config_value'] should never be empty.

  91. +++ b/core/modules/media/tests/src/Functional/MediaAccessTest.php
    @@ -0,0 +1,119 @@
    +  /**
    +   * The test media type.
    +   *
    +   * @var \Drupal\media\MediaTypeInterface
    +   */
    +  protected $testMediaType;
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function setUp() {
    +    parent::setUp();
    +    $this->testMediaType = $this->createMediaType();
    +  }
    

    Nit: We could remove all of this and just create the media type in testMediaAccess().

  92. +++ b/core/modules/media/tests/src/Functional/MediaCacheTagsTest.php
    @@ -0,0 +1,78 @@
    +   * Each media item must have an author and a thumbnail.
    

    This line should probably be a // comment in the method.

  93. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestTrait.php
    @@ -0,0 +1,65 @@
    +   * @param string $source
    +   *   (optional) The media source plugin that is responsible for additional
    +   *   logic related to this media type.
    

    Should say what the default value is.

  94. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestTrait.php
    @@ -0,0 +1,65 @@
    +    if (!isset($values['bundle'])) {
    

    Should be if (empty()) to prevent against falsy values.

  95. +++ b/core/modules/media/tests/src/Functional/MediaFunctionalTestTrait.php
    @@ -0,0 +1,65 @@
    +    $this->assertEqual(SAVED_NEW, $status, 'Media type was created successfully.');
    

    This should probably be assertSame().

  96. +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,167 @@
    +  /**
    +   * The test media type.
    +   *
    +   * @var \Drupal\media\MediaTypeInterface
    +   */
    +  protected $testMediaType;
    

    This doesn't seem to be used in the test, so we can probably ice it.

  97. +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,167 @@
    +    $media = $this->container->get('entity_type.manager')
    +      ->getStorage('media')
    +      ->loadUnchanged($media_id);
    +    $this->assertEquals($media->getRevisionLogMessage(), $revision_log_message);
    +    $assert_session->titleEquals($media->label() . ' | Drupal');
    

    Why aren't we asserting that $media->label() and $media_name are equal?

  98. +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,167 @@
    +    $page->fillField('name[0][value]', $media_name2);
    +    $page->pressButton('Save and keep published');
    +    $assert_session->titleEquals($media_name2 . ' | Drupal');
    

    Same here?

  99. +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,167 @@
    +    // An empty tab container would look like this.
    +    $raw_html = '<div data-drupal-selector="edit-advanced" data-vertical-tabs-panes><input class="vertical-tabs__active-tab" data-drupal-selector="edit-advanced-active-tab" type="hidden" name="advanced__active_tab" value="" />' . "\n" . '</div>';
    +    $assert_session->responseNotContains($raw_html);
    

    It seems a little dicey to assert against raw HTML, since that can vary by theme. Could we do an assertElementNotExists() instead?

  100. +++ b/core/modules/media/tests/src/Functional/MediaUiFunctionalTest.php
    @@ -0,0 +1,167 @@
    +    $media = $this->container->get('entity_type.manager')
    +      ->getStorage('media')
    +      ->loadUnchanged($media_id);
    +    $this->assertEquals($media->getRevisionLogMessage(), $revision_log_message);
    

    Could we also assert that a new revision was, in fact, created?

  101. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaJavascriptTestBase.php
    @@ -0,0 +1,110 @@
    +/**
    + * Base class for Media functional JavaScript tests.
    + *
    + * @package Drupal\Tests\media\FunctionalJavascript
    + */
    

    I've never seen anything in Drupal use the @package annotation, should we get rid of it here?

  102. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaJavascriptTestBase.php
    @@ -0,0 +1,110 @@
    +abstract class MediaJavascriptTestBase extends JavascriptTestBase {
    

    A lot of the stuff in here ($modules, $adminUserPermissions, $adminUser, $nonAdminUser, etc.) seems awfully similar to MediaFunctionalTestTrait, which this is already using. Is it copypasta? Can we remove it?

  103. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaTypeCreationTest.php
    @@ -0,0 +1,98 @@
    +    $this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}/fields");
    +
    +    // Check 2nd column of first data row, to be machine name for field name.
    +    $this->assertSession()
    +      ->elementContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[2]', 'field_media_test');
    +    // Check 3rd column of first data row, to be correct field type.
    +    $this->assertSession()
    +      ->elementTextContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[3]', 'Text (plain)');
    

    This is neat but it seems like it might be a little more stable if we assert this stuff at the API level.

  104. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaTypeCreationTest.php
    @@ -0,0 +1,98 @@
    +    // Create a new media type, which should create a new field we can reuse.
    +    $this->drupalGet('/admin/structure/media/add');
    +    $page = $this->getSession()->getPage();
    +    $page->fillField('label', 'Pastafazoul');
    +    $this->getSession()
    +      ->wait(5000, "jQuery('.machine-name-value').text() === 'pastafazoul'");
    +    $page->selectFieldOption('Media source', 'test');
    +    $this->assertSession()->assertWaitOnAjaxRequest();
    +    $page->pressButton('Save');
    

    I wonder if this should be moved to a helper method of MediaJavascriptTestBase.

  105. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaTypeCreationTest.php
    @@ -0,0 +1,98 @@
    +    // Check that there are not fields created.
    

    Should say "Check that no new fields were created."

  106. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php
    @@ -0,0 +1,197 @@
    +    $this->assertFalse($storage->isNew(), 'Source field definition was saved.');
    +    $this->assertTrue($storage->isLocked(), 'Source field definition was locked.');
    

    Should say "Source field storage definition".

  107. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php
    @@ -0,0 +1,197 @@
    +    $assert_session->pageTextContains('Create new revision');
    +    $assert_session->pageTextContains('Automatically create new revisions. Users with the Administer media permission will be able to override this option.');
    +    $assert_session->pageTextContains('Download thumbnails via a queue.');
    +    $assert_session->pageTextContains('Media will be automatically published when created.');
    +    $assert_session->pageTextContains('Media sources 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.');
    

    I dunno how I feel about this. It makes it a little harder for us to change the text easily -- as I have suggested about a million times over the course of this patch review. Seems overzealous to me. What are these assertions really proving for us?

  108. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaUiJavascriptTest.php
    @@ -0,0 +1,197 @@
    +    // Test type delete prevention when there is existing media.
    

    This should be reworded, it's kinda unclear.

  109. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaViewsWizardTest.php
    @@ -0,0 +1,88 @@
    +    $page->pressButton('Save and edit');
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $this->assertEquals($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
    

    As far as I know, Views wizards don't do AJAX things once you click 'Save and edit'...do they?

  110. +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaViewsWizardTest.php
    @@ -0,0 +1,88 @@
    +    $page->pressButton('Save and edit');
    +    $assert_session->assertWaitOnAjaxRequest();
    +    $this->assertEquals($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
    

    Ditto here.

  111. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -0,0 +1,58 @@
    +    $this->assertEquals([], $test_media_type->get('field_map'), 'Could not assure the correct field map.');
    

    I'm not sure what this proves; the field map is empty by default. I think we should check for some sort of sane default value here.

  112. +++ b/core/modules/media/tests/src/Kernel/BasicCreationTest.php
    @@ -0,0 +1,58 @@
    +    $this->assertEquals('Nation of sheep, ruled by wolves, owned by pigs.', $media->get($source_field_name)->value, 'Source returns correct source field.');
    

    The assert message seems wrong; isn't it supposed to be a failure message?

  113. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $this->assertEquals('default_name', $media->getSource()->getPluginDefinition()['default_name_metadata_attribute'], 'Default metadata attribute is used for the default name.');
    +    $this->assertEquals('media:' . $media->bundle() . ':' . $media->uuid(), $media->getSource()->getMetadata($media, 'default_name'), 'Value of the default name metadata attribute looks correct.');
    

    I'd rather call getSource() once at the top of the test and reuse that variable throughout (or at least, as often as possible). Also, given everything else I've seen in this test suite, are these assert messages supposed to be affirmative or negative? It's totally mixed.

  114. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    \Drupal::state()->set('media_source_test_attributes', ['alternative_name' => ['title' => 'Alternative name', 'value' => $name]]);
    +    \Drupal::state()->set('media_source_test_definition', ['default_name_metadata_attribute' => 'alternative_name']);
    

    Ah, so that's why the source plugin uses state. Couldn'twe just as easily call setConfiguration() on it, rather than doing this?

  115. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $this->assertEquals('alternative_name', $media->getSource()->getPluginDefinition()['default_name_metadata_attribute'], 'Correct metadata attribute is used for the default name.');
    +    $this->assertEquals($name, $media->getSource()->getMetadata($media, 'alternative_name'), 'Value of the default name metadata attribute looks correct.');
    

    More than one sequential call to getSource(). This is my nemesis :)

  116. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $this->assertEquals('public://thumbnail1.jpg', $media->getSource()->getMetadata($media, 'thumbnail_uri'), 'Value of the metadata attribute is correct.');
    +    $this->assertEquals('public://thumbnail2.jpg', $media->getSource()->getMetadata($media, 'alternative_thumbnail_uri'), 'Value of the thumbnail metadata attribute is correct.');
    

    Can we remove the sequential calls to getSource()?

  117. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    try {
    +      $media->save();
    +      $this->fail('Save was allowed without validation.');
    +    }
    +    catch (EntityStorageException $exception) {
    +      $this->assertTrue(TRUE, 'Validation was enforced before save.');
    +    }
    

    The EntityStorageException could have been thrown for a whole mess of reasons. We should either catch a more specific exception, or assert the exception message.

  118. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $this->assertEquals(1, $violations->count(), 'Expected number of validations found.');
    

    I think ConstraintViolationListInterface is countable, so we could use assertCount() here.

  119. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $this->assertEquals(0, $violations->count(), 'Expected number of validations found.');
    

    Could use assertCount() here.

  120. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    try {
    +      $media->save();
    +      $this->fail('Save was allowed without validation.');
    +    }
    +    catch (EntityStorageException $exception) {
    +      $this->assertTrue(TRUE, 'Validation was enforced before save.');
    +    }
    

    Again, let's see if we can't trap the exception a little more specifically.

  121. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $this->assertEquals(1, $violations->count(), 'Expected number of validations found.');
    

    Can be assertCount().

  122. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $this->assertEquals(0, $violations->count(), 'Expected number of validations found.');
    

    Can be assertCount().

  123. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $type = MediaType::create([
    +      'id' => 'test_type',
    +      'label' => 'Test type',
    +      'source' => 'test',
    +    ]);
    

    Can we reuse the media type created by the base class?

  124. +++ b/core/modules/media/tests/src/Kernel/MediaSourceTest.php
    @@ -0,0 +1,411 @@
    +    $type = MediaType::create([
    +      'id' => 'test_constraints_type',
    +      'label' => 'Test type with constraints',
    +      'source' => 'test_constraints',
    +    ]);
    

    Can't we re-use the media type created the base class?

  125. +++ b/core/themes/classy/templates/content/media.html.twig
    @@ -0,0 +1,27 @@
    + * Theme override to display a media.
    

    Should say "media item".

  126. +++ b/core/themes/stable/templates/content/media.html.twig
    @@ -0,0 +1,19 @@
    + * Theme override to display a media.
    

    Should say "media item".

gábor hojtsy’s picture

Looking at the patch writing a change notice draft at https://docs.google.com/document/d/1IlfkkVLbFhlrj0NRfIa8UIXgoqQBudiRxRDP...

I think hiding the module is not useful given that this goes into a branch that will not get released for months. I would suggest not to hide the module in its info.yml.

marcoscano’s picture

StatusFileSize
new76.41 KB
new244.79 KB

Most quick-fixes from #319 are addressed here.

There is still a failure with MediaCacheTagsTest that I am unable to troubleshoot right now...

Status of the feedback from #319:

1: Done
2: Needs work
3: Needs work
4: Done (yes: https://www.merriam-webster.com/dictionary/tweet)
5: Done
6: Done. Changed to administer media, to keep consistent with how nodes deal with this
7: Done
8: Done
9: Done
10: Done
11: Done
12: Done
13: Done
14: Done
15: Done
16: Done
17: Needs work
18: Done
19: Done
20: Done
21: Needs work
22: Done
23: Done
24: Needs work
25: Done
26: Done
27: Done
28: Needs work
29: Needs work
30: Needs work
31: Done
32: Needs work
33: Needs work
34: Done
35: Done
36: Needs work
37: Done
38: Done
39: Done
40: Done
41: Done
42: Done
43: Done
44: Done
45: Done
46: Done
47: Done
48: Done
49: Done
50: Done
51: Done
52: Done
53: Needs work
54: I don't believe we should do that. We can't use $this->entity->getSource() if the MediaType source property is empty.
55: Needs work
56: Needs work
57: Done
58: Done
59: Needs work
60: Done
61: Needs work
62: Needs work. (Are you sure? I believe there is logic inside the if that should only happen if !$source_field)
63: Done
64: Done
65: Done
66: Done
67: Done
68: Needs work
69: Done
70: Done
71: Done
72: Done
73: Needs work.
(IMHO: I believe this is done this way to be consistent with with what node does, and it may allow for easier debugging perhaps?)
74: Done
75: Done
76: Done
77: Needs work (same as 73)
78: Needs work
79: Needs work
80: Done
81: Needs work
82: Needs work
83: Done
84: Done
85: Needs work
86: Done
87: Done
88: Done
89: Needs work
90: Done
91: Done
92: Done
93: Done
94: Done
95: Done
96: Done
97 and 98: Needs work
(IMHO, being a functional test, doesn't ::titleEquals() have a better coverage than simply checking the entity's value? We are testing the whole "create an entity using the form" process, not just the entity creation itself. Although I agree it is a very small difference :)
99: Done
100: Done
101: Done
102: It's similar to the MediaFunctionalTestBase, but we are not extending it. Do you mean move all this elsewhere?
103: Needs work
104: Needs work
105: Done
106: Done
107: Needs work.
(IMHO: Well they prove that all elements are on the page, with their respective descriptions. If in the future we change any of these, the same change should also update the test, shouldn't it?)
108: Done
109: Done
110: Done
111: Needs work
112: Done
113: Done
114: Needs work
115: Done
116: Done
117: Needs work
(I kind of don't get this line. Wouldn't the message be printted only if the assertion fails? (which is never in this case) )
118: Done
119: Done
120: Needs work
121: Done
122: Done
123: Needs work
(I believe the idea here is to test the source field creation that happens during the creation of the media type. The one in the base class is already created and saved at this point)
124: Needs work
(same as 123)
125: Done
126: Done

xjm’s picture

I think hiding the module is not useful given that this goes into a branch that will not get released for months. I would suggest not to hide the module in its info.yml.

Hiding the module is the release management decision to allow adding module to core now. Regardless of when the release is released, Drupal 8 branches need to be in a shippable state at all times. This means hiding the module until the agreed followup steps are completed.

gábor hojtsy’s picture

@xjm: ok, if that is the only way, it is still better than not committing this until all the required followups are committed, so that will be the way then. Let's work together to identify which of the 9 existing followups would be a requirement so we can prioritize work accordingly. Also of course if any followups needed that were not yet identified.

berdir’s picture

I'd argue it *was* the decision for getting it into 8.3 and 8.4 might be a bit different, at least if we get it in *soon*. My suggestion would be to get it in now without hiding it and then decide before 8.4.0 beta or RC if we're going to release 8.4.0 with it being hidden or not? It will make testing it for non-experienced users and on simplytest.me a lot easier for the next few months.

xjm’s picture

The module presently offers no user-facing features, and, as @Gábor Hojtsy has said, "A UI which tells you that you can't do things." Presumably, those non-experienced users who are going to be testing the module will be doing so with a contrib module that prompts them to enable it anyway as a dependency, or with patches for additional user-facing functionality like the library. Also, in the followup issues, exposing two image entity types is problematic and we'll need a solution for that. So, until we address those things, the module should remain hidden, so that contrib can depend on it but core does not offer a user experience with it yet.

Edit: Looking back at #2825215-42: Media initiative: Roadmap (sorry it's been a month), the plan was to add the module as beta experimental, in the experimental package, then mark hidden if it was not stable. The module needs to either be marked hidden, or in the experimental package, until the user-facing experience makes sense with the relevant plugins for other types, a way to handle the duplication with stable core types, CKEditor support, etc. We can discuss more on that issue, but just reiterating that the module needs to be hidden if it's not in the experimental package, and for me that is commit-blocking. :) Moving it to the experimental package and removing the hidden instead is also an option (and what slashrsm and I agreed on), but let's not spend time discussing that here.

seanb’s picture

I addressed all the comments marcoscano marked as 'Needs work'. The 2 following comments are still open:

  • #319-28: Create a followup if we want this.
  • #319-36: Maybe slashrsm can comment on this one?
  • Fix the tests (I think they will fail, got some weird local issues so just want to see wat the testbot says). It has something to do with the source field creation.

Added interdiffs for 318 and 325 for easier review.

Here is more detailed feedback on all other comments:

  • #319-2: Done. I think it's safe to rely on hook_requirements.
  • #319-3: Done. I'm not 100% sure why it was added here specifically, But the use of inline_template was suggested in #104-9. I think the main reason was it is easier to read.
  • #319-17: Done. There is actually :). I changed Media entity to extend RevisionableContentEntityBase. Here we have RevisionLogEntityTrait. Fixed!
  • #319-21: Done. Changed to string[][] and updated comment. The actual format is $this->mediaItems[id][langcode] = langcode
  • #319-24: Done. This was added as requested in #104-9.
  • #319-28: Needs work. I vote for a follow up. Let's get this in please :)
  • #319-29: Done. There was a suggestion to move this to the EntityForm base class in #2835535: Standardize basic content entity form logic in ContentEntityForm . Link to that issue? My suggestion is to leave it for now since there is some discussion on this.
  • #319-30: Done. Button type primary is for styling purposes (see https://www.drupal.org/node/1848288). I see why you would want to unset the button type for the non primary button. In reality though, the property is not even set for the media form. Maybe this was copied from the node module? Changed it to explicitely set the button_type.
  • #319-32: Done. Yep, added todo for when https://www.drupal.org/node/2833378 lands.
  • #319-33: Done. Added comment.
  • #319-36: Needs work. Not sure about this one.
  • #319-53: Done. This looks like a find/replace error, so I removed it.
  • #319-54: Done. Like marcoscano already said, we can't use $this->entity->getSource() if the MediaType source property is empty.
  • #319-55: Done. I don't think so. FieldDefinitionInterface doesn't define isBaseField.
  • #319-56: Done.
  • #319-59: Done.
  • #319-61: Done. In \Drupal\media\MediaTypeForm->buildForm().
  • #319-62: Done.
  • #319-68: Done.
  • #319-73: Done.
  • #319-77: Done.
  • #319-78: Done.
  • #319-79: Done. Yes, in this case it returns FileFieldItemList. Which works, since the get() method on FieldItemList uses the first() method. And in this case, first() returns a ImageItem object. Maybe it's more clear to use $media->get('thumbnail')->first()
  • #319-81: Done. See 82.
  • #319-82: Done. Let's do that.
  • #319-85: Done. The original value from contrib was changed (I think this is copied from the node module). WimLeers mentioned this already in #191. Now changed it to media_field_revision-created.
  • #319-89: Done. During the test we want to vary the metadata attributes. Fetching the attributes from the state, add the flexibility to do that. Added some comments to clarify this.
  • #319-97/98: Done. We now check label and page title.
  • #319-102: Done. Moved all duplicate code to the trait. Since MediaCacheTagsTest used this trait as well for media type creation, I added a separate trait for this.
  • #319-107: Done. I fully agree with marcoscano.
  • #319-111: Done. Added some defaults.
  • #319-114: Done. The plugin configuration is not supposed to be used for this. Adding it there in a testing situation could lead to confusion. I think it's good we use something different like state storage just for testing.
  • #319-117/120: Done. Now asserting exception message.
  • #319-123/124: Done. I don't think so. We want tot test the creation of the source field. Since the type in the base class already had a source field this wouldn't work.
seanb’s picture

Status: Needs work » Needs review
Related issues: +#1945040: Rename assertIdentical() to assertSame() for better compatibility with PHPUnit
StatusFileSize
new244.69 KB
new7.39 KB
new40.75 KB
new103.69 KB

I fixed the tests (hopefully). The 2 following comments are still open:

  • #319-28
  • #319-36

Added a bunch of interdiffs for easy review. Phenaproxima, could you check and confirm all points are properly addressed?

This was done:

  • #319-62: Reverted since we need to execute more source field logic the first time. Marcoscano already said it but I was a fool and didn't listen :(
  • #319-73/77: Reverted since access() returns a bool when $return_as_object = FALSE so we can't chain it then.
  • #319-95: Changed to use assertIdentical() since assertSame() is not supported by simpletest. Added todo with a link to https://www.drupal.org/node/1945040.
  • Fixed issue in MediaUiFunctionalTest
  • Changed some doc comments for @todo's to be consistent and a fixed a copy/paste error for the @todo of getCreatedTime() and setCreatedTime().
xjm’s picture

Issue summary: View changes

Attention reviewers!

Last week during the Media meeting, we discussed how we can ensure that all points of feedback are addressed on this issue so it is easier for people to sign off on the issue with confidence. I made this spreadsheet:
https://docs.google.com/spreadsheets/d/1Ba_dN9K7SbfHMBHZmcGVAI576Gy_u8ZX...

  • It has a row for each comment and point.
  • I put the newest review comments at the top since those are the most likely to still be relevant.
  • There is a column to track where each review point has been addressed; leave blank if it hasn't.
  • There is also a column for the reviewer to confirm that yes, their feedback for that point has been addressed in full in the listed comment. Enter a 1 if you are the person whose name is listed in the reviewer column and your feedback is fully addressed now.
  • There are columns for whether a followup is needed for the point and whether said followup is listed in the issue summary; enter 1 for each when relevant.
  • There are a bunch of columns to mark whether a specific point might affect the listed area (security, data integrity, etc.). These are to help me do a release management review of the issue.

Anyone can help populate the spreadsheet. If you have previously reviewed this issue, please enter your review comment in the sheet and check whether your feedback has been addressed!

Thanks!

Edit: I have review notes for comments from #1 through #107 so I will enter those in the spreadsheet in another tab for now (so we don't edit over top of each other).

phenaproxima’s picture

Checked my review (#319) and updated the spreadsheet from #333.

Here are my comments regarding the ones that have not been addressed. I'm quite comfortable letting many of these go; the rest can be addressed in follow-ups. None of these are commit blockers.

#319.3: Reviewing @dawehner's original comment in #104, it looks like the inline template was only beneficial when we wanted to include HTML tags. This is no longer the case, so the inline_template serves no purpose. That said, I'm not hell-bent on removing this. Can always be done later or in a follow-up issue.

#319.6: The _permission requirement allows us to "or" permissions together like administer media+delete any media. That might be a better way to go here, but I'm OK with that being a follow-up issue.

#319.10: $this->set() is being called but the value is not returned, which breaks interface conformance. Same for setOwner().

#319.28 and #319.29: Both should be follow-ups.

#319.36: OK for now, but should be a follow-up issue.

#319.45: Still incorrect but I don't really mind. We can fix this when we fix it, in some future issue...

#319.46: Ditto.

#319.50: Ditto.

#319.54: Not fixed, but it's just a style thing. No big deal.

#319.58: Greatly improved, although there is a tiny grammatical snafu in the new description. It can be fixed at some later time, as far as I'm concerned.

#319.62: Doesn't seem to be fixed, but like #54, it's a style thing and not a big deal at all.

#319.73 and #319.77: Not fixed, but I don't care much.

#319.103 and #319.104: Don't appear to be fixed (bit hard to tell), but these tests are incredibly dense anyway and could use some refactoring anyway, IMHO. Doable in the future, as a follow-up or series of follow-ups. The most important thing is that the tests are robust and very thorough, which they most certainly are.

seanb’s picture

Addressed #335:

#319.3: Removed inline template.

#319.6: Changed to administer media+delete any media

#319.10: Added missing return.

#319.28: Follow up -#2862422: Add per-media type creation permissions for media

#319.29: Follow up -#2835535: Standardize basic content entity form logic in ContentEntityForm

#319.36: Maybe slashrsm can comment on this one?

#319.45: Fixed.

#319.46: Found a missing one indeed and fixed it.

#319.50: Fixed.

#319.54: This won't work: Call to a member function get() on null in media/src/Entity/MediaType.php on line 174

#319.58: Changed it a bit. If there are better suggestions please let me know!

#319.62: I don't think we can change this (I could be missing something). See my comment in 332.

#319.73 and #319.77: I don't think we can change this (I could be missing something). See my comment in 332.

#319.103 and #319.104: Follow up: #2862425: Refactor tests for media module

wim leers’s picture

My last comment was #311. Catching up on everything since then.


#312 addressed my feedback in #310.


#316: I like NULL means no value. I dislike exceptions for the reasons given in #317. This was implemented in #318. Great!


#319: wow, nice write-up! Also, nice of you to soften the blow of a 126+-point review with a very positive introductory paragraph :P Many of the points are nitpicks, but also quite a number are not nitpicks at all, for example points 28, 30, 36, 53, 54, 56, 81, 107. (Also, I've never heard of the elvis operator :D)
The only one I think that doesn't make sense is #73/77 (same remark). The answer to 89 is that it's okay to use State in tests to emulate different implementations of hooks/event subscribers. 103 I disagree with explicitly, because the whole point of that test is that it's testing that the UI works as expected. It's not about testing what's stored, and hence testing the API would be missing the point — the point is that we're testing the auto-generated UI directly.
#325 and #330 addressed all this, amazingly!

  1. +++ b/core/modules/media/src/Plugin/Action/UnpublishMedia.php
    @@ -31,10 +31,8 @@
    -    $result = $object->access('update', $account, TRUE)
    -      ->andIf($object->status->access('update', $account, TRUE));
    -
    -    return $return_as_object ? $result : $result->isAllowed();
    +    return $object->access('update', $account, $return_as_object)
    +      ->andIf($object->status->access('update', $account, $return_as_object));
    

    This is wrong.

    This might cause BOOL->andIf(BOOL), which will simply fail.

    Please revert this. (That was for #319.73 + #319.77.) EDIT: already done in #332, great :)

  2. +++ b/core/modules/media/src/Plugin/Field/FieldFormatter/MediaThumbnailFormatter.php
    @@ -188,2 +173,29 @@
    +   * Get the URL for the media thumbnail.
    ...
    +  protected function getMediaUrl(MediaInterface $media, EntityInterface $entity) {
    

    I was going to say that I don't like this. But it's protected, so that's fine.

    However, the description is wrong.


#321 + #326 + #327 + #328 + #329: regarding the module being hidden or not: sounds like that's been sorted out :)


I think this is pretty much ready. I already indicated that in #309. Would be good to still address #319-28 + #319-36. And probably the nits in #335?

I could do another complete review, but I think we caught pretty much all the things at this point. Now updating the spreadsheet for my review points.

My main concern was the "complex field mapping" functionality, but I was convinced by #310 + #311 that it's fine/safe to defer it to a follow-up.

amateescu’s picture

StatusFileSize
new244.7 KB
new625 bytes

Here's the small update for #2248983: Define the revision metadata base fields in the entity annotation in order for the storage to create them only in the revision table that I promised to write here at Dev Days Seville :)

The current usage of RevisionLogEntityTrait only works because \Drupal\Core\Entity\ContentEntityType::getRevisionMetadataKeys() uses the BC layer by default and specifies default names for revision metadata keys, but we should be future-proof and specify them explicitly.

webflo’s picture

StatusFileSize
new244.7 KB

generic.png was broken. I replaced it with the one from media_entity module.

imiksu’s picture

Re #337.2

I was going to say that I don't like this. But it's protected, so that's fine.

However, the description is wrong.

As per quick IRC conversation with Wim Leers, the problem was that the method is getMediaUrl but the description is "get media thumbnail". A media entity's URL !== a media entity's thumbnail.

Therfore he suggests renaming the method name to getMediaThumbnailUrl or even just getThumbnailUrl since it's protected anyway.

xjm’s picture

Status: Needs review » Needs work

Marking NW for #346.

I think #345 undoes previous feedback in #191 about removing Adobe metadata (though probably better to have that than a broken image).

diff --git a/core/modules/media/media.info.yml b/core/modules/media/media.info.yml
new file mode 100644
index 0000000..8926804
--- /dev/null
+++ b/core/modules/media/media.info.yml
@@ -0,0 +1,11 @@
+name: Media
+description: 'Allows for media to be created.'
+type: module
+package: Core
+version: VERSION
+core: 8.x
+hidden: true
+dependencies:
+  - file
+  - image
+  - user

I thought the intent was that media entities should eventually replace file and image; how does media depend on file and image then? (Edit: pasted the wrong snippet; fixed now).

imiksu’s picture

StatusFileSize
new244.71 KB
new1.3 KB

I decided to go with getMediaThumbnailUrl, I felt it was more descriptive to what the method does.

wim leers’s picture

Status: Needs work » Needs review
Issue tags: +DevDaysSeville
StatusFileSize
new3.65 KB
new245.06 KB

#349: thanks!
#347: RE: Adobe metadata: there's actually no Adobe metadata in there. It's already good to go.

Discussed the remaining unaddressed feedback at DDD Seville. Several of the ones that we agreed required some changes to the patch were assigned to me:

  1. #103.2 — discussed with @slashrsm and @seanB — the plan was first to create a follow-up for this, but we agreed on the solution: replace FILE_EXISTS_REPLACE with FILE_EXISTS_ERROR
  2. #160.4 (agreed on solution in person with people working on Media)
  3. #213.2: agreed that we defer this to @yoroy. So, I talked to @yoroy at DDD Seville. He settled on Create reusable media.
  4. #279.6 (agreed on solution in person with people working on Media)
  5. #309.2 (agreed on solution in person with people working on Media)
slashrsm’s picture

StatusFileSize
new246.58 KB
new246.61 KB
new2.64 KB

This addresses #319-36.

slashrsm’s picture

StatusFileSize
new1.75 KB
new247.35 KB
new247.38 KB

OK.... I was to fast. Test only patch will pass in #352. Improved a test a bit and made sure it actually fails without the fix. Sorry for the noise :(

alexpott’s picture

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

    I think this use of t() might be incorrect - or at least use the entity's language and not just the global language. Tricky - I'm not sure I can think of precedent.

  2. +++ b/core/modules/media/src/Entity/Media.php
    @@ -0,0 +1,458 @@
    +    $fields['thumbnail'] = BaseFieldDefinition::create('image')
    +      ->setLabel(t('Thumbnail'))
    +      ->setDescription(t('The thumbnail of the media item.'))
    +      ->setRevisionable(TRUE)
    +      ->setDisplayOptions('view', [
    +        'type' => 'image',
    +        'weight' => 5,
    +        'label' => 'hidden',
    +        'settings' => [
    +          'image_style' => 'thumbnail',
    +        ],
    +      ])
    +      ->setDisplayConfigurable('view', TRUE)
    +      ->setReadOnly(TRUE);
    

    I'm pretty sure that the thumbnail field should be translatable - see #2861508: Thumbnail is not language-aware - and certainly the alt and title columns need to be.

slashrsm’s picture

StatusFileSize
new247.42 KB
new983 bytes
new621 bytes

#361 addressed.

I also parsed the commit history of the contrib module. Attached is the list of all contributors. It would be great if we could attribute them when committing this patch.

alexpott’s picture

I think we should have some tests around #361 and the solution in #363. Seems something that could easily get broken.

webchick’s picture

I'm trying to test the proposed change record at https://www.drupal.org/node/2863992 by porting the https://www.drupal.org/project/media_entity_audio module.

The first thing you run into is the removal of the media_entity_copy_icons(). I see earlier in the discussion there was speculation that this function isn't used outside of Media Entity module. However, in a quick survey of the sub-modules listed at https://www.drupal.org/project/media_entity (Twitter, Audio, etc.), all of them are doing this in their hook_install():

function media_entity_XXX_install() {
  $source = drupal_get_path('module', 'media_entity_XXX') . '/images/icons';
  $destination = \Drupal::config('media_entity.settings')->get('icon_base');
  media_entity_copy_icons($source, $destination);
}

This seems further backed up by the documentation at the top of the $default_thumbnail_filename which reads:

+   * The thumbnails are placed in the directory defined by the config setting
+   * 'media.settings.icon_base_uri'. When using custom icons, make sure the
+   * module provides a hook_install() implementation to copy the custom icons
+   * to this directory. The media_install() function provides a clear example
+   * of how to do this.

Unfortunately, the media_install() function went from 3 lines to many lines, due to the inlining of this function:

+  $source = drupal_get_path('module', 'media') . '/images/icons';
+  $destination = \Drupal::config('media.settings')->get('icon_base_uri');
+  file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+
+  $files = file_scan_directory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/');
+  foreach ($files as $file) {
+    file_unmanaged_copy($file->uri, $destination, FILE_EXISTS_ERROR);
+  }

That's an awful lot of code to copy/paste X every media plugin type. Could we add the helper function back?

Additionally, it should be noted that the inline code is different from the original copy of this function (exception checking removal, additional icon types, etc.), but I surmise those changes were by design.

EDIT: Oh, and @xjm notes that it might be nice IN A FOLLOW UP to automatically copy icons to their destination rather than adding boilerplate code to hook_install().

xjm’s picture

So reading over the map-of-comments, I think #2863438: Allow modules to declare the list of files it ships with that should be publicly accessible is the issue intended to address the "Why do we need to copy this manually anyway?" point, although it's scoped differently than I would expect.

Regarding these three lines:

  $source = drupal_get_path('module', 'media_entity_XXX') . '/images/icons';
  $destination = \Drupal::config('media_entity.settings')->get('icon_base');
  media_entity_copy_icons($source, $destination);

To me they only seem like they would need to be one line? The module name and icon_base_uri can both be retrieved from the module in a less boilerplate way. So we move that code or the like into the module's helper API for hook_install(), and then follow up to eliminate the need for any hook_install(). Feels a similar pattern to optional config, but for assets.

boobaa’s picture

StatusFileSize
new247.43 KB
new1.41 KB

Fixing two typos in comments only.

xjm’s picture

Issue summary: View changes

I've updated the issue summary with some but not all of the remaining tasks, and removed resolved ones. I'm marking comments on the DDD meeting notes resolved as I work through the action items from the meeting and check them.

Regarding contribution credit, we will add issue credit for the Media contrib module contributors (provided by @slashrsm and linked in the summary) just prior to commit. There are 66 names (not hundreds or thousands) so I think this is safe to do on the issue.

Remaining tasks I've added to the summary, plus a couple new things:

  1. I see a followup issue for #2862453: Make "update any media" a restricted permission, or remove the flag from "delete any media", but this is actually a pretty important issue from a security perspective. Let's do that in this patch instead. So add the restrict access flag here:

    +++ b/core/modules/media/media.permissions.yml
    @@ -0,0 +1,26 @@
    +update any media:
    +  title: 'Update any media'
    

     

  2. +++ b/core/modules/media/src/Entity/Media.php
    @@ -189,7 +189,13 @@ protected function updateThumbnail($from_queue = FALSE) {
    +   * Update the queued thumbnail for the media item.
    +   *
    +   * @return \Drupal\media\MediaInterface
    +   *   The updated media item.
    +   *
    +   * @internal
    +   * @todo If the need arises in contrib, consider making this a public API, by adding an interface that extends MediaInterface.
    

    Few docs nitpicks; just saw these scanning to confirm the fixes made during the sprint:

     

  3. Address #366 / #367 (possibly with a followup to avoid adding procedural API unnecessarily).
     

  4. Add test coverage for #361 and #363; see #364.
     
  5. Regarding this point from the summary:

    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.

    Can we file and link those now?
     

  6. There is a small point that I think we missed following up on in our discussion on Thursday regarding which image file types should be allowed as icons. In #213 @Wim Leers says:

    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.

    But these file types were (I think) added in response to previous feedback which I've lost track of. During the meeting we said this could be discussed in a followup because it's not that important, but I don't see a followup issue for it. Added a note to the summary for now; maybe @Wim Leers can confirm/clarify.
     

  7. Finally, @tedbow and @webchick worked on the change record draft during the DDD sprint. This document should eventually communicate what contrib developers need to know to easily update their code from Media Entity in contrib. This is probably the biggest remaining single task (other than committer review and signoff...) and so we could definitely use the help both of people trying to upgrade contrib code but not familiar with this issue, and of people with strong knowledge about this patch and about contrib Media Entity to confirm its accuracy. (@webchick was testing the documentation out by updating one particular module, as she describes above).

Edit: regarding:

I see a followup issue for #2862453: Make "update any media" a restricted permission, but this is actually a pretty important issue from a security perspective. Let's do that in this patch instead. So add the restrict access flag here:

I'm wrong about this. The "Edit any X" and "Delete any X" permissions in core for node content do not have restrict access, so we shouldn't add it to media either. And, at least, they should be consistent. I'll reopen the other issue.

boobaa’s picture

Another question has been surfaced during the IRC meeting today: how could the MediaSource plugin (or the module providing it) change the default entity form display for its auto-created field?

As createSourceField() only creates the field_config but does not save it, I can't find any way to change the MediaSource entity form display for this field to a more sane default (like a custom derivative of inline_entity_form in my case). As this method is called from MediaTypeForm::save() (and not from anything in MediaSourceBase), I cannot even intervene. There's not even a hook there. In the contrib era, I could get this done via hook_media_bundle_insert()/hook_ENTITY_TYPE_insert(), but this isn't available either. So we have this question.

@seanB asked if this means we can't change the display in the interface because it's locked, or does it just mean the default is not good enough?

As locked fields' form display settings can be changed (at least now, before #2274433: Do not allow to alter Locked field via UI is fixed), it's only about the latter part: allowing the MediaSource plugin (or at least the module providing it) to change the form display settings so they can be "good enough". In the contrib era, brightcove.module solved this by changing the form display settings from hook_ENTITY_TYPE_insert(). As you can see, that approach wasn't the best one either, because the field wasn't created with appropriate form display settings (honestly, I don't really know if it's even possible), but those settings were only changed afterwards.

OTOH, you can see it's only about providing that $form_display_settings somehow, as it's fairly clear when to do what with that information. @seanB and I agreed that it should be doable to provide this information via the annotation. Because of that, @seanB said we could do this without changing the interface, ie. we could fix this in a followup.

To cut this long story short: MediaTypeForm::save() should be able to create the field with form display settings provided by the annotation (or fall back to defaults if the annotation doesn't have this info). A followup is created for this: #2865184: Allow MediaSource plugins provide default field form/view display settings.

seanb’s picture

While the field storage is locked, you can edit the field widget/field formatters for the field you have created. Besides that when you ship a module with default config for a media type you can also add some defaults.

Boobaa, could you add an example of the settings you would like to be able to do? I'm not sure what you exactly would like to change (widget and/or formatters) and how far we should go in allowing modules to change through the annotation. Adding more methods to the MediaInterface is something we should try to avoid.

I do see cases where you want a specific widget or formatters when creating a field, but since you also get defaults when adding a field to a media entity (or a node for that matter) through the interface, I'm not sure if we should do this.

boobaa’s picture

@seanB, the followup at #2865184: Allow MediaSource plugins provide default field form/view display settings already has an example, both for the widget and the formatter stuff.

Regarding the defaults: for complex stuff (like our Brightcove Video entities), MediaSource plugins might want to have their own defaults for their fields instead of relying on the defaults the field type provides. Particularly in our case, we prepared a custom inline_entity_form for handling our brightcove_video entities when they're linked eg. from a node via an entity reference field. While the generic, core-provided defaults for entity reference fields might be sufficient for most cases, it's not not the best for our admittedly complex case.

But hey, as you already suggested on IRC, the MediaInterface does not need any changes for this to work – more details on this can be found in the followup (which has some copy-pasted code that actually works, btw).

seanb’s picture

Status: Needs work » Needs review
StatusFileSize
new252.42 KB
new10.5 KB

Fixed:

  1. Tests for #364
  2. #370.1 is now a followup
  3. #370.2 is done
  4. #370.3 Updated the google doc with the change request at https://docs.google.com/document/d/1IlfkkVLbFhlrj0NRfIa8UIXgoqQBudiRxRDP... and added a description to deal with the removed media_entity_copy_icons function to address #366.
  5. #370.4 Tests are done.
  6. #370.5: Follow up added by naveenvalecha in #375
  7. #370.6 I found where this was changed. The copying of files was changed as requested by Wim Leers in #191.13 and addressed in #198. We can change it again to only allow svg and png?

Still todo:

  • #370.7: The CR, which is being done in the google doc mentioned before.
phenaproxima’s picture

The change record has been updated with explicit one-to-one conversion instructions. It would be helpful if @webchick (or anyone else) could try to port a media contrib module again using those instructions, to see if there are any gaps left. I based the instructions on everything in the Google Doc, though, so hopefully it's pretty thorough.

naveenvalecha’s picture

Issue summary: View changes

Removing File followups for Devel Generate and Plugin contrib modules? from remaining tasks in the favour of Plan issue #2860796: Plan for contributed modules with Media Entity API in core

//Naveen

naveenvalecha’s picture

StatusFileSize
new253.87 KB
new1.54 KB

I did the port of couple of the media_entity contributed modules here #2860796: Plan for contributed modules with Media Entity API in core and during the port of the Facebook Media Source #2869021: Facebook Port to the proposed Media core module API, I encountered the issue which was due to the module didn't define the config metadata of the media source which results in the WSOD.Isn't this seem to be the blocker here I have posted its backtrace there #2869021-13: Facebook Port to the proposed Media core module API?
I have re-read the whole thread to find that there's nothing follow up left.Found that we need tests for the media_theme_suggestions_media.
Fixed:
#191.23 Added tests for media_theme_suggestions_media

//Naveen

phenaproxima’s picture

Status: Needs review » Needs work

Forgive me (and correct me) if I'm wrong, but I see two things wrong here --

+++ b/core/modules/media/src/Entity/Media.php
@@ -0,0 +1,461 @@
+      $this->thumbnail->alt = $this->t('Thumbnail', [], ['options' => $this->langcode->value]);

I think this call might be wrong. According to https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21StringTra..., this call should be:

$this->t('Thumbnail', [], ['langcode' => $this->langcode->value])

Corollary to this, it would appear that the tests added by @seanB in #376 are not actually testing that the thumbnail's alt text is translated. It seems to be a generic test of general field translatability, in this case covering the media item's source field.

By no means am I against having a lot of test coverage, but that seems like something that should be, and probably is, thoroughly tested elsewhere in core. We should change the tests, or at least amend them, to specifically test the translation of the thumbnail's alt text.

boobaa’s picture

Additionally, the patch should not add calls to deprecated functions (eg. the current MediaTypeForm::save() calls both entity_get_form_display() and entity_get_display(), which are marked as deprecated).

phenaproxima’s picture

Additionally, the patch should not add calls to deprecated functions (eg. the current MediaTypeForm::save() calls both entity_get_form_display() and entity_get_display(), which are marked as deprecated).

Well...those functions are deprecated, sure, but they have no real replacement. There is an issue open to create replacements on EntityDisplayRepositoryInterface (I know 'cause I wrote the latest patch for it :) but it has stalled for the time being. So, for now, it's OK if the patch calls those functions.

chr.fritsch’s picture

StatusFileSize
new254.61 KB
new3.49 KB

I fixed the first comment from @phenaproxima in #380

Also i tried to add test coverage, but then i ran into one problem. It seems that $media_translation->get('thumbnail') is always null in MediaTranslationTest.

Unfortunately i have to go in a meeting now, so i can not continue on it now. Here is my work so far.

seanb’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new255.67 KB
new9.76 KB

Good thing we added more tests. It appears the preSave() method on Media only runs for the active translations (not for all other translations). So the metadata was not being added for the translations. Same for postSave(). Both are fixed.
Also replaced deprecated QueryFactory with EntityTypeManager and a deprecated assertEqual() call.

I left entity_get_form_display() and entity_get_display() since they don't have real replacements yet as phenaproxima mentioned. Added followup #2872159: Media: Replace deprecated entity_get_(form_)display() with replacement methods in EntityDisplayRepository and a @todo in the code.

phenaproxima’s picture

+++ b/core/modules/media/src/Entity/Media.php
@@ -273,25 +273,31 @@ public function preSave(EntityStorageInterface $storage) {
+          if ($translation->hasField($entity_field_name) && ($translation->get($entity_field_name)->isEmpty() || $translation->sourceFieldChanged()) && ($value = $media_source->getMetadata($translation, $metadata_attribute_name)) && $value !== NULL) {

In reviewing this change, I realized that there is a flaw in the logic:

($value = $media_source->getMetadata($translation, $metadata_attribute_name)) && $value !== NULL

If the first condition passes, $value is by definition truthy (and therefore most definitely non-null). So the second condition is redundant.

But worse, checking $value for truthiness means that values like FALSE, '0', '', 0, and so forth will not pass the condition...and therefore not be updated in the translation, even if they are valid values. This means we might be throwing data out. Seems like something we might want to fix? NULL is the only value that should represent "no value".

+++ b/core/modules/media/src/Entity/Media.php
@@ -273,25 +273,31 @@ public function preSave(EntityStorageInterface $storage) {
+        if (!$translation->label()) {

I'm not sure $translation->label() will ever be empty. Correct me if I'm wrong, but aren't all the values copied into the translation when it's created? Therefore, aren't all translations guaranteed to have a label?

seanb’s picture

StatusFileSize
new255.64 KB
new1.01 KB

I decided to remove the check for the getMetadata() value. It could be that a metadata value is unset after a update of the source field. In that case you also want to update the field to a NULL value.

The check for the translation label is there because the code is also being executed for the original entity (not only for translations). Btw, if for some reason the label is empty for the original entity and the translations are created before the media entity is saved (like in the test), the label is empty on all translations.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Baltimore2017

So many people have gone over this patch so many times. We've iterated and improved a lot of things and caught a lot of possible issues, and we have our work cut out for us in the follow-ups.

That said:

  • The code has been thoroughly reviewed. It's high-quality and seems to have satisfied the most pressing architectural concerns.
  • The review spreadsheet from #333 is completed -- all issues have either been confirmed fixed by the person who spotted them, or deferred in a follow-up.
  • The change record draft is complete, and has been used as a working guide to port a Media Entity contrib module into this new architecture. As far as we can tell, all possible gotchas of porting have been addressed.
  • #361 and #363 are addressed and covered by the tests.
  • The media_entity_copy_icons() bugaboo has been deferred to a follow-up.
  • We have a very clear plan of action moving forward after this patch lands, outlined here.
  • #2836153: Improve metadata mapping UI on Media type form seems pretty up-to-date. Namely, we haven't agreed on a solution to the UX problem, but we have a follow-up filed in which we can bikeshed and work on a patch.

In light of all this, I don't think we need to wait any longer. The time has come for this patch to move on to the committers. It's Miller time.

Godspeed.

phenaproxima’s picture

StatusFileSize
new255.64 KB
new367 bytes

A small re-roll (very much still RTBC), which removes the hidden flag from the Media module's info file and puts it in the Core Experimental package. This is in accordance with @xjm's comment in #2825215: Media initiative: Roadmap.

naveenvalecha’s picture

StatusFileSize
new255.64 KB
new2.62 KB
new45.98 KB

@dawehner, Peter Wolanin, @xjm and I had a word regarding the restrict True on the delete and update any media permission. As these are the normal content entities and the node system does work this way so we don't need the strict restriction on the update any media and delete media permissions. Related issue #2862453: Make "update any media" a restricted permission, or remove the flag from "delete any media"

I have also addressed few minor things which I found.

We only have the two deprecated functions that we are using in the media module and we have the follow-up issue #2872159: Media: Replace deprecated entity_get_(form_)display() with replacement methods in EntityDisplayRepository for that.
Deprecated functions

//Naveen

xjm’s picture

Status: Reviewed & tested by the community » Needs work

This review is cross-posted at #2877346: Media Entity finalish review (disregard if you're not part of the Media Initiative; working around long issue node) since this issue node is practically unusable. That issue will be used as a scratch workspace for final work on this patch.

The following review and action items is the result of discussion between @catch, @alexpott, @seanB, @phenaproxima, and myself. Most of it is followup stuff. Enjoy!

Remove from patch into "must" or "should" followups

These things should be removed from the patch so that they can be reviewed on their own without blocking the issue. Each should be mentioned in the change record with a link to the followup issue.

In the case of some parts, we might reduce the scope and add the rest back later; in other cases, the functionality might not need to be in core. But we'll discuss each in a dedicated issue. Be sure to add the notes below to the applicable summaries.

  1. Token integration. media_token_info(), test coverage, etc. This is a "must" followup because it is required for pathauto support. From @catch:

    do we really need tokens for vid and uuid, even node doesn’t provide uuid.

  2. Actions integration, including all test coverage for it, configuration, SaveMedia action, etc. This should be a "Should" followup since we are unsure how much contrib needs this. There should also be a related followup for generic entity actions (as per tstoeckler's suggestion early on the issue). From @catch:

    The actions are in config/optional but the only thing they depend on is media and system therefore they are always going to be installed. If they are there they should be in config/install.

    Also, in particular:

    SaveMedia::execute() is weird setting changed to 0

    This was copied over from node and may not be needed?
    @catch also notes that the action itself is weird and might not be needed even if we add the media actions in core.

  3. Remove forced validation in Media::preSave() and file a followup to discuss presave validation for Media and/or for entities in general ("Should").
  4. Remove full view mode and "tricky" template title handling. There are many valid usecases for this, but it doesn't necessarily need to be in core. Add a "Could" followup note to discuss whether it should be in core. (No need to file an issue yet for a "could".)

Other followups to file

  1. File a dedicated, postponed Media Entity followup for #2696555: On the entity form of revisionable entities, make "create new revision" and "revision log" configurable and add it under "Should" on the roadmap.
  2. File a "Must" followup to fix Media Entity tests for #2827014: Throw an exception when testing status code or response headers in functional JavaScript tests. From @alexpott:

    The problematic test is MediaUiJavascriptTest - there are 4 calls to $assert_session->statusCodeEquals() - 3 of which can be removed but the last needs replacing with an assertion on something like "The media type %name has been updated." being on the page

  3. File a "Must" followup to review JS tests. From @alexpott:

    there's new code to wait for stuff that's not being used. This might mean that they currently have random errors.

  4. File a "Should" followup to discuss the use of <article>:

    Twig templates use ? https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article - I don't think that media items should wrapped in that.

    phenaproxima: Open to suggestions here, but not sure what a more appropriate tag would be.

    seanB: It’s either this, or a plain old div I believe. I think perfectly described that a media item is a ‘self-contained composition in a document’ and ‘independently distributable or reusable’.

    alexpott: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article the aria roles that are related really do not make me think this is at all related to what is in media and when you put a media entity on a node using entity reference it's going to be wrapped in article tags - inside another article - seems weird.

  5. The ongoing saga of Media::updateThumbnail() which has been discussed at length on the issue and at the DDD sprint. Mark the darn thing explicitly internal and file a followup to discuss it more ("Should"). Someone should go through the whole issue, the DDD spreadsheet, the DDD agenda/notes, this review's notes, etc. and collect all feedback about it and put it in the followup.

Things to fix in the patch

  1. @naveenvalecha changed "tempstore" to "temp store" in the patch, however:
    [ibnsina:drupal | Wed 08:25:38] $ grep -r "tempstore" * | wc -l
          75
    [ibnsina:drupal | Wed 08:25:45] $ grep -r "temp store" * | wc -l
          17
    

    So let's use "tempstore" for now.

  2. Fix the summary of Media:getThumbnailUri(). It gets the thumbnail's URI, not the media entity's. Also explicitly mark the method internal. @todo also check the issue for other discussion of this method.
  3. Rename Media:sourceFileChanged() to Media::has/isSourceFileChanged() (which?) and explicitly mark it @internal.
  4. In In path_entity_base_field_info() fix the in_array() usage to be strict to avoid bugs.
  5. Change \Drupal\media\MediaAccessControlHandler::checkCreateAccess() to allow users with 'administer media' to create (and add tests).
  6. Constructs an ImageFormatter object. C&P error; should be MediaThumbnailFormatter.
  7. Mark Media::updateThumbnail() @internal explicitly and add an @todo referencing the followup to be added above.
  8. Add an @todo to the template with <article> referencing the followup to be added above.
phenaproxima’s picture

We're on home stretch, everyone!

All feedback from #390 has been addressed in #2877346: Media Entity finalish review (disregard if you're not part of the Media Initiative; working around long issue node), and I have closed that issue as a duplicate. Attached is the final patch to emerge from that issue, and its interdiff against #389.

Here is #390, point by point. I reviewed the code changes and confirmed they are completed.

Token integration. media_token_info(), test coverage, etc. This is a "must" followup because it is required for pathauto support.

Removed and moved into its own patch at #2877378: Add token replacements for Media.

Actions integration, including all test coverage for it, configuration, SaveMedia action, etc.

Removed and moved into its own patch at #2877383: Add action support to Media module.

Remove forced validation in Media::preSave() and file a followup to discuss presave validation for Media and/or for entities in general

Removed, and a follow-up has been filed: #2877397: Determine when and if media items should validate themselves

Remove full view mode and "tricky" template title handling.

This was NOT done, because it breaks things for reasons that are not directly related to Media. @Wim Leers explained it beautifully in #2877346-25: Media Entity finalish review (disregard if you're not part of the Media Initiative; working around long issue node). In the meantime, Media will do what Taxonomy does, which is...provide a full view mode with tricky title handling. This problem should be solved generically in core, and we can discuss it in the follow-up: #2877400: Find a way to make title handling generic in entity templates.

File a dedicated, postponed Media Entity followup for #2696555: On the entity form of revisionable entities, make "create new revision" and "revision log" configurable

Done: #2878110: [PP-1] Make revision log and "create new revision" configurable for media items

File a "Must" followup to fix Media Entity tests for #2827014: Throw an exception when testing status code or response headers in functional JavaScript tests

Done: #2878112: Fix Media JavaScript tests to account for changes in JavascriptTestBase

File a "Must" followup to review JS tests.

Done: #2878113: Find possible random errors in Media's JavaScript tests

File a "Should" followup to discuss the use of &lt;article&gt;

Done: #2878115: Make the HTML wrapper tag for media items more semantically correct

The ongoing saga of Media::updateThumbnail() which has been discussed at length on the issue and at the DDD sprint. Mark the darn thing explicitly internal and file a followup to discuss it more

Done: #2878119: Whether queued or not, update the media thumbnail and metadata before beginning the entity save database transaction

Change "temp store" to "tempstore"

Done.

Fix the summary of Media:getThumbnailUri(). It gets the thumbnail's URI, not the media entity's. Also explicitly mark the method internal. @todo also check the issue for other discussion of this method.

Done.

Rename Media:sourceFileChanged() to Media::hasSourceFieldChanged() and explicitly mark it @internal.

Done.

In path_entity_base_field_info() fix the in_array() usage to be strict to avoid bugs.

Done.

Change \Drupal\media\MediaAccessControlHandler::checkCreateAccess() to allow users with 'administer media' to create (and add tests).

Done.

Constructs an ImageFormatter object. C&P error; should be MediaThumbnailFormatter.

Done.

Mark Media::updateThumbnail() @internal explicitly and add an @todo referencing the followup to be added above.

Done.

Add an @todo to the template with <article>

Done.

catch’s picture

Interdiff looks great, thanks!

Gábor Hojtsy credited cbr.

gábor hojtsy’s picture

gábor hojtsy’s picture

gábor hojtsy’s picture

gábor hojtsy’s picture

Anonymous’s picture

gábor hojtsy’s picture

catch’s picture

OK it's taken a long time to get here, but I've just committed 01621e5 and pushed to 8.4.x.

This is by far the single largest feature to go into 8.x directly as stable since 8.0.0 was released, so thanks for everyone's extremely hard work both on figuring out how to do it in the first place, and getting it done.

Looking forward to seeing everyone in the next issues around this.

catch’s picture

Status: Reviewed & tested by the community » Fixed

One extra comment is never not enough. Marking fixed this time :P

  • catch committed 01621e5 on 8.4.x
    Issue #2831274 by slashrsm, seanB, Wim Leers, chr.fritsch, phenaproxima...
gábor hojtsy’s picture

nevergone’s picture

Well, another content entity type in core. But where is related access layer? #777578: Add an entity query access API and deprecate hook_query_ENTITY_TYPE_access_alter()

dbt102’s picture

gábor hojtsy’s picture

Issue tags: +8.4.0 release notes

Let's not forget to put this into the release notes ;)

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

joseph.olstad’s picture

I believe that this change may have caused a regression preventing the 'contrib' version of 'media' 8.x from being uninstalled from 8.x

OnkelTem’s picture

Thanks guys for your great work of adding this to the Core.

But where is media browser? From what I observe, it's not available in Drupal 8.4.2, there is no information about it both in this thread and on the Media module page. And honestly it's kind of odd that one of the most popular module had been first abandoned with promises it would reappear somewhere in future, and now when the future is here it's not even available in UI, and when you finally enable with drush Media browser is lacking...

oriol_e9g’s picture

@OnkelTem the media browser is available in contrib https://www.drupal.org/project/media_entity_browser

michaelkoehne’s picture

It would be good if the Media API offered similar functionality as the Node API:
hook_node_access_records => hook_media_access_records
hook_node_grants => hook_media_grants

Is there already a feature request for this?

mxh’s picture

It would be good if the Media API offered similar functionality as the Node API:
hook_node_access_records => hook_media_access_records
hook_node_grants => hook_media_grants

Is there already a feature request for this?

Have a look at #2909970: Implement a query-level entity access API (currently at Entity API contrib module)