Adding the Image component results in a state considered invalid

Created on 24 January 2025, 12 days ago

Overview

After adding the XB shipped Image component to the page, I'm getting following unrecoverable error state:

Drupal\experience_builder\Exception\ConstraintViolationException: Validation errors exist: Array.model.295d63ae-7d57-40d2-adaa-5d13913eb055.image.src: File '/sites/default/files/600x400.png' not found. Object(Drupal\node\Entity\Node).model.295d63ae-7d57-40d2-adaa-5d13913eb055.image: The property image is required in Drupal\experience_builder\Controller\ApiPreviewController->clientModelToInput() (line 144 of /var/www/html/drupal/modules/custom/experience_builder/src/Controller/ClientServerConversionTrait.php).

Proposed resolution

User interface changes

🐛 Bug report
Status

Active

Version

0.0

Component

Page builder

Created by

🇫🇮Finland lauriii Finland

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @lauriii
  • First commit to issue fork.
  • 🇮🇱Israel heyyo Jerusalem

    heyyo changed the visibility of the branch 3501902-adding-the-image to hidden.

  • 🇮🇱Israel heyyo Jerusalem

    heyyo changed the visibility of the branch 3501902-adding-the-image to hidden.

  • 🇺🇸United States bnjmnm Ann Arbor, MI

    This works fine on a fresh install hook_install needs to run to create the placeholder image and associated media entity.

  • 🇫🇮Finland lauriii Finland

    I had just re-installed before submitting this issue. Re-installed again and still seeing the same error message.

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    Can you put a breakpoint in the install hook and advise where it exits?

  • 🇫🇮Finland lauriii Finland

    How does the default image support work? Do extensions shipping components with default images have to provide an install hook to use an image by default?

  • 🇫🇮Finland lauriii Finland

    Can you put a breakpoint in the install hook and advise where it exits?

    The file /sites/default/files/600x400.png exists if I visit it manually 🤔 The media item that was added looks correct.

    I'm a bit confused about the whole underlying change. I'm not sure that it's the right behavior that we're adding the default images to the media library. The image property is not supposed to be tightly coupled with media library and we may want to add support for image uploads in future too: 🐛 Redux support for ImageWidget: `[image] String value found, but an object is required` Postponed .

  • 🇺🇸United States bnjmnm Ann Arbor, MI

    It is working fine for me with what the install hook provides.

    To narrow down what might be different it's probably worth checking admin/content/media to see if the media entity created via the install hook actually got created, and if so, is it accessing the image properly

    There are two steps in the process that can be checked.

    First Step

    First, the image in experience_builder/images/600x400.png is added as a managed file and copied to the root of public://

      $file = \Drupal::service(FileRepositoryInterface::class)->writeData(\file_get_contents(__DIR__ . '/images/600x400.png') ?: '', 'public://600x400.png');
    

    Questions:
    Is the file being copied?
    Where is it being copied to?

    Second Step

    A media entity is created referencing the managed file created in the prior step.

    if (MediaType::load('image')) {
        Media::create([
          'bundle' => 'image',
          'name' => 'Default placeholder image',
          'field_media_image' => [
            [
              'target_id' => $file->id(),
              'alt' => 'Placeholder image',
              'title' => 'Placeholder image',
            ],
          ],
        ])->save();
      }
    

    The "Default placeholder image" entity pictured below should exist and the preview should actually provide an image .

    Questions:
    Does this media entity exist?
    Is it previewing a valid image/path?

    If everything checks out above, please share path of public::// on the site that is failing on this.
    The sdc has the preview image path configured as - src: /sites/default/files/600x400.png which seems like it's making some assumptions regarding the stream path so I'd like to rule that out if there's nothing earlier in the chain failing.

  • 🇫🇮Finland lauriii Finland

    Media item exists and preview fine in the media UI:

    File exists:

  • 🇫🇮Finland lauriii Finland

    I was able to narrow done potential reason for this; I have the image multiple times in the /files directory because I've re-installed XB multiple times. This means that the file in the file entity has a suffix _4 and cannot be found with \Drupal\Core\Entity\EntityStorageInterface::loadByProperties.

  • 🇫🇮Finland lauriii Finland

    Deleted the file, tried re-installing but now I have the file existing in the file entities but the media entity doesn't exist. 🤷‍♂️

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    All of this is temporary code until we can delete ::findTargetForProps which is what 🐛 Some components cannot be used in XB because UI prevents SDC props being named `name` Active and its children are for.

    I think in the meantime using FileExists:Replace will smooth the issue Lauri experienced

  • Merge request !588Issue #3501902 Replace file if exists → (Open) created by larowlan
  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    Opened an MR to replace the file if it exists, that should smooth things until we get a chance to work on 🐛 Some components cannot be used in XB because UI prevents SDC props being named `name` Active

  • 🇫🇮Finland lauriii Finland

    Still running into the same problem from #13 that the media entity doesn't get created when I first install XB on a fresh install. I uninstalled XB and installed once more and now the image component is working. This allows me to UAT 📌 Media Library dialog styling Active but keeping this open because I bet that others will run into this problem.

  • 🇫🇮Finland lauriii Finland

    FWIW, I'm getting this error even when selecting existing images from the media library:

    Drupal\experience_builder\Exception\ConstraintViolationException: Validation errors exist: Array.model.b95fc0dd-f47f-4f1b-8d9f-b5cf59d8ef58.image.src: File '/sites/default/files/2025-01/Screen%201%20Teams%20Admin%20Section%20card.png' not found. Object(Drupal\experience_builder\Entity\Page).model.b95fc0dd-f47f-4f1b-8d9f-b5cf59d8ef58.experience_builder:image/image: The property image is required. in Drupal\experience_builder\Controller\ApiPreviewController->clientModelToInput() (line 144 of /var/www/html/drupal/modules/custom/experience_builder/src/Controller/ClientServerConversionTrait.php).
    
  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    I'm very keen to keep going on 🐛 Some components cannot be used in XB because UI prevents SDC props being named `name` Active so we can kill off ::findTargetForProps which is where this issue is coming from - but it sounds like we need to do something else in the meantime as a band-aid

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    #14++
    #19++

    I'd prefer to have @larowlan to focus hard on getting that stuff on track because we have to get it on track anyway.

    Let's not put a band-aid on a band-aid 😅🙏

    I'd like to make it @larowlan's top priority in the next 2 weeks to keep pushing forward on 🐛 Some components cannot be used in XB because UI prevents SDC props being named `name` Active and all its child issues/blockers to solve this at the root.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    📌 Implement saving block settings forms Active introduced this, by the way.

  • 🇺🇸United States bnjmnm Ann Arbor, MI

    re #20

    I'd like to make it @larowlan's top priority in the next 2 weeks to keep pushing forward on #3467954: META: Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc. and all its child issues/blockers to solve this at the root.

    That issue seems like the right priority, When that work is done, would it still be necessary for every prop-example image to become a media entity? It's fine with our dev-collection of components, but as more custom components start happening I could see that causing issues, such as someone deleting that placeholder image would make the component un-addable. It might be helpful to have a placeholder image generating controller, similar to placehold.it, where the URL determines width, height, color, content, then the required src for preview won't be at risk of deletion and repos don't need to have as many image files just to placehold stuff.

    Again, the data model work seems like the priority to me, but if the above seems worthwhile this issue could be the home for it as it would make the band-aid unnecessary.

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    I hit this on a fresh install and can confirm the file is created, but the media entity is not, which would indicate the media type doesn't exist when experience_builder.install is running - likely because we're now installing modules in bulk 📌 Add the ability to install multiple modules and only do a single container rebuild to ModuleInstaller Active (I'm running 11.x HEAD)

  • 🇬🇧United Kingdom longwave UK

    I hit this on a reinstall in the same way as #12. As per #23 I don't think we can rely on files existing at specific paths; perhaps in the images case at least we should be more lenient, if we can't find the underlying file then should we really be throwing an error message?

    @larowlan for now I think we should only consider compatibility with 11.1, we have a few months and 🐛 PHPUnit Next Major tests failing Active to solve compatibility with 11.2.

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    I wonder if everyone who is hitting this is testing using 11.x HEAD? 🤔

    #25: — if we don't, then the SDC subsystem itself will. At least generally speaking, I suspect that in this case, you're implying "just generate <img src="http://example.com/cat.jpg" alt="a cat"> and let the browser then fall back to showing the text alternative?

    #23: thanks, @bnjmnm — much appreciated! And is a very intriguing idea, with a lot of potential indeed 🤔

    Related: 📌 Update XB's `image` SDC to comply with best practices, and document those best practices Needs review .

    What you wrote actually ties in nicely with my alternative idea, in case #20 doesn't work out:

    • we need an SDC to be able to specify a default, and for generic SDCs, what you describe makes perfect sense
    • but we also need the ability for A) a site-specific SDC with an SDC-provided default image in its single directory, B) for a site to assign a site builder-specified default image when making a generic SDC available as component. (And this implies C) the ability to override an

      Both A and B can be solved by the same infrastructure: if the XB Component config entity could contain (and export/import like any config) the default image, then it'd be guaranteed to work. In case A ("SDC-provided"), the initial assignment happens automatically (upon creating the Component config entity), in case B ("site builder-provided") it'd happen manually (requires editing a created Component config entity).
      After that initial step, the config entity would carry a dependency on the MediaType config entity, the Media content entity UUID, and the base64-encoded image data. Upon saving this Component config entity, that'd result in the Media entity with that UUID being recreated if it doesn't already exist.

      Which is exactly what we already PoC'd many months ago, and which last surfaced in September:

      FYI: we'll need something like #6 too for allowing, among others:
      an SDC will need to be able to define a default image, per 📌 [SPIKE] Comprehensive plan for integrating with SDC Active
      that image will have be a file inside the SDC's directory
      … which means that it won't exist as a File or a Media entity
      … which means its must be auto-created.

      This is exactly why @f.mazeikis in his work on 📌 Auto-create/update Component config entities for all discovered SDCs that meet XB's minimum criteria Fixed exempted any SDC whose StorablePropShape ended up using an EntityReferenceItem (entity reference field type) or subclass.

      The infrastructure I PoC'd would allow that to start working, and would allow a default image to be specified by the SDC and correctly tracked in the Component config entity, when combined with @f.mazeikis' prior work on auto-encoding content entity dependencies into the config entity and auto-restoring upon config import.

      — yours truly at #3473336-8: The UI only handles resolved props values, hence complex widgets do not show the current value in props form (e.g. image with Media Library Widget)

  • 🇫🇮Finland lauriii Finland

    I don't really understand how we've ended up forming an assumption that the default images need to be added to the media library? That doesn't really seem like the expected user experience. Let's say, I'm using Acquia DAM for managing our media assets, and I download a community component which comes with a default image. I certainly wouldn't expect the default image to appear in the DAM. 🤔

  • 🇬🇧United Kingdom longwave UK

    Yeah I don't think an SDC-provided default should end up in the media library; it's not part of the site's media and you would never want to really select it nor should you have the ability to change it directly, it's just a fallback.

    But a site-builder specified default probably *should* end up in the media library as described in #26, if it's configured by editing the Component config entity?

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    @lauriii FYI we previously discussed SDCs providing default images in the context of 📌 [SPIKE] Comprehensive plan for integrating with SDC Active months ago.

    Note that an SDC currently cannot provide a valid default image, because an image URI must be either:

    • an actually working http:// URI
    • an actually working https:// URI
    • an actually working root-relative http:// URI

    … plus it must NOT be some random image hosting service: it MUST be powered by the current site's domain or a DAM service it integrates with.

    IOW: the sole example of this in the XB codebase itself is in need of an overhaul:

        image:
          title: 'Image'
          $ref: json-schema-definitions://experience_builder.module/image
          type: object
          examples:
            - src: /sites/default/files/600x400.png
              alt: 'Boring placeholder'
              width: 600
              height: 400
    

    👆 that is a default value not provided by the SDC (hence 📌 [SPIKE] Comprehensive plan for integrating with SDC Active ), nor is it absolute, nor is it guaranteed to be root-relative.

    This default value only works thanks to experience_builder_install(). Except when it doesn't. Which is what is being reported here! 😅

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Discussed this with @longwave and @effulgentsia. We didn't get to a resolution.

    Here's the scenario that forces us to import/create the SDC/"code component"-specified default image as a File entity (if using the image field type for the "image" prop shape), a Media entity (if the media library is installed) or a DAM (if such a module is installed):

    • I have a "Author" SDC, with 2 required props: profile_pic + bio.
    • To ensure instantaneous previews of XB components, XB imposes a rule on SDCs to be XB-eligible: all required props MUST provide >=1 example; the first example will be used as the default.
    • When I place a component in XB, powered by the"Author" SDC, I must be able to save it immediately after placing it. This WILL result in an image file/image media/DAM reference being stored.
    • Therefore it is necessary for the SDC's default image to be created as the "kind of thing" any user-provided image would be stored (file/media/DAM).
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Sounds like @larowlan also has been thinking about this:

    Once that is in, we will need default values to cover both. e.g. If we've got an image reference, the default value of the hydrated props might be a URL, alt tag and width and height, but the stored value would be the media or file ID.

    — @larowlan, January 14, 2025, #3493943-7: [PP-1] Split default values into resolved and raw

    I'm not sure how he imagines making the SDC-provided default image into a resolveable URL though.

    But this is exactly why I wrote #20: it all is connected. And the more small pieces of 🐛 Some components cannot be used in XB because UI prevents SDC props being named `name` Active are solved, the easier it becomes to reason about this. ( 📌 Move clientside assumptions about prop form data shape into a series of prop specific transforms Active just landed!)

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Related: Feature request: Ability to specify minimum image resolution for an image upload Active — many image-consuming SDCs would likely not provide their own default images, but would rather instead have generic images generated that show the maximum resolution?

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    The root of the issue here is that we map the image property to an entity reference field of type media, but set a default value that is a hard-coded URL - suggesting that in fact this could/should be a URI field. But we want to use the media library to edit the values. So we're in a state where the component has a default value that doesn't match the data shape (field type) we've specified. We work around this with ::findTargetForProps but this is a temporary workaround (And commented as such).

    In HEAD we try to reconcile this by at least ensuring that a media entity exists with the given default image URL - but as discussed in this issue, that isn't working in all cases.

    We're trying to resolve this on multiple fronts.

    - 📌 Support server side massage and validation of component prop form values Active
    - 📌 Split model values into resolved and raw Active

    However those are both very tricky. I'll have a think about what we can do in the meantime

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    Came here to post a reference to 📌 Update XB's `image` SDC to comply with best practices, and document those best practices Needs review and then realised Wim already had 😀

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    I think we also have a disconnect in our components API with what we store in the config entity

    Here's what's in the components api for the default value for the image component - this is taken from the example in the component.yml file.

    But here's whats in the config entity:

    uuid: 0202d62a-193a-4129-ae3a-b7c8f742f743
    langcode: en
    status: true
    dependencies:
      module:
        - media_library
    label: Image
    id: sdc.experience_builder.image
    source: sdc
    category: Other
    settings:
      plugin_id: 'experience_builder:image'
      prop_field_definitions:
        image:
          field_type: entity_reference
          field_storage_settings:
            target_type: media
          field_instance_settings:
            handler: 'default:media'
            handler_settings:
              target_bundles:
                image: image
          field_widget: media_library_widget
          default_value: {  } # 👈️👈️👈️
          expression: 'ℹ︎entity_reference␟{src↝entity␜␜entity:media:image␝field_media_image␞␟entity␜␜entity:file␝uri␞␟url,alt↝entity␜␜entity:media:image␝field_media_image␞␟alt,width↝entity␜␜entity:media:image␝field_media_image␞␟width,height↝entity␜␜entity:media:image␝field_media_image␞␟height}'
    
    

    And this is because \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getClientSideInfo special cases the a prop with $ref 'json-schema-definitions://experience_builder.module/image'

    And I also think that reveals a possible solution for us here as follows:

    • We already have \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getPropsForComponentPlugin which is called from updateConfigEntity and createConfigEntity.
    • Currently in that code we're bailing out if the prop shape maps to an entity-reference field BUT perhaps we shouldn't - perhaps we should add support in there for looking at the default image referenced in the $ref and importing it into a media entity AND then setting a default value that includes the target ID
    • Remove the special casing of 'json-schema-definitions://experience_builder.module/image' in \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getClientSideInfo
    • This also allows us to manage config dependencies, as we can make a reference from the component config entity to the media entity which will mean \Drupal\Core\Config\ConfigEvents::IMPORT_MISSING_CONTENT gets fired on a config import
    • We then make use of an additional key in the source plugin settings for SDC/Code components to store a base64 encoded version of any default images for deployment sake and add an event listener for \Drupal\Core\Config\ConfigEvents::IMPORT_MISSING_CONTENT to take care of creating the missing media entity in a deployment based on these settings
    • Then our default value would include target_id and this would get us closer to removing findTargetForProps too

    If this seems like a reasonable idea I can do a spike on it.

    Most of this seems to have todo's in the code so it might be time to pay down these debts

  • 🇫🇮Finland lauriii Finland

    Is there really not a solution that wouldn't involve importing images to media library? As I already pointed out in #27, I don't think adding default images from components to the media library is part of the ideal experience.

    #29 is pointing out that the limitation is that the image URI must be either:

    • an actually working http:// URI
    • an actually working https:// URI
    • an actually working root-relative http:// URI

    Could we not then allow the default value to remain either:

    • an actually working http:// URI
    • an actually working https:// URI
    • an actually working component-relative http:// URI

    Then we would transform the component-relative URI to a root-relative URI before it actually gets rendered.

    … plus it must NOT be some random image hosting service: it MUST be powered by the current site's domain or a DAM service it integrates with.

    Where does this requirement come from? Why can we not allow images coming from image hosting services?

  • 🇫🇮Finland lauriii Finland

    But a site-builder specified default probably *should* end up in the media library as described in #26, if it's configured by editing the Component config entity?

    I think that's right, at least in the site where the component was built. I would imagine that Defining props for code components Active would be using media / image upload for uploading default images and therefore they would end up in the media library. I think in the case of a config deployment, it would make sense to create the media or file entity.

    However, in future we may allow exports and imports of a component. In this scenario, it might not make sense anymore to add the image to the media library because the image may not have anything to do with the site where it gets imported.

    It looks like https://wordpress.org/patterns/ is using remote URLs for the images so that the component can be simply copy pasted.

  • 🇬🇧United Kingdom longwave UK

    In #27 @lauriii states that the default image should not end up in the site's media library. It's not even guaranteed that media library will be used for media storage in the Acquia DAM case.

    Instead of trying to store the default image as a media entity I'm wondering if it would be worthwhile having a custom image controller that renders default images for any component. This image can still be stored as base64 in the config entity but it otherwise doesn't need to exist on disk or as a content entity. If the prop value is empty, then we output the URL that points to this custom controller.

    Optional images are still awkward though. If optional images have a default example, what do you use to tell the difference between the user wanting no image at all and the default image to be shown?

  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    Yep, agreed with #33.

    I'd prefer to have @larowlan continue on 📌 Support server side massage and validation of component prop form values Active (reviewed in-depth today) and other 🐛 Some components cannot be used in XB because UI prevents SDC props being named `name` Active child issues, because it'll make the correct solution here clearer.

    The code @larowlan is quoting in #35 is being touched upon by 📌 Maintain a per-component set of prop expressions/sources Active — which is yet another child issue of #3467954.

    Will follow through in-depth here tomorrow, with hopefully #3467954 committed and #3499550 close. 🤞

    P.S.: Quoting \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::getClientSideInfo() (which was moved multiple times but dates back to the very early XB days):

          // @todo Add support for default images in SDCs: /components/image/image.component.yml. (And entity references in general.)
          // @see \Drupal\experience_builder\Entity\Component::getDefaultsForComponentPlugin
    
  • 🇫🇮Finland lauriii Finland

    Discussed this at length with @longwave, @wim leers, and @effulgentsia. We essentially identified four scenarios that we need to support and whether we'd expect the default images to appear in the media library:

    In order to accomplish this, we'll add support for scenario 1. by using the JSON Schema default annotation.. This would essentially allow specifying a default image which would not show up in the form, but would be loaded as the default value in the field.

    Scenario 4. is a future consideration because we don't have the option to export code components outside of regular config currently. We can fallback to the behavior from scenario 3. until then.

    In future, we may consider adding support for JSON Schema examples values for images. This would require being able to display a non-media library in the field widget. This depends on finishing designs for the media widget.

  • 🇦🇺Australia larowlan 🇦🇺🏝.au GMT+10

    In 📌 Support server side massage and validation of component prop form values Active I've managed to do away with ::findTargetForProps once the user selects something from the media library

    However I've had to retain it for the use-case when the user adds a new image component because the default value is invalid

    This reinforces my comment above

    The root of the issue here is that we map the image property to an entity reference field of type media, but set a default value that is a hard-coded URL - suggesting that in fact this could/should be a URI field. But we want to use the media library to edit the values. So we're in a state where the component has a default value that doesn't match the data shape (field type) we've specified

    I think until we have a way to reconcile those two we're at an impasse. Either we're storing a media reference OR we're storing a URL.

    And perhaps that points to us needing a custom field type - a compound field that is an ER with additional columns/properties for src/alt/width/height.

Production build 0.71.5 2024