Problem/Motivation
When using a layout for a node that has content moderation enabled, the form for moderating content is output outside of the layout, which is an issue from a theming perspective if the intent was to allow the layout to control where wrappers are placed.
Layout Builder allows manipulating all fields of an entity by providing a block for each.
However, it ignores all "extra fields" defined by hook_entity_extra_field_info()
Before
Moderation state is added at the top of the content
Moderation state is not present in the Layout Builder UI
After
Moderation state is now available in the Layout Builder UI
Moderation state placement can now be adjusted
Proposed resolution
Write a block plugin deriver that processes extra fields
Figure out a way to render them (since they are usually rendered in hook_entity_view_alter()
)
Keep an eye on https://www.drupal.org/project/extra_field as a possible solution
Remaining tasks
N/A
User interface changes
N/A
API changes
N/A
Data model changes
N/A
Comment | File | Size | Author |
---|---|---|---|
#77 | error1.png | 8.71 KB | CulacovPavel |
#63 | 2953656-extra_field-63.patch | 29.71 KB | tedbow |
#63 | interdiff-60-63.txt | 898 bytes | tedbow |
Comments
Comment #2
sarahjean CreditAttribution: sarahjean at Acquia commentedComment #3
tim.plunkettComment #4
tedbowHere a start with basically just the deriver.
Still trying figure this out but it seems tricky.
The node module seems to add its extra
links
field in\Drupal\node\NodeViewBuilder::buildComponents()
but yes others add inhook_entity_view_alter
I tried getting the entity view builder.
But this causes recursion because the entity view is already being built at this point.
Comment #5
tedbowTo avoid the recursion I did the hacky
Not sure how to avoid this.
I also implementing
hook_entity_view_alter
otherwise the other modules implementation will render the extra fields too.@todo Either make sure our implementation of
hook_entity_view_alter
runs last or just add #preRender callback inhook_entity_view_alter
which will remove extra fields from render.Comment #6
tedbowOk after talking with @tim.plunkett here is much simpler implementation.
Now
\Drupal\layout_builder\Plugin\Block\ExtraFieldBlock::build
simply adds a placeholder and then inlayout_builder_entity_view_alter
the placeholder is replaced by the built extra field. Then the extra field in the main render array is unset.@todo We should probably remove the extra field has no output. Like the node links for a logged in user.
Comment #7
tedbowThis patch does:
\Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::setComponent
BC logic to handle extra fields. This allows extra fields that were configured in 'field_layout' to work in layout builder.hook_builder_entity_view_alter
is not fired in the builder so the actual extra fields will not be generated.Comment #8
tedbowComment #9
tedbowNow with tests!
\Drupal\Core\Entity\EntityDisplayBase::init
is callingsetComponent
for all field definition components but not for the extra fields so the BC logic wasn't being handled for extra fields.Tests were breaking for because of user extra fields because user has no bundle type.
This handles fieldable entity types that don't have bundles.
The regular not links don't show up usually so to make testing easier I added a test link that will always render.
Comment #11
tedbowInstead of using the node links I changed the test module to just have its own extra field.
This doesn't fix the cache test failures. Not sure on those yet.
Comment #13
tedbowOk think I have track down the problem to the fact that the 'member_for' extra field being displayed on the page.
I have commented out this extra field and I think the layout builder tests will pass.
Of course this cause the user tests will fail
Comment #14
mglamanThis should be it. I tried the patch to remove "Member for" but it didn't disappear.
Comment #16
tedbow@mglaman I have tried this no existing site.
The patch in #13 was just to prove that test failures for layout builder are related the member_for extra field from the user field.
Before this issue the layout_builder module moves all of the regular fields to the new display.
This issue tries to also move the extra fields. I think for the tests the concerns are links and 'member_for' because node and user are enabled.
The changes to this test method prove that extra field on nodes are actually being rendered.
I think is safe to assume that the "member_for" extra field from user is also being rendered. Because removing it causes the test to fail.
Comment #17
tim.plunkettFor me the problem was the ::init() call. It was running over and over, and shouldn't have needed to.
Instead I added a post_update hook, and test coverage for it.
Additionally I cleaned up some other things I noticed in the patch, no runtime changes.
EDIT: The interdiff is from #11
Comment #19
tedbowIn #17
\Drupal\Tests\layout_builder\Functional\LayoutBuilderTest::testLayoutBuilderUi
fails because it only fixes the extra fields for existing EntityDisplay entities. In the test a new bundle is created after layout builder is installed.I have manually tested this and it works for bundles that exist at the time that Layout Builder is installed but not for 1 created after.
Comment #20
tedbowWill self review in next comment
Comment #21
tedbowRemoved this update function because it only solves it for displays that are present on module install.
Make sure this logic only happens for new displays. Added extra testing to make sure this happens.
Not positive about this. Wonder if removing this would cause cache not be cleared if label is on block config is changed?
This is the fix that actually fixes
\Drupal\Tests\layout_builder\Functional\LayoutSectionTest::testLayoutSectionFormatter
I think the problem was that the cache was applied to
$block['content']
applying it to$block
fixes the cache problems.I noticed in manual testing that extra field was being added again every time the display was saved. While this test passed in #11 it was checking this.
The change to
\Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::init
in this patch fixes this.Comment #22
tedbow#20 failed on
\Drupal\Tests\layout_builder\Functional\Update\ExtraFieldUpdatePathTest
Added back
layout_builder_post_update_add_extra_fields()
that @tim.plunkett added in #17 to handle existing displays.Comment #24
tim.plunkettI found the bug that was necessitating that
init()
addition.#2925657: EntityDisplayBase::init() should use ::setComponent() for fields only fixed init() for full fields and ignored extra fields.
This change allows us to not need the custom handling.
Also, I restored the
CacheableMetadata::createFromObject($this)->applyTo($build);
to match FieldBlock.The true bug was fixed by the interdiff in #20:
Also fixing an indentation error added in #22.
Comment #25
tim.plunkettBlocked by #2956202: EntityDisplayBase::init() should use ::setComponent() for extra fields as that fix is not directly within LB.
Comment #27
tim.plunkettPostponed on the above fix.
Comment #28
tim.plunkettThought that'd be a faster way to get this in. Whoops!
The blocker was committed to 8.6
Comment #30
tim.plunkettHuh. Interesting.
label_display
is a string, thanks to \Drupal\Core\Block\BlockPluginInterface::BLOCK_LABEL_VISIBLE.But not consistently within core! Only in tests that trigger a schema check.
Comment #31
tim.plunkettBundles are not always config entities. Using the service to retrieve them.
Comment #33
tim.plunkettRolled against a stale local 8.6, whoops.
Comment #34
mglamanThis looks great and has unblocked using embedded node teasers with Layout Builder. My one nit was about removing
layout_builder_entity_view_alter
and havingLayoutEntityDisplay
do all the logic. But, as tim.plunkett pointed out, Core itself is invoking that alter and Core is attaching extra fields through it.Comment #35
tim.plunkettAdded screenshots
Comment #36
xjmThanks @tim.plunkett for the screenshots.
It'd probably be good for the hook implementation here to some have some documentation of "what/why".
Hmm, this seems brittle. Why do we get to assume we get to be the last one? What happens if another module does the same thing? Content Translation also has a hook implementation that does essentially this, so it seems kind of like a "I get to run last" cold war, or a semi-deterministic/unpredictable situation in which the module that implement-alters last to be last is the one that actually gets to be last.
What happens with both LB and CT? (I'm making an assumption here that we want content layout stuff to be translatable.) At the least, I think we should have a test that uses this together with content translation to explicitly document what we expect to happen in that case.
CT is in fact the only other
hook_module_implements_alter()
in core and we only went that route because CT is "special". :) So that's another thing that raises questions for me.Meanwhile, a nit:
hook_entity_view_alter()
needs parens.Don't we already have a different block that does that? This one is specifically for the extra fields, right? It'd be good to @see the normal field block and mention its relationship to this.
I think it's also worth directly saying that this is for extra fields as provided by
hook_entity_extra_field_info()
.Nit: This sentence should have articles (a placeholder, the entity view).
Whoa. I've never seen the underscore-prefixed render array build keys before. Do we have other examples of this?
Also, nitpick: I think
placeholder
should be one word (notplace_holder
).Normally we use
static::
and notself::
; is there a particular reason we're usingself::
here?Am I internal because LB is experimental or because other reasons? Would be good to document here one way or the other. (We should do that with any
@internal
, really.)Huh, I feel like I've either used or written a method much like this recently. Might be worth a followup to actually add this to the BTB API. (Not relevant/blocking here, of course; just a thought.)
An update test even. Nice work! :)
Comment #37
tedbow@xjm thanks for the review.
1. Added what/way.
2.
This is hard to get around because modules are suppose to add the rendered output for extra fields in
hook_ entity_view_alter
. But that also means that we would only affected if another module did the same thing if they were also usinghook_ entity_view_alter
to add extra fields. There would many, most uses of thehook_ entity_view_alter
that would not matter if they ran after LB's version.Even though LB and CT both implement
hook_module_implements_alter
they react moving implementations for different hooksLB,
entity_view_alter
and CTentity_type_alter
,entity_bundle_info_alter
. so they would have no effect on each other.Looking at
\Drupal\Core\Extension\ModuleHandler::buildImplementationInfo()
This hook is called once for each hook with their own list of$implementations
so these are totally separate arrays.fixed nit
3. fixed.
4. fixed
5. I don't think we need this. Fixed.
6. fixed
7.
Many class in LB have
I don't see anything policy about derivers here https://www.drupal.org/core/d8-bc-policy
I added in
Though I don't see other classes in core specify why they are internal, so do we really need this.
8. This does seem familiar but I can't find it.
10. not sure
Comment #38
phenaproximaI don't think hook names should be quoted; this should just say hook_entity_extra_field_info(), same with all other functions mentioned in this comment.
Supernit: 'implementations' is indented one space too far.
Can we have a comma after "provide"?
Can we say "before this point in..."?
"to the end of the list"
There is no need to keep $field_manager in its own variable.
$extra_field is never used, so let's just loop over array_keys($extra_fields['display']) instead.
This can be a single ternary line.
Ideally, we should have rock-solid, high-level test coverage of this entire thing. We should be damn sure that our view hook is being properly invoked for the "variants" of hook_entity_view_alter(), e.g. hook_node_view_alter(). I say this because I've been personally burned by the mind-bending logic of ModuleHandler::buildImplementationInfo(), which does a rather strange dance for hooks with varying names.
This could benefit from some inline comments explaining what it's doing and why.
I know it already existed, but this could use a comment.
The function name should have () after it, and not be quoted.
Maybe throw an \InvalidArgumentException() here if $field_name is empty?
Does this need to be translatable?
"Replaces"
This description should probably say something like "The built render array for the elements" or similar.
If this is a render array, the description should say so.
===
Nit: This can be condensed to one line.
Should this be here?
Comment #39
dqdGreat "cooking" here! 1+ Did some "kitchen-cleaning" along #38. :)
With reference to the last #slack Layout Builder Initiative meeting on 26th of June with @timplunkett and @tedbow the points 1, 2, 3, 4, 5, 7, 12, 14, 15, 16, 17 made by @phenaproxima have been addressed in my "cleaning". Points not listed above need some more discussion. No time to be creative at the moment over here, so just some notes:
if (!empty($extra_fields['display'])) {
right before?Comment #40
dqdComment #42
dqd$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
seems not to be enough in mygit diff >
encoding issue of the last days. It's utf8 now, but still with BOM encoding ...Note for myself: Always check encoding again before uploading.
Comment #43
dqdComment #44
dqdComment #46
johnwebdev CreditAttribution: johnwebdev at Kodamera AB commented@diqidoq Could you provide a interdiff from patch in #37?
Comment #47
tim.plunkettThe patches since #37 are missing the entire plugin, its deriver, and the test coverage.
Comment #48
dqdThat.was.strange.
OK, sorry folks. My dev enviroment was messed up here. Re-roll, but let me test step by step: I will additionally leave out 7) yet (revert it from #39) and will come back to it later.
So: 1-5 and 12(++), 14, 15, 16, 17, 18 of review #38 are addressed in this first step. And this time with an interdiff! :)
Comment #49
tim.plunkettFixed the double translation, and the rest of the function references in docblocks.
Comment #50
phenaproximaLooks good! The logic is bit dense in places, but that's par for the course when dealing with many entity types and fields.
Need () after layout_builder_module_implements_alter.
Nit: This can be a single line.
Maybe this should use $entity_type->getPluralLabel() instead?
Should be assertSame().
Comment #51
phenaproximaComment #52
tim.plunkett3) needs to stay, as it is the category used for all the other fields
Fixed the rest.
Comment #53
Dinesh18 CreditAttribution: Dinesh18 as a volunteer commentedManually reviewed the fixes mentioned in #50.
2953656-extra_field-52.patch looks good to me. +1 to RTBC
Comment #54
phenaproximaHey, it's a patented phenaproxima Nitpick Review(tm)!
We don't need the !empty() check. From my reading of EntityFieldManager::getExtraFields(), if I'm interpreting it correctly, $extra_fields['display'] will be defaulted to an empty array, so we can just foreach over that and remove a level of nesting from this function.
This can be changed to ternary syntax.
===
Can the doc comment explain *why* this needs to be done? It looks like it's just saving already-existing configuration.
Why do the components need to be sorted? Can we at least get a comment on this?
This feels a little convoluted and hard-to-read. Maybe we can refactor it like so, to avoid expanding the size of that if statement:
Should we also @see layout_builder_entity_view_alter()? Also, "provide" should be "provided".
Can we assert() that $field_name is not empty?
Are we sure we don't want to cache this result?
No need for this; if $extra_fields['display'] is empty, looping over it will be a no-op.
Do we need the $this->t() call? I think entity type annotations already use @Translation for the label.
Nitpick: We probably don't want to encourage the use of the constructor with ContextDefinition (I think the ultimate dream is to make it protected, in fact), so let's use ::create() here so that we can also chain the subsequent calls to setLabel() and addConstraint(). Also, we don't need to explicitly mark it required, since that's the default.
Comment #56
tedbow1.
Actually looking at it
EntityFieldManager::getExtraFields()
it looks like it will only be an empty array if the key is not set by one of the of the hooks. So it could beFALSE
orNULL
depending on how a hook is implemented. So I think it is not a bad idea to leave it.2. fixed
3. fixed
4. Expanded the comment to my understanding.
One thing I didn't understand about logic in
layout_builder_post_update_add_extra_fields
. Since only\Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::setComponent()
was updated and not other instance ofsetComponent()
do we really need to call it if!$display instanceof LayoutBuilderEntityViewDisplay
?5. Added a comment. @tim.plunkett should probably double check my reasoning.
6. I shortened the if statement. I also notice that we were initializing
$configuration
as an empty array but then adding certain elements to the array in both cases. So I changed it to initialize the array with these values.7. Fixed "provided" we already have a @see to
layout_builder_entity_view_alter
inbuild()
near the placeholder logic.8. added
9. added caching
10. Since the value is providing by hook implementations which may set it as empty but not an array I don't see the harm in leaving this.
11. I think you are right. fixed
12. Changing to
EntityContextDefinition::fromEntityType()
and removing the todo to #2932462: Add EntityContextDefinition for the 80% use case. We don't need to set the label now.Comment #57
tim.plunkett4) Once the module is installed, it will always be the right class. If this lands after the opt-in patch lands, we'll want to check
::isEnabled()
. So we're fine for now.Interdiff review:
' vs ` around setComponent()
Usually we don't quote method names like that. Either
::setComponent()
or the fullClassName::setComponent()
I don't think the first sentence is needed (and also not 100% relevant/correct). The second one is fine by itself
Nice!
Comment #58
tedbowAddressing all in #57
Comment #59
phenaproximaThis is very close.
This is a bit obscure; it's not very clear why we are unsetting $build[$field_name]. A comment would be beneficial here.
'::setComponent()' should not be quoted, nor should it have the :: prefix.
Nit: Can we rephrase the comment? "Sort the components to avoid them being reordered by setComponent()."
Why are we doing this?
In FieldBlock, this is FALSE -- which is correct?
The array_flip() call is leaking implementation details of FieldBlock. I think we should add a new method on FieldBlock (
static::getFormatterOptions()
?) which does this work, so as to keep all FieldBlock-relevant details confined to FieldBlock."confirms" --> "confirm"
I never knew about substr_count()! Thanks :)
Comment #60
tedbow1. added comment.
2. fixed
3. fixed
4. Added comment
5. Changing to FALSE. i think this is correct after look at other time this is set in core.
6. I think is BC code, are those settings really just field options? No all formatters?
7. fixed.
8. 👍
Comment #62
tim.plunkettThis must be an integer!
Also changing it to anything else is way out of scope.
Comment #63
tedbowAlas, fixed
Comment #64
phenaproximaI'm satisfied. Let's do it.
Comment #66
alexpottDoes this need to be run on all displays? Or only displays that implement LayoutEntityDisplayInterface? Maybe I'm wrong looking at the update test. But I noticed that
layout_builder_entity_view_alter()
limited to displays with this interface.I think we should use
mb_substr_count()
just in case later we add unicode to something... ah looking atpageTextContains()
we maybe want to keep things consistent... so something like:Also maybe a follow up to add this to
\Drupal\Tests\WebAssert
as this looks useful.Comment #67
tim.plunkett1)
All displays implement that once the module is installed, due to how entity type class switching works.
The only difference here vs in layout_builder_entity_view_alter() is for typehinting purposes. The alter could have also used a /** @var */ comment instead for the same effect...
I think this is fine as-is
2)
I could have sworn there was an issue to add something like pageTextContainsOnce(), @tedbow would know I'm sure.
Comment #68
alexpottI opened #2987478: Add WebAssert::pageTextContainsOnce() as a follow-up.
Comment #69
alexpottAdding review and issue management credit.
Comment #70
alexpottCommitted and pushed 045fcb5d44 to 8.7.x and 09530d7ea8 to 8.6.x. Thanks!
Remove used uses on commit.
Comment #73
andypostComment #74
tedbow@alexpott thanks!
Comment #76
dqdAwesome, thanks @all.
Comment #77
CulacovPavel CreditAttribution: CulacovPavel commentedThanks work :)
Comment #78
AnybodyIf someone else should also run into issues with layout_builder + extra fields now (e.g. by hook or via extra_fields / extra_fields_plus module), I found two follow-up issues you should check and help to fix:
Comment #79
AnybodyHi all here,
sorry to disturb here, but I'd like to ask if someone of you could help to answer, if adding a third_party_settings form to extra_fields in layout_builder is logically / technically possible in the current implementation after this patch. I tried to find that out but honestly I'm not sure if that's a follow-up and currently not cleanly possible or if I'm just to stupid to find the required information.
Background: Extra Field Settings Provider ("extra_fields_plus") "provides an interface and the extra field base plugins with editable display settings.". That works great and clean, but doesn't work with layout_builder as documented in #3069861: [3.x] Settings do not show up in layout_builder. The only patch idea there looks a bit dirty to me, so I tried to find a better solution, but failed. I guess with or without this module it makes sense to be able to allow third_party_settings (forms) for extra_fields to configure output.
I would very much appreciate the help of someone who helped to create the patch and has a better understanding of the logics in layout_builder combined with extra_field and third_party_settings.
I guess we need hook_field_formatter_third_party_settings_form to work on extra_fields?
Thank you very much in advance.
Of course I'll create a follow-up if it's not possible yet and makes sense to you, but decided to ask here first, because I think it's very close related to this fix.
Comment #80
Anybody@tim.plunkett, @phenaproxima, @tedbow, sorry to address you directly. I know you have a lot to do and my English is not good enough to express how much I appreciate your work! I really feel bad to ask you that and I hope this question is important and general enough to ask you for a short look and feedback on #79.
I did a lot of research, but couldn't find a clean solution to solve this general problem in contrib. If it is possible and you have a short feedback on that without investing much of your valuable time, could you perhaps have a short look at the linked issue and reply there or here for general documentation / state / plan?
I guess there are other important modules which have a similar demand... sorry in advance. I guess there are not many people as deep in this topic as you are. Feel free to advise me to create a follow-up documentation issue for that, if it makes sense for you ;)
Thank you very, very much in advance!