Problem/Motivation

Entity browser will currently allow widgets to select just any entity. If it is an entity it can be selected. We need a mechanism that will allow users to define conditions that entities need to meet in order to be selected with a given entity browser. Examples of this conditions would be:

  • Entity must be of type A
  • Entity must belong to bundles X,Y,Z
  • Entity must be older than 1 year
  • At most N entities can be selected
  • At least M entities must be selected
  • Entity must have field "foo" and it's value needs to be "bar"

This seems like a very hard problem. In reality we'll need basic checks most of the time (entity, type, bundle, cardinality), but it would be nice to come with a system that would be extensible enough to allow people to do nice things in the future.

It would be great if we could use APIs that come with core.

Proposed resolution

There are many things to figure out here, but I think there are two main issues to resolve:

Where are conditions enforced?

  1. Widgets: leave responsibility entirely on entity browser widgets which need to be sure entities they are selecting meet all conditions. This would be optimal from UX point of view, but is very hard to do it right. Imagine a View widget that would need to check entity type it operates on, make sure all filters to meet conditions are set on the view, ...
  2. Before entity is added to current selection: This happens after an entity has been selected. We check all conditions and let through only those that meet all conditions. Easier to implement but not very nice from UX perspective (imagine how frustrating it is after you created something and EB rejects it).
  3. Combination of both: Propagate conditions to widgets which do their best to meet them. Once entities are selected check them once again and reject the ones that might not meet all the conditions.

What to use to define conditions?

  • Core Contexts/Conditions: Currently used with blocks to limit visibility. It seems that Context concept won't fit in our use-case very well.
  • Core Constraints: Allows us to re-use existing constraints and to write new ones that work better for us. It will most likely be hard for widgets to check for conditions before entities are created/selected (optimal from UX perspective).
  • Rules: Might be useful? We can probably implement this in a way that allows us to plug rules in the future.
  • Custom solution: Most flexible but definitely not desired.

Remaining tasks

  • brainstorm and decide on architecture
  • write patch
  • write test coverage
  • review
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

slashrsm’s picture

Issue tags: +Media Initiative, +sprint
slashrsm’s picture

Issue tags: -sprint +D8Media
slashrsm’s picture

Title: Integrate with conditions » Support defining conditions
Issue summary: View changes
slashrsm’s picture

Issue summary: View changes
slashrsm’s picture

Issue summary: View changes
Primsi’s picture

A gave this a thought and imo the most elegant solution would be working constraints that we use to validate on widget submission.

Let's try that on an example workflow:

  1. When a user creates a new EB field he should be able to define validation criteria. This means that we would have to construct an interface where the user could chose from a list of available constraints and then define constraint parameters. We would have to pre-define which constraints are applicable, maybe by creating a wrapper plugin around constraints and use this plugin to define the previously mentioned configuration form too. This would be saved in this field configuration.
  2. The field configuration part is done and the user goes to create a new entity, opens the EB widget and starts creating the 'child' entity
  3. When finished submits the form. On form validate we check the configuration of the parent field (It would help a lot to have the parent form available) and run the constraints.
  4. How exactly validation is done wold have to take into account what the widget is doing, so I presume this would have to be widget's responsibility
Primsi’s picture

Status: Active » Needs review

Initial stab at this: https://github.com/drupal-media/entity_browser/pull/117/files

Still needs:
- a way to specify conditions
- cleanup
- tests

Berdir’s picture

One important thing that we discussed is how we send those conditions/requirements to the entity browser.

My suggestion was to use the same as the EntityAutocomplete element. Hash the settings, store them by that hash in key value then pass along the hash in the URL or so.

Primsi’s picture

Finally found time to continue working on this. Only Upload widget uses this validation for now- views widget runs it's own validation thing.

Also, as the patch stands now, it would require other widgets to follow.

slashrsm’s picture

Status: Needs review » Needs work

Thanks! This looks very good. Few comments on pull. Also needs reroll and @file blocks can be removed.

samuel.mortenson’s picture

Here's a vanilla re-base on top of 8.x-1.x, no notes addressed and no manual testing yet.

samuel.mortenson’s picture

FileSize
34.23 KB

Last patch was no good, no idea why.

samuel.mortenson’s picture

So after (visually) reviewing the current code and reading up on Symfony Constraints, I understand that the reason we're adding a new plugin is that we need to mock the typical use of Core's field validation. It's a pretty cool concept! Just need to do cleanup work on comments and inject more dependencies into the validator plugins, then test that it's working.

samuel.mortenson’s picture

FileSize
39.78 KB
16.28 KB

Updated patch which addresses all notes from https://github.com/drupal-media/entity_browser/pull/117 except the note on WidgetValidationBase::setConfiguration "We need to call this function from the constructor and respect default configuration before setting.". Not sure what is required for that.

Moving into manual testing now.

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
41.59 KB
7.54 KB

Fixed fatals, cleaned up more code, and made the Cardinality and EntityType WidgetValidators actually work. I believe this is ready for some form of review.

Status: Needs review » Needs work

The last submitted patch, 15: entity_browser-2366335-15.patch, failed testing.

The last submitted patch, 15: entity_browser-2366335-15.patch, failed testing.

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
41.21 KB

Re-roll.

Status: Needs review » Needs work

The last submitted patch, 18: entity_browser-2366335-18.patch, failed testing.

The last submitted patch, 18: entity_browser-2366335-18.patch, failed testing.

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
34 KB

Another huge re-roll, with a lot of code removed now that we have a generic way to pass information down to the Entity Browser.

Status: Needs review » Needs work

The last submitted patch, 21: entity_browser-2366335-21.patch, failed testing.

The last submitted patch, 21: entity_browser-2366335-21.patch, failed testing.

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
35.11 KB
6.16 KB

Hopefully fixed tests.

samuel.mortenson’s picture

Existing tests pass, writing new test coverage now.

samuel.mortenson’s picture

FileSize
3.26 KB
38.51 KB

Added a new test for the entity_type validator, I think this should be enough coverage for this patch unless we want to test every validator we have individually.

phenaproxima’s picture

Status: Needs review » Needs work
  1. +++ b/entity_browser.api.php
    @@ -62,5 +62,16 @@ function hook_entity_browser_field_widget_display_info_alter(&$field_displays) {
    + * @param $validation_plugins
    

    Needs a type hint.

  2. +++ b/entity_browser.api.php
    @@ -62,5 +62,16 @@ function hook_entity_browser_field_widget_display_info_alter(&$field_displays) {
    +function hook_entity_browser_widget_validation_info_alter(&$validation_plugins) {
    

    Should be type hinted.

  3. +++ b/src/DisplayInterface.php
    @@ -71,4 +73,23 @@ interface DisplayInterface extends PluginInspectionInterface, ConfigurablePlugin
    +   * Gets the selection storage.
    

    I think this should say "Gets the selection storage values." Otherwise it sounds like we're getting the actual storage handler.

  4. +++ b/src/Plugin/EntityBrowser/WidgetValidation/Cardinality.php
    @@ -0,0 +1,45 @@
    +    $max = $options['cardinality'];
    

    IMHO this should be $options['maximum'], for clarity's sake.

  5. +++ b/src/Plugin/EntityBrowser/WidgetValidation/Cardinality.php
    @@ -0,0 +1,45 @@
    +      $message = 'You can not select more than %max Entities.';
    

    s/Entities/entities

  6. +++ b/src/Plugin/EntityBrowser/WidgetValidation/EntityType.php
    @@ -0,0 +1,22 @@
    + * Validates that each passed Entity validates
    

    This sentence doesn't seem complete :)

  7. +++ b/src/WidgetBase.php
    @@ -60,7 +61,14 @@ abstract class WidgetBase extends PluginBase implements WidgetInterface, Contain
    +  private $validationManager;
    

    Should be protected.

  8. +++ b/src/WidgetBase.php
    @@ -189,7 +203,45 @@ abstract class WidgetBase extends PluginBase implements WidgetInterface, Contain
    +    if (in_array('submit', $trigger['#array_parents'])) {
    

    This is quite dangerous, IMHO. It's entirely possible that someone will have an element parent named 'submit', and then things will blow up for them. Can we find a better way of determining whether validation should occur? How about #limit_validation_errors?

  9. +++ b/src/WidgetBase.php
    @@ -189,7 +203,45 @@ abstract class WidgetBase extends PluginBase implements WidgetInterface, Contain
    +        if (!empty($violations)) {
    

    $violations is an object, so it will never be empty. This should be $violations->count() > 0.

  10. +++ b/src/WidgetBase.php
    @@ -189,7 +203,45 @@ abstract class WidgetBase extends PluginBase implements WidgetInterface, Contain
    +      $widget_validator = $this->validationManager->createInstance($validator_id, []);
    

    I'm not sure we need the if check here -- won't the plugin manager throw PluginNotFoundException if the constraint does not exist?

  11. +++ b/src/WidgetInterface.php
    @@ -79,13 +79,27 @@ interface WidgetInterface extends PluginInspectionInterface, ConfigurablePluginI
    +   * @return \Drupal\Core\Entity\ContentEntityInterface[]
    

    Shouldn't this be EntityInterface[]?

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
40.56 KB
6.48 KB

#27.1/#27.2 - It looks like core doesn't use type hints for hook_*_info_alter() implementations, I'm not sure why. I cleaned up the whole api.php file.
#27.3 - Done
#27.4 - We use "cardinality" in a number of locations (entity_browser.common.js, entity_browser_entity_form.module), and so do some dependent contrib modules. I would be open to changing this across the board in a new issue if we choose to also support cardinality minimums.
#27.5/#27.6/#27.7 - Done
#27.8/#27.9 - Nice catches, done
#27.10 - Since the WidgetValidationManager extends DefaultPluginManager, we get the benefits of FallbackPluginManagerInterface and this function will not throw exceptions if a fallback plugin is available. See \Drupal\Component\Plugin\PluginManagerBase::createInstance() for details.
#27.11 - Done

slashrsm’s picture

Status: Needs review » Needs work
  1. +++ b/src/WidgetBase.php
    @@ -189,7 +203,39 @@ abstract class WidgetBase extends PluginBase implements WidgetInterface, Contain
    +  public function prepareEntities(FormStateInterface $form_state) {
    +    return [];
    +  }
    

    Do we need to implement this function in all other widgets too? Can we come up with a sane default implementation. I think that we should declare it as abstract if not. Maybe even make it protected?

  2. +++ b/src/WidgetInterface.php
    @@ -79,13 +79,27 @@ interface WidgetInterface extends PluginInspectionInterface, ConfigurablePluginI
    +   *
    +   * We need this method when we want to validation or perform other operations
    +   * before submit.
    +   *
    

    s/validation/validate

  3. +++ b/src/WidgetInterface.php
    @@ -98,6 +112,20 @@ interface WidgetInterface extends PluginInspectionInterface, ConfigurablePluginI
    +   * @param array $validators
    +   *   Array of additional widget validator ids.
    

    Why additional? These are validators.

  4. +++ b/src/WidgetValidationBase.php
    @@ -0,0 +1,141 @@
    +/**
    + * @file
    + * Contains \Drupal\entity_browser\WidgetValidationBase.
    + */
    

    Can be removed. Applies to the entire patch.

  5. +++ b/src/WidgetValidationBase.php
    @@ -0,0 +1,141 @@
    +      'constraint' => null,
    

    s/null/NULL

Primsi’s picture

#29,1
I think I remember that I every plugin needs to implement it because entities must exist if we want to reference them. We need to load them or create them. I am not sure if we can create a default. See upload widget example in patch.

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
40.83 KB
4.84 KB

This should address all concerns in #29, implementing prepareEntities() for the View widget actually abstracted some logic out of the submit handler, which is good too.

samuel.mortenson’s picture

Status: Needs review » Needs work
samuel.mortenson’s picture

The Views widget implements its own validate() method, so we need to make sure it also calls the validators. I'll manually test as well.

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
430 bytes
41.04 KB

Alright, the Views widget now calls the normal WidgetBase validate() method. I manually verified that cardinality and entity_type work with the Views widget as well.

slashrsm’s picture

FileSize
39.66 KB
7.04 KB

Some last minute refactoring.

  • slashrsm committed b8d656c on 8.x-1.x authored by Primsi
    Issue #2366335 by samuel.mortenson, Primsi, slashrsm, Berdir: Support...
slashrsm’s picture

Status: Needs review » Fixed

Yay! One big step towards beta.

Status: Fixed » Closed (fixed)

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

Gábor Hojtsy’s picture

Issue tags: -Media Initiative