Split out from #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder
Problem/Motivation
When you create a Custom Block entity, a Block Plugin definition is derived and is available anywhere Block Plugin definitions are listed. This makes a lot of sense when Custom Blocks are meant to be re-used, for example you may want a "Logo" Custom Block on many pages in different regions.
However when using Custom Blocks within the Layout Builder the current functionality has many problems:
Performance
When using something like Panels or Layout Builder, Custom Blocks are often only meant to be used once. If you're building out hundreds of pages you could end up with thousands of Custom Blocks and lead to performance issues. This blocks would shown on every place you are able to pick a block including the layout builder and the current block place listing.
Proposed resolution
Add a new base field to Custom Blocks that determines if they are re-usable. If a Custom Block is not re-usable, it should not have a derived Block Plugin definition and should not be visible in the default "Custom Block library" View listings.
Non-reusable blocks access will be determined by:
- Access to the block itself, existing logic and
- Access to a new access dependency which the modules using non-reusable blocks are responsible for setting.
Modules using non-reusable blocks should when possible call $block->setAccessDependency()
before access is called on the block. The dependency is not saved with the block content entity but called when interacting with the block object.
In the case when $block->setAccessDependency()
has not been called before $block->access()
, probably because another module is accessing access, the BlockContentAccessControlHandler
will fire a new BlockContentGetDependencyEvent
that will allow modules to set access the access dependency.
Non-reusable block will be used by the Layout Builder to create inline blocks in #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder.
Remaining tasks
Add testing for changes to \Drupal\block_content\BlockContentAccessControlHandler::checkAccess
User interface changes
None this issue will not exposed the creation of the non-reusable blocks.
Non-reusable blocks will be used in #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder
API changes
BlockContentInterface
will have 3 new methods to deal with the new reusable
base field.
- isReusable
- setReusable
- setNonReusable
Data model changes
New reusable
field on block_content
entity type. The default value would be TRUE so that all existing blocks would function the same as they currently do.
Comment | File | Size | Author |
---|---|---|---|
#107 | 2976334-107.patch | 11.24 KB | tedbow |
#91 | 2976334-91.patch | 72.39 KB | tedbow |
Comments
Comment #2
tedbowComment #3
tedbowComment #5
phenaproxima"Dependee" is a strange word, can we clarify what it means in this context? Right now, it's not clear what is depending on what.
What if no dependee has been defined?
Also known as a "dependency", which might be a better word here. :)
Rather than completely copy the field definition, it might be preferable to directly invoke BlockContent::baseFieldDefinitions() and use what comes back from that.
We should check if $view->isNew() and return if it is, because that would mean that the block_content view had been deleted by the site owner. Also, we don't need to keep $conig_factory in a local variable.
I believe all of these calls can be chained.
Should be "Alters"
I'm not a huge fan of the fact that this hard-codes the name of the data table. Can we get the name of the table from the entity type definition instead?
Also, under what circumstances would the query not be an instance of SelectInterface?
Set to TRUE, not one.
"...function to find..."
Won't the condition either be an array or ConditionInterface? We can probably improve this type hint.
Under what circumstances will $condition['field'] be NULL?
I think we should use ConditionInterface instead.
We should probably check $condition first before recursing downwards, so that we can return earlier.
The is_int() makes me nervous, because it's coupling itself to deep implementation details of the Condition class. Not 100% sure how to work around that, though.
I'm not sure why we'd have this check. Can't we just add AccessDependentInterface to BlockContentInterface, so that all custom blocks implement it? Also, I think this should be \LogicException.
Why is this different from the field definition in the update hook?
I think $this->set() already returns $this, so the second line is redundant.
Comment #6
tedbow@phenaproxima thanks for the review!
1. Changing to "access dependency". Added a longer description in
AccessDependentInterface
.2. Changing the
@return
description to mention it might be NULL.3. changed.
4. Looking at other modules that add new fields and block_content module when it adds fields it seems that the field definitions are always duplicated.
For example
block_content_update_8003
adds new field this way.5. fixed
6. fixed
7. fixed
8. Good call, using
\Drupal::entityTypeManager()->getDefinition('block_content')->getDataTable();
to get the data table.I am not sure but since you can apply any tags to any query then this could an update query or any object that implements
\Drupal\Core\Database\Query\AlterableInterface
so trying to be safe here.9. fixed
10. fixed
11, 13, 14, 15 I want to improve
_block_content_has_reusable_condition
but I want to add test cases toBlockContentEntityReferenceSelectionTest
where the selection has a condition onreusable
that is a conjunction or nested condition.12. When
$condition['#conjunction']
is set.16. I think it is possible to switch out the class that handles the entity. But perhaps we always return AccessForbidden in this case and just log the warning?
Also now that think about it maybe the plugin deriver added in #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder should check that the class for the entity implements
AccessDependentInterface
and log a warning if does not and not make derivatives since they won't work anyways. This is probably an edge case but could happen.17. fixed
18. fixed.
Comment #8
johnwebdev CreditAttribution: johnwebdev commentedGreat work @tedbow!
Why do we need two separate update hooks?
or => ||
Would it make sense to make this optional? i.e. I have a block that should not be re-usable (... or derivable) but is not used by a dependee. I'm not entirely sure when (and if) that would be applicable though.
I think it was asked for a profiling of this change in the earlier issue.
Comment #9
johnwebdev CreditAttribution: johnwebdev commentedComment #10
tedbow@johndevman thanks for the review!
re #8
1. I am not sure about this. I thought that we keep update hooks separate that do 2 different things even if we add them at them at the same time.
This would also keep the descriptions separate when seen when running updates via drush or drupal console. Not sure on this.
2. 😁Well I guess I make a new mistake every way. Fixed
3. Making this required means that any existing custom code that deals BlockContent entities and call ::access() will always get an access forbidden for non-reusable blocks unless it is updated. I think we have to be very careful because we are messing with a stable module.
Here is edge case where I think we want the AccessForbidden by default.
If we don't forbidden access unless
!empty($dependee)
then non-resuable will rendered in this "Related block videos" section.The custom code could be update to only return
$reusable === TRUE
block entities but I think we want to do everything we possibly can to not exposed this now access controlled blocks. Without any existing code having to be updated.4. Yes @eclipseGC wants to make sure if we start to have thousands of non-reusable blocks then we don't start to have the kind of performance problems we would have now if we created thousands of blocks.
From 4) I see that I am still using "dependee" here changing to "dependency".
Also in slack @phenaproxima mentioned
I thought this was good idea and did this in #6. But when I was making the combined patch for #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder I realized this actually cause a problem because the new block plugin
InlineBlockContent
. But\Drupal\Core\Block\BlockPluginInterface::access
is not fromAccessibleInterface
so this causes a problem. Since other interfaces have the concept of access but don't implementAccessibleInterface
and in this will needAccessDependentInterface
this won't work. Changing back.Comment #12
johnwebdev CreditAttribution: johnwebdev commented#10
So the problem with that edge case is that we break that either way, if the block is denied access there will be no block rendered at all. Though I agree, it's probably better to NOT render it, than rendering something that SHOULD not be rendered.
Comment #13
tedbow#12
That is good point. but yes I it much worse, a security issue, to render something that user doesn't have access to.
If I had added here
picks a random 1 that the user has access to that would have been more correct. Because of course even though the block_content module itself has limited access control any other module could implement
hook_ENTITY_TYPE_access
so we should always check access.In that case we wouldn't break this example because it would pick one the user had access to which would filter out the non-reusable blocks, as it would filter out any other blocks the user didn't have access to for any other reason.
Comment #14
tedbowWhoops was using a class for a contrib project in my project 😟.
Fixed. The full console output of the test failure told me the actually error.
Also now excluding the /modules folder in phpstorm which should make this harder to happen again.
Comment #16
samuel.mortensonIs this hook implementation necessary? Entity queries check access by default and the access control handler for block content has been modified to deny access unless an access dependency is present.
Comment #17
tedbowYes this is necessary.
I don't think this is true.
\Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection::buildEntityQuery()
explicitly adds the access tag.in
But then does not call
$entity->access()
so the access control handler is not called.It is up to the module providing the entity type to actually control access like
node_query_node_access_alter()
does.That is why without this hook implementation
\Drupal\Tests\block_content\Kernel\BlockContentEntityReferenceSelectionTest::testReferenceableEntities
will fail and return the non-reusable block.I changed this to
block_content_query_entity_reference_alter()
because it is not really access that we want to alter but rather what should be able to be re-used.The user may actually have access to a non-reusable block in a sense that they have access to the parent entity but that does not mean we want them to be able reference them in a entity reference field.
Because the blocks are non-reusable we should not let them be reused by being referenced in an entity reference value. It just doesn't make sense.
This won't even work because entity reference is not going to call
setDependency()
to so the access would alway be false.What actually happens without this hook.
If a contrib module wanted to make a EntityReferenceSelection plugin that let you reference non-reusable blocks then they could do this by explicitly setting condition on
reusable
in their query.Comment #18
tedbowI meant to update a patch with #17
Here it is
Also change the location of
$data_table = \Drupal::entityTypeManager()->getDefinition('block_content')->getDataTable();
to after the initial checks for performance reasons. Now this will not be called for every 'entity_reference' query.Comment #19
tedbowThis patch:
_block_content_has_reusable_condition()
Among other things re @phenaproxima comment #5
This will always be an array now.
Not doing this anymore.
\Drupal\Tests\block_content\Kernel\BlockContentEntityReferenceSelectionTest::testReferenceableEntities()
by creating\Drupal\block_content_test\Plugin\EntityReferenceSelection\TestSelection
which has a test module and sets condition onreusable
in various ways to make sure the expected referenceable entities are returned.Comment #20
phenaproximaJust about everything I found is a doc comment thing. I think this makes a lot of sense, and it's clean! However, we are going to need a test of the update path.
getAccessDependee() no longer exists.
Should this extend AccessibleInterface?
Can we rephrase the description: "The object upon which access depends"?
Should this method return $this?
Can we rephrase the second sentence for clarity -- something like "The default view may have been replaced by a completely different one with the same ID."
These can be combined:
if (array_key_exists(...) && !_block_content_has_reusable_condition(...)
We can remove a level of nesting if we change this to:
foreach (array_filter($condition, 'is_array')) as $nested_condition)
Can we add a comment explaining why strpos() is called?
Why are we adding $dependency as a cacheable dependency if it's empty?
Can we get away with having BlockContentInterface extend AccessDependentInterface? Or would that be a BC break? (I think such a change might be covered under the 1:1 class/interface rule...?)
O_O Did we not already have coverage for this?!
For clarity: "Tests that only reusable blocks are derived".
Yeah, but this one goes up to 11. ;)
Comment #21
johnwebdev CreditAttribution: johnwebdev commentedBlockContentListBuilder
to make sure they don't expose non-reusable blocks?BlockContentUpdateTest
Comment #22
tedbowre #20 @phenaproxima thanks for the review again!
1. fixed
2. We can't if we use it for
InlineBlockContent
in #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder because it conflicts\Drupal\Core\Block\BlockPluginInterface::access()
with (see #10).3. fixed
4. fixed
5. fixed
6. fixed
7. fixed
8. Added a comment. It is because there could a suffix like "block_content_field_data_2" on nested conditions. I don't think it is enough to test for "reusable" field name because this could on table that is joined also.
9. fixed. Changed to add as a cacheable dependency if it is not empty and
::access()
is called.10. Thanks for asking this! I thought we couldn't but I re-read the policy and found
So yes we can! Any class implementing this interface is suppose to extend
BlockContent
so that the new method won't be problem(explained in the policy).11. Nope, @johndevman added this in the first patch in #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder where this patch started. but also there wasn't really any logic in this deriver before.
12. Fixed
13. Small nit here the "%" makes the joke not work, but will leave, 😜
@johndevman thanks for taking another look
Good point
BlockContentListBuilder
was actually exposing non-reusable blocks! Fixed and added test coverage.Thanks for bring this up again! I made #2978102: Profile 1000s of custom non-reusable blocks for performance so we don't forget.
But also this got me thinking about performance.
In
\Drupal\block_content\Entity\BlockContent::postSave()
and::postDelete()
we call::invalidateBlockPluginCache()
This is because up until now every Custom Block entity corresponds to a Block Plugin. But now reusable blocks don't have an individual plugin.
So we don't always need to do this.
In
::postDelete()
will only need to clear the cache if there is a reusable block.In
::postSave()
we need to clear cache if$this->isReusable() || (isset($this->original) && $this->original->isReusable())
.Though this patch doesn't allow changing blocks from reusable to non-reusable custom code or contrib might.
Yep, I need to research how to do this.
Comment #23
tedbowThis patch adds
BlockContentReusableUpdateTest
thanks @tim.plunkett and @phenaproxima for helping me understand what I need to do for the update test.
It tests
The 'reusable' field is configured correctly after the update
The custom block library view page is updated with the
reusable
filterThat an extra display on this view will also be updated with the filter.
Also added
when creating the
reusable
field.We all found that making 10000 reusable blocks make the site not load but making 20000 non-reusable blocks works fine.
This makes sense
\Drupal\block_content\Plugin\Derivative\BlockContent
is not picking up these 20000 blocks and having 20000 content entities is no problem for Drupal.Also #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder which need this current issue should also not have a problem because on 1 new plugin derivative will be create for each custom block *type* not entity.
#2978102: Profile 1000s of custom non-reusable blocks for performance I won't close now because it anyone else want to test that issue it would be great. But don't think it should be blocker now that the performance has been tested.
Could @johnzzon get commit credit here since testing that issue really enables this 1? (@johndevman obviously should already get commit credit for his helpful reviews here and but also he started this patch off in #2957425 where this code started!)
Comment #25
tedbowWhoops forgot to add
@group
to the new test file which is for sure a totally new thing that I for sure don't forget to do almost every time I add a new test file 😜Comment #26
tedbowStarted the change record: https://www.drupal.org/node/2978419
The only thing that concerns when explaining the change(not finished yet) is:
Does this matter? Isn't this the case for all entity fields except nodes which are filtered out?
On thing we could do is to implement
hook_views_query_alter
and never return non-reusable blocks. This might make sense because you can never callsetAccessDependency()
on the block you can never really get access.This would the absolute safest and would remove the need for
block_content_update_8601
which updates the existing View.UPDATE
I played around with Views a little and reminded myself that, yes, this how Views handles entity field rendering and it only checks access on nodes in core I think when filtering.
So since this is expected behavior I say we do 2 things
block_content_update_8601()
to update all Views with the newresuable == TRUE
filter. This will make sure all existing Custom Views don't select non-reusable blocks because that could expose fields that are now access sensitive.resuable == TRUE
filter.Comment #27
tedbowThis patch does the suggestion I made #26
block_content_update_8601
toblock_content_post_update_add_views_reusable_filter
block_content_post_update_add_views_reusable_filter
to update all Custom block views with the newreusable == TRUE
filter.\Drupal\block_content\Plugin\views\wizard\BlockContent
which addsreusable == TRUE
filter to all new Custom block viewsComment #29
tedbowComment #32
tedbowJust notice the "Credit others" field here. Can I update this?
Adding @johnzzon because help on #2978102: Profile 1000s of custom non-reusable blocks for performance
Comment #33
tedbowNamespace was still wrong! 😅
Comment #34
johnwebdev CreditAttribution: johnwebdev at Kodamera AB commentedLooks great to me, +1 to RTBC this.
Comment #35
tim.plunkettI think this needs a retitle, as this issue seems decoupled from LB (with that work occurring in #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder).
Comment #36
tedbowChanging title
Comment #37
larowlanWhat if there is more than one dependency?
There is hook_entity_query_TAG_alter which would be a more focussed hook?
wow, this sucks, entity query sure doesn't support introspection does it
feels like we should add such an api (follow up)
We have a config entity updater utility class now, this should use that
do we normally have these in core yml files?
scientists agree
So, whilst I get the intent of the patch, I have a question.
Are we sure we're not forcing a square peg into a round hole.
If we need a limited use content entity that is for layout plugins only, why don't we just create one and avoid bolting this functionality onto an existing entity type. Content entities are cheap and easy to create in Drupal 8.
Doing so would avoid the need for the access dependency thing and all of the entity query/views stuff.
We already have a way to say 'don't show this block in the normal block UI', because the field plugin deriver does that. Those field blocks are available to layout builder, but not to the normal 'block layout' page. That sounds exactly like what we want here. A new content entity, with a block deriver, but limited only to layout builder usage.
I know that is a dramatic change of direction - but it feels better to purpose build something and have complete control over it than to bolt something onto existing code.
Thoughts?
Comment #38
tedbow@larowlan thanks for the review!
No patch right now just comments.
1. Yes I had it as multiple dependencies in this issue or one of the related on earlier but someone convinced be not do it b/c we were currently using it.
I guess if we make this specifically for the Block Content use case we should probably keep it in the module.
If it is more general we should change to supporting multiple.
2. I didn't find that but will look.
3. Yeah if we just care about the top level it would be easier.
Regarding make a new content entity type....
I think the main reason to stick with Custom Blocks is because it would be what site builders expect. I talked to about 4-5 site builders and asked them what they would expect if they could make makes inline in the new Layout Builder and they all said they would expect to be able to use there existing Block Types. I would encourage other to check and see.
If it was new content entity type then if you had a Video, Map, and Image block and you wanted the functionality in both the block UI and layout builder you would have to keep those bundle fields in sync across both entity types.
In #2974864: [Meta] Determine best method of allowing inline content creation Layout Builder we talk about the idea making an entity type that would automatically mirror the fields I think that would be even more of pain, especially for any existing contrib and custom code.
Also I think if we can avoid the situation like the Drupal 7 contrib space where we had boxes, beans, and fieldable panel panes I think that would be good.
Fieldable Panels Panes already has on there project page:
Bean has:
So in addition to the site that have already customized their block types for use with Block UI many sites are have already probably put in a good level of effort building out their custom block types.
I think it would be a big win if they "just worked" with the layout builder.
I did start to look at the new entity type way #2974860: Allow inline content creation in layout builder through a single purpose entity type
but after talking with people and several layout initiative meetings it seemed like reusing Content Blocks had big advantages.
I think they are cheap programmatically but not site builder-wise as far as having many content entity types to manage.
Comment #39
johnwebdev CreditAttribution: johnwebdev at Kodamera AB commentedHaving multiple dependencies interface used on BlockContent is misleading, because it would only ever use one.
Maybe we could rename it better to explain the composition we're actually after?
Comment #40
tedbow@larowlan I reopened #2974864-8: [Meta] Determine best method of allowing inline content creation Layout Builder and added some comments about I why think reusing the existing entity type is better.
That issue discussed a few possible alternatives.
Comment #41
tedbow\Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
by checking access to parent similar to the previous logic that required theAccessDependentInterface
. But because block itself keeps track of it's parent the code checking access to the block doesn't have to call::setAccessDependee()
.has_parent
filter to views that uses a boolean_string filter onparent_entity_type
(id may be removed if parent deleted). This is added to all existing custom block views and the new wizard adds them to new views too(of course can be removed).\Drupal\block_content\BlockContentViewsData::getViewsData()
because these don't seem very useful. We could add a @todo to make views relation ship to the parent entity with these fields as follow up.The reason have not done this is there may be use if you leave them.
For example you use the EVA module.
.
Comment #43
phenaproximaEDIT: Redacted.
Comment #44
tedbowblock_content_update_8600()
to useNULL
for default valuesComment #45
larowlanoh for DER in core :)
english is off here
an instance?
feels like this should be something generic we could use
we have the config updater service for this
should we
->addCacheableDependency($parent_entity)
too?if so, missing test coverage for that?
ha - jinx!
should we catch plugin not found exception here in case parent_entity_type is mangled/references a deleted entity type?
shouldn't we need both for it to be valid?
we should assert that the field and filter aren't present before we run the updates, to prevent people inadvertently updating the fixture and rendering this test redundant
debug?
Comment #46
tedbow@larowlan thanks for the review!
1. Is there an issue for issue for bringing DER into core? I checked https://www.drupal.org/project/ideas but I didn't see it.
2.Fixed
3. fixed and cleared up sentence
4. Should we add a @todo and follow up issue to add to
\Drupal\Core\Database\Query\ConditionInterface
with a implementation in\Drupal\Core\Database\Query\QueryConditionTrait
with something like:
public function hasConditionOnField($table_name, $field_name)
5. Changed to use new config updater service.
Left a comment in the handbook that we should update the doc page to demo this service: https://www.drupal.org/node/2535454/discuss
6. This section was wrong:
I removed both calls to
->addCacheableDependency($entity)
inside theif ($entity->hasParentEntity()) {
block.I changed:
return AccessResult::forbidden('Parent entity not available.')->addCacheableDependency($entity);
$access = $access->andIf(AccessResult::forbidden('Parent entity not available.'));
(returned later)Using
\Drupal\Core\Access\AccessResult::andIf()
ensures whatever cachable metadata was added earlier in the code will be passed on.For instance
->addCacheableDependency($entity)
is called but alsoAccessResult::allowedIfHasPermission($account, 'administer blocks'))
maybe called which adds cache context 'user.permissions'.I removed the call to
->addCacheableDependency($entity)
from here$access = $access->andIf($parent_entity->access($operation, $account, TRUE))->addCacheableDependency($entity);
also. I had meant to had
$parent_entity
but that is the responsibility of the call to the parent entity access to handle this so it is not necessary.8.. I am not sure what to do here.
I don't think we want to catch the exception and then not return a parent. Because then code might assume this block is parent free. I think any code should call
::hasParent()
first then assume there should be parent even if::getParentEntity()
doesn't return one, likeBlockContentAccessControlHandler
does. My worry is that if just::getParentEntity()
is called and for some reason it doesn't return a parent because we caught the exception then it will be assumed it should not have a parent.9.
Yes both should be valid but I think I was being over cautious.
I have entangled the logic of #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder around deleting parent entities which I need to fix.
Basically when a layout parent entity is deleted in some cases I need to mark content block to be delete. I was setting
parent_entity_id
to NULL but leaving theparent_entity_type
. Otherwise for parent entity types that don't use a datatable there is no way to tell if a blocks parent has been deleted except going through every single content with the parent type.In some cases because of parent revisions there could be hundreds of content blocks for a single parent in the layout use case. So we can't delete all the content block entities on
hook_entity_delete
for the parent.I will fix this is a follow up patch.
10. Fixed.
11. Yep I removed that in #44.
Comment #47
tedbowUPDATE: I tried a simpler approach below in #49 which might make more sense
Ok this moves the logic of deleting blocks with parents from inside
layout_builder
in #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder intoblock_content
.Basically the logic assumes that when a parent entity is deleted that the block entities with this parent should be deleted. This the case for
layout_builder
and it seems logical if it really is a parent.Here is how it works
hook_entity_delete()
if the parent entity orblock_content
entities are not using a datatable then all blocks with entity being deleted as the parent are update to removeparent_entity_id
. This will be used later to delete these blocks on cron.hook_entity_delete()
because there is another way to determine whichblock_content
entities should be deleted. This allows for the use case of having hundredsblock_content
entities to have the same parent. For instance nodes using the layout builder for overrides with hundreds of revisions using differentblock_content
entities.block_content
entities where the parent entity no longer exists(determined via "notExists" subquery)block_content
entities where theparent_entity_id
is NULLComment #49
tedbow\Drupal\block_content\BlockContentWithParentDeleter
dealing with parents being deleted differently depending on whether the entity type uses a datatable for storage.This was to get around using a usage or deletion table.
This patch simplifies that logic by adding the table block_content_delete which stores the ids of blocks that should be deleted when the parent is deleted.
The logic is must simpler. It also should work if there would be hundreds of blocks where the parent entity is config entity type. Right now in Layout Builder I can't see that case because the only config entity type that would be parent entities would be 'entity_view_display' entities but maybe it could happen in another use case.
BlockContentAccessControlHandler
becauseBlockContentParentEntityUpdateTest
because we are using the API to delete the View entity and fields have not been added yet.Comment #51
johnwebdev CreditAttribution: johnwebdev at Kodamera AB commentedCan't we just add a property to manage the state of the entity if it should be deleted or not, like File entity (file_managed) does?
Comment #52
tedbow@johndevman I thought about that but if we had base field
parent_status
(status is taken) but then we would still have the update the block entities and call$block->save()
which could be a problem if there are hundreds(thousands) of blocks using the deleted entity as a parent.UPDATE: I looked into this and I think actually we could get away with not calling
$block->save()
. If we do this in storage handler like\Drupal\block_content\BlockContentStorage::setParentStatus
I think this would be ok because it would be swappable. This allow use to update hundreds of records with 1 update query.I looked at other storage handlers in core and this is done to update a base field in
\Drupal\taxonomy\TermStorage::resetWeights()
I think this is similar use case.
I am working on patch to do this.
Comment #53
tedbowOk this patch
parent_status
field toBlockContent
.block_content_delete
table.BlockContentStorage
with 2 methods updateparent_status
on parent delete and delete blocks with this setting.BlockContentWithParentDeleter
because nowBlockContentStorage
can just be used directlyBlockContentAccessControlHandler
to deny access if$entity->get('parent_status')->value === BlockContentInterface::PARENT_DELETED
BlockContentParentEntityUpdateTest
BlockContentDeriverTest
to make sure the plugin shows up after$block_content->removeParentEntity()
Comment #54
tedbowThis patch
parent_status
field_block_content_has_parent_entity_condition
to base table name on alias so check for exact match notif (strpos($field_parts[0], $data_table) === 0) {
BlockContentEntityReferenceSelectionTest
to use dataprovider and to also test 'parent_status' field. Now tests to make sure that if any conditions are set on any of these fields['parent_entity_id', 'parent_entity_type', 'parent_status']
then_block_content_has_parent_entity_condition
will not alter the query.Comment #56
tedbowWorking on #2957425-160: Allow the inline creation of non-reusable Custom Blocks in the layout builder I realized the access changes here won't work for layout_builder. 😿
See the other issue for more detail and failing tests that demonstrate the problem.
But related to this issue:
Comment #57
tedbowThis patch:
Comment #59
tedbowWhoops! In #57 I only include the diff of
core/modules/block_content
and forgot that there were changes tocore/lib
Comment #60
tedbowOk this brings back to using reusable blocks from #33 but with bringing improvements up to #54 such as testing improvements.
It has the
inline_block_content_usage
table to track blocks that used in the Layout builder so they be deleted.Comment #61
BerdirDidn't really review yet, but one scenario we might want to explicitly test are private files. That's AFAIK the main use case where entity are being checked for view access outside of their usual context that's actually testable from the perspective of a user. So create a block type with private field and then make sure the file can be accessed as expected, e.g. not when it only exists as a pending revision on a node.
That said, there's AFAIK actually a bug with having it working at all on non-default revisions of an entity, that might prevent that kind of test for now, not sure.
Comment #62
tedbow@Berdir thanks for bring up private files.
I have the updated the combined patch on #2957425-167: Allow the inline creation of non-reusable Custom Blocks in the layout builder to handle the access to private files case in Layout Builder attached to non-reusable custom blocks with tests.
Since file usage only stores entity id and revision ID there needs some Layout Builder specific logic to determine which revision of the entity should be considered the access dependency.
So I added an event to
\Drupal\block_content\BlockContentAccessControlHandler::checkAccess
that fires if the block is non-reusable and the dependency has not been set. In the layout builder the only case I know of where this would happen is when checking view or download access for private file attached to an inline block. The event allows modules to set the dependency for a block. If a module doesn't set a dependency in the event the then access is denied so it defaults to no access.This would also be fired if you had a view or custom code was rendering non-reusable blocks. In #2957425 we update views so they filter out non-reusable blocks. So it seem like this will not be common case.
Comment #63
tedbowComment #64
tedbowOk here is test coverage for
BlockContentAccessControlHandler
.Right not it just covers 'view' operation.
I had a question about other operations. If you have 'update' or 'delete' access to the access dependency should you have the same access to the non-reusable block?
Layout Builder does not make this assumption. It controls editing on these blocks so if you access to the layout you can update/delete the block.
But right now if you knew the ID of a non-reusable block you could put it in the path
block/[ID]
and be able to edit the block. Layout Builder should not work this way because you configuring layout is separate permission from editing the entity.So I think there are to options:
BlockContentAccessControlHandler
if the block is not reusable returnAccessResultNeutral
if the operation is not 'view'. Any module wanting to change this would implement
hook_block_content_access
to alter that access. Layout Builder would have to do nothing.BlockContentAccessControlHandler
the way it is now where access for all operations depends on the access to the same operation on the access dependency. Then any module like Layout Builder would have to implementhook_block_content_access
to alter that access.I am leaning towards 2) because even though layout builder is our first use case for non-reusable the access seems more like a special case.
Comment #66
tedbowLeft out files outside of
block_content
interdiff in #64 is still validComment #67
Wim LeersI reviewed 80% of the patch, I didn't review all the test coverage in detail yet. I think you'll find helpful feedback in here already:
Woah! Impressive. We've long needed this.
To my ears,
DependentAccessInterface
sounds better, but that's perhaps because I'm a non-native speaker.Can you think of consequences for other parts in Drupal? AFAICT this handily shifts responsibility to every class that implements this interface; because it causes any
AccessibleInterface
implementation to need to update their::access()
method to take this into account. I think that's fine.Ideally this interface would not have any setters.
I checked and … the only caller of this method is a test! So it's totally possible to do this without the setter 🤞
What if there are multiple?
Also: shouldn't this interface extend
AccessibleInterface
?Whitespace nit.
Incomplete sentence?
Not very clear. This is already explained in the docblock. Let's just remove this.
I wish there was an API for this sort of thing, because this code definitely looks both complex and wonky. Those two adjectives in one sentence make it scary.
🙈 + 📚 = 😱
s/to/to all/
This is the only mention of "inline" in both this patch and in the
block_content
module. Let's rename that.This should not accept a parameter. We need:
This is the pattern that
\Drupal\Core\Entity\EntityPublishedInterface
introduced.Why this change? (I like it, but why is it happening in this issue/patch? What change is triggering it?)
Comment #68
tedbow@Wim Leers Thanks for the review! 👍🐎
1. hmm. the way it is now sounds better to me. Will leave for now but interested in others opinions
UPDATE: talke to @tim.plunkett he likes this change also so will change.
2. I think it is fine. In the non-reusable block usage we don't care about the access dependency if the block is reusable(all blocks currently are).
I think other use cases would likely also not have simple always check access dependency for access logic.
3. There are calls to the setter in
layout_builder
in the related issue which is dependent on this one #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder4.
In a previous version of the patch I had multiple. @larowlan asked the same question in #37
For the non-reusable blocks it makes sense to have only 1 because if it is truely not reusable it would only it would only have 1 dependent(if had 2 you are reusing it)
That being said I could see other uses of the this interface needing multiple dependencies. Do we change it so the other possible future uses could incorporated?
If we do change it do we enforce it in
\Drupal\Core\Access\AccessDependentTrait::setAccessDependencies
that should only 1 dependencies or could there be cases the dependencies are different from where it is "used" which should be only 1.Also if we change to multiple should the logic in
\Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
check that you have access to all dependencies?Leaving for feedback.UPDATE: Talked with @tim.plunkett about this and he mentioned that in the past when we have opted for one vs array in these cases we have gotten burned.
Changing it to handle multiples. In
\Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
i have changed to require access to all dependencies.5. fixed
6. fixed.
7. removed
8 @larowlan also suggested this. Created a new issue #2984930: Add hasCondition() method to \Drupal\Core\Database\Query\ConditionInterface and added a @todo here.
9. fixed
10. fixed.
11. Added
setNonreusable
this sounds better to me.12. This was mistake. This is in the patch because I copied the
core/modules/block_content/config/optional/views.view.block_content.yml
into a test module to make sure a view with multiple displays gets updated byblock_content_post_update_add_views_reusable_filter
. When I exported the View it most have updated it.I also updated
\Drupal\Tests\block_content\Kernel\BlockContentAccessHandlerTest()
to test UPDATE and DELETE operationsComment #69
EclipseGc CreditAttribution: EclipseGc at Acquia commentedPlural. Also worth noting that this is the perfect place to introduce variadics. It's sad to see us creating a new interface that could benefit so much from that a version before we have it. :-( oh well d9.
For myself, I'd prefer an "addAccessDependency()" method that guarantees the AccessibleInterface object is passed, but once I've gone that far, I start to wonder about and/or logic and how we have to iterate the getAccessDependencies() method to do this properly. It leaves me wanting 2 classes that just implement AccessibleInterface for and/or that take an array of dependencies in the constructor and just call the access() method.
No error checking to ensure AccessibleInterface.
Any good reason not to make this a service?
This is great, and is probably more evidence to support an addAccessibleDependency() method on the DependentAccessInterface interface above. That being said, I still think there's an opportunity here to use custom dependent access and/or classes in this case to simplify things, but that could totally be done in a follow up or at some later date.
maybe setNotReusable() ??
getEntity() maybe? I know it's a block content entity, but I'd be lost first time I saw this. Maybe it doesn't matter.
I'd totally add a addAccessDependency() method to this event and use that to interact with the entity properly. That actually means you could abstract away from entities doing all this work later and just introspect the entity to make decision (if at some point that makes sense to do).
Obviously there are some other methods that might make sense to add on the event once you do that.
All in all I really like this patch and I think it's super close to being ready. We're probably too late in the cycle to seriously consider my DependentAccessAnd/Or class suggestion, but other than that, I think the rest is pretty straight forward and on point. Super excited about this.
Eclipse
Comment #70
tedbow@EclipseGc thanks for the review!
1. In #68 I changed
DependentAccessInterface
to allow adding multiple dependencies because a few reviewers asked about this and it seemed it allowed for flexible uses of this interface.But this required a change to
BlockContentAccessControlHandler
so this locks the access control to be an "AND" of all the dependencies. Obviously other access control handlers that are handling an entity that implements
DependentAccessInterface
can do different logic. but the code calling$entity->setDependencies()
won't have control over this.I think the code above
$dependency->in_preview
also points to an assumption theat the dependencies are going to entities butDependentAccessInterface
actually expects justAccessibleInterface
implementations.I think looking at
DependentAccessInterface
again without the assumption that the dependencies will be entities actually allows for more flexibility for the code calling$entity->setDependency()
with a singular dependency.Right now we don't have a way to group
AccessibleInterface
implementations but what if introduced(in a follow up)AccessibleGroupInterface
which would extendAccessibleInterface
.This would allow code like
$entity->setDependency(new AccessGroupAnd([$entity1, $entity2]))
or even
$entity->setDependency(new AccessGroupAnd([$entity1, new AccessGroupOr([$user1, $user2])]))
Because both expects implementations of
AccessibleInterface
and is itself an implementation ofAccessibleInterface
this would allow complex nested access logic.Then because
AccessibleGroupInterface
would extendAccessibleInterface
the handler logic could always simply be$entity->getDependency()->access($operation, $account, TRUE)
The handler doesn't need to care if the dependency is a single entity(
EntityInterface extends AccessibleInterface
) or complex nestedAccessibleGroupInterface
.2. Doesn't apply because shifting back to singular dependency.
3. Was going to leave because this will eventually move to
\Drupal\Core\Database\Query\ConditionInterface
in #2984930: Add hasCondition() method to \Drupal\Core\Database\Query\ConditionInterface. Though we could make this @internal service for now. No change right now.4. I think my point in 1) would make this very flexible.
5. changed
6 I change it to
getBlockContentEntity()
since the dependency might also be any entity(though not necessarily) I thought this was clearer.Added
setAccessDependency()
andgetAccessDependency()
because of the changes I made in 1).Also changed the #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder to use this
setAccessDependency()
, patch to be posted soon.Comment #71
Wim Leers#68: 👍👍
#68.3: Okay, so #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder also has some calls to the setter. Is it actually necessary though? Every single time I've worked on a value object and added setters to it, I've regretted it later. I'd like to help us not introduce more of that. Immutable value objects prevents lots of frustrating bugs/debugging.
(Funny that I independently made several remarks that @larowlan also made :) And yay for consulting @tim.plunkett to speed up consensus building!)
#69:
+1
This is also relevant wrt #68.3.
+
i.e. the default interface is immutable, the optional extra interface adds mutability. That'd be much more comfortable.
So then you end up with:
+
Although to address Kris' point, you could as well do
assert(Inspector::assertAll(function($v) { return $v instanceof AccessibleInterface; }, $dependencies));
.#70:
This is fine, use
assert(Inspector::assertAll(…));
to verify that your precondition holds true. It's fine here because this is code specifically dealing withBlockContent
entities. If this were generic code, it would be a problem.… this is looking a lot like repeating the structure
\Drupal\Core\Config\Entity\Query\Condition
.Patch review:
Huh? Why did this change back to single instead of multiple? I guess that's what you were getting at in #70.1, but I thought you were just theorizing there.
It doesn't make sense for me to review this patch in detail until we agree on single vs multiple.
I'd like to get @tim.plunkett's thoughts on this.
Comment #72
tedbow@Wim Leers thanks for looking again.
re setter on-
-
- this is looking a lot like repeating the structure \Drupal\Core\Config\Entity\Query\Condition.
-
- It takes the need to understand how combine multiple instances of
- In the non-reusable blocks it would allow other uses of non-reusables to have different logic for multiple accessible. Currently layout builder doesn't need multiple depenedencies but other uses might and then might need AND or OR or some combo.
- It would allow much more complex logic
DependentAccessInterface
You convinced I removed it. Now we have
RefinableDependentAccessInterface extends DependentAccessInterface
for the setter.Re
$dependency->in_preview
Actually the
$entity
in\Drupal\block_content\BlockContentAccessControlHandler::checkAccess
is a BlockContent entity. The$dependency
is only guaranteed to be instance ofAccessibleInterface
. For the layout_builder use case this is actually always anFieldableEntityInterface
but since we are addingDependentAcccesInterface
toBlockContentInterface
we can't be sure how other modules will use this.I guess in a very lose since 🤷♂️. This much less complex because almost would be handled by
\Drupal\Core\Access\AccessResultInterface::orIf()
and\Drupal\Core\Access\AccessResultInterface::andIf
I have attached access_group_idea-do-not-test.patch because I think it will be more clear(or PR 😅)
No, I think the single with the groups idea actually is more powerful and flexible because:
AccessibleInterface
out of access handlersHopefully the attached patch access_group_idea-do-not-test.patch will show this
Ok I would love to get consensus on this.
Comment #74
Wim LeersThis is IMHO the best argument.
+1 — can we get feedback from @tim.plunkett on this?
Comment #75
tedbowI talked with @tim.plunkett about this problem.
He liked the idea of the
DependentAccessInterface
only having 1 dependency. Especially we can't simply haveDependentAccessInterface
implementAccessibleInterface
itself if we want to use with Block plugins as layout builder will need becauseaccess
would clash with\Drupal\Core\Block\BlockPluginInterface::access()
(I think are problems beyond that)But it doesn't want
\Drupal\Core\Access\RefinableDependentAccessInterface
to only havesetAccessDependency()
meaning you would be always erase previous access unless you did. He wanted to make sure that it also hadmergeAccessDependency()
so you can easily merge an accessible without erasing existing ones.This means will really need the access group classes I added in the access_group_idea-do-not-test.patch.
So I have added them and tests for the access groups and the new
mergeAccessDependency()
To explain how the merge works I will just copy in the docblock
Comment #78
tedbowComment #79
tim.plunkettThis matches my understanding of our conversation, I think it looks great!
Deferring to Wim for the final sign-off.
Comment #81
Wim LeersI both agree and disagree with that!
setAccessDependency()
is bad, for the reason he cited: it overwrites (erases) previous data.mergeAccessDependency()
but disagree with its name: I thinkaddAccessDependency()
is better. I explained in detail why in #71. In short: because it then follows the example set byRefinableCacheableDependencyInterface::addCacheableDependency()
Comment #82
tim.plunkettWe went back and forth over add vs merge for a while.
Interesting that
addCacheableDependency()
is a merge operation and exists on the same interface asmergeCacheMaxAge()
!I'm fine with either name.
Comment #83
Wim LeersTime for detailed patch review including nits!
s/fooBar/foo_bar/ in parameter names.
I think
would be clearer than ?Accumulated? Or combined?
In
array_reduce()
this is called the carry.80 col.s
I thought this was supposed to no longer be there?
s/well/will be/
Trait for FCQN_OF_INTERFACE.
elseif
s/this a function to a call/this function with a call/
This is inspecting the entity. Therefore you must precede it with
$access->addCacheableDependency($entity)
to ensure the access result varies correctly.I don't understand the rationale behind this if-test. It's probably obvious to you because you're so deep in this problem space. I think a brief comment would help a lot :)
DependentAccessInterface
Non vs not, let's be consistent.
New field added, hence also exposed via REST. Makes sense!
(Let's also mention this in the CR.)
Comment #84
tedbow#81 that makes sense to stick to existing naming pattern. Fixed.
#78 fail because update test need
@group legacy
now I guessComment #85
tedbow@Wim Leers Thanks for detail review!!!
1. fixed
2. Changing the name to
combineAcces
because "combine" is in the method docs of\Drupal\Core\Access\AccessResultInterface
forandIf
andorIf
. And we are calling this.3. Changing to "The combined access" b/c 2)
4 exactly 80 now
5. No we wanted to leave this otherwise there is not way to replace all the dependencies with your own. The comment details that existing dependency will be replaced.
6. fixed.
7. fixed
8. fixed
9. fixed
10. fixed
11. add a comment.
12. Changing the comment slightly as now we
\Drupal\block_content\Event\BlockContentGetDependencyEvent::setAccessDependency()
this allows setting the dependency on the event and not changing the block at all.13, changed to
setNonReusable
as this is used everywhere.14. oh forgot about that. fixed
Nope, that threw an error.
Only:
return $this->get('reusable')->first()->get('value')->getCastedValue();
worked.Thats the only way I could get a
\Drupal\Core\TypedData\Plugin\DataType\BooleanData
wow. Drupal, wow
15 👍
Comment #86
larowlanWould it be simpler to initialize this as a neutral and then use ->combineAccess everytime? or would that not work?
we returned above so we don't need elseif here
My only remaining question is - do we absolutely want to put those access interfaces in the core namespace. It means we only have one go to get them right. Layout builder is still beta but if we put those classes in core, we are fairly locked in to what we do with them. Should we consider marking them internal? Should the event be internal for the time being? Thoughts?
Comment #87
tedbowif (!$dependency instanceof EntityInterface || empty($dependency->in_preview))
I started to wonder if there is anyway we could handle this in
layout_builder
directly since could cause problems for other uses of non-reusable blocks.I figured that since in Layout Builder we are setting the dependency and the layout builder knows when it is using preview for the defaults it can just set an accessible dependency that will pass in the circumstance.
Anyways I will update #2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder with the changes
For this issue we can just remove that logic.
andIf
andorIf
I believe it won't matter to start with a neutral access result but what if someone extend it for a group that only passed if there were only allowed. Not likely but I would really not combine any result that weren't explicitly added as a dependency if we can avoid it.I did look at the logic again and I simplified it.
First I made it explicit that if there were no dependencies added then there is access forbidden result. I added test coverage for this.
Second, instead of starting with a neutral I simply shifted off the first dependency, after determining there were dependencies, and started with the access result of that dependency. Then loop through the remaining dependencies. If there is only 1 dependency this still works fine.
We can't add the interfaces to only layout_builder because @tim.plunkett was only ok with the
DependentAccessInterface
if you were able to haveaddAccessDependency()
to merge the access results in.I think @Wim Leers was only also behind this. and also having single dependency returned from
\Drupal\Core\Access\DependentAccessInterface::getAccessDependency
.he gave a big ➕1️⃣ to
So that combination really means that since block_content needs all of this including the groups so it can't live in layout_builder.
Comment #88
Wim Leers#85
#86 Agreed with this concern.
#87:
Patch review
Should be
AccessResult::neutral()
.This only partially addresses @larowlan's concern. The fact that it already lives in
core/lib
is a problem though; it should live in the Layout Builder or Block Content module. Otherwise many developers will start using it anyway.Comment #89
tedbowWe can't actually shift off a dependency from the array here or the dependency will not be here if you call access again.
re #88 Patch review.
1. Fixed
2. Moved all the access classes and tests to Block Content module.
We can't move them to Layout Builder because layout builder needs the Block Content entities to implement
RefinableDependentAccessInterface
.Comment #90
Wim LeersShouldn't this then also become
@internal
?Also: CR should be updated to not mention these
@internal
things.These are the key changes in this issue, and it has a CR 👍
Comment #91
tedbow1. marking classes as internal
2. Will update change record next.
Also did
was also going to remove:
`AccessibleGroupInterface` `AccessibleGroupBase` and `AccessGroupOr`
and just have `AccessGroupAnd extent Accessible`
Then the only code that would be left that would reference `AccessibleGroupInterface` and I could hard code that to `AccessGroupAnd`
Comment #92
tedbowUpdated change record and issue summary
Comment #93
Wim Leerscore/modules/block_content
.@internal
.Thanks for helping to descope based on input from @larowlan and me!
Comment #94
tim.plunkettBlocks a major (#2957425: Allow the inline creation of non-reusable Custom Blocks in the layout builder)
Comment #95
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThis looks great to me. Adding credit to all the reviewers. Thank you!
Comment #97
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI pushed this to 8.7.x. Since alpha is this week and this patch is not trivial and might have side effects to stable core code that we haven't foreseen, leaving at RTBC for 8.6.x for a release manager to evaluate if and when to cherry pick to 8.6.
Comment #98
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedKind of late to the party, but I see that this patch has quite some code dealing with entity reference and an upgrade path, so here's a review:
I don't understand why this was implemented like this, wouldn't it be easier to write a custom entity reference selection plugin for the block_content entity type and add a default condition in
buildEntityQuery()
?Is it because of the nested logic? If so, selection handlers also have a
entityQueryAlter()
which gives you access to the underlying db query.Some sites might have disabled translatability of custom blocks, so we need to do the "is translatable ? get data table : get base table" dance, like everywhere else in Views :)
Also, we should get the table names from the storage (after making sure that the entity type is using core's
SqlContentEntityStorage
), because the table names specified in the entity type definition are about to be deprecated.I thought we were supposed to include the full plugin definition in update functions, otherwise any change in defaults for that plugin would result in a config difference.
Extra empty line here and a missing space before
{
:)Why do we need to add the entity as a cacheable dependency unconditionally here?
Shouldn't we only add in the
if
below?We shouldn't include the initial value in the base field definition, it has no effect here because it's only supposed to be used in the update function which adds the field.
We usually call
(bool) ...
instead of->getCastedValue()
. In fact, this is the first time I see a call togetCastedValue()
in a content entity class.This should also be in the
block_content
group.Needs
()
at the end of the method names.I'm not sure it's safe to install a module before running the updates, we usually add/alter views and other config objects in a special "db preparation" file, like
core/modules/field/tests/fixtures/update/drupal-8.views_entity_reference_plugins-2429191.php
for example.We have
EntityReferenceSelectionAccessTest
for testing all of our custom selection handlers, can we move this one over there in atestBlockContent()
method? Otherwise it would be a lot harder to find...Extra empty line :)
Comment #99
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedAlso, if this patch doesn't end up in 8.6.x after all, we need to rename this update function to
block_content_updae_8700()
otherwise we'll end up with the same confusing situation as we had withblock_content_update_8400()
, which only exists in 8.5.x :/Comment #100
tedbow@amateescu thanks for taking a look.
I have just addressed the points that I have answer for right now.
1. The reason I wrote the alter this way instead of writing a new entity reference selection plugin for block_content entities is that there could be custom entity reference selection plugins already in existence that have been written to handle block_content entities.
Because we are now using block_content entities in a much more access restricted way we to whatever level possible do not want these existing entity reference selection plugins to pick up any of the new non-reusable blocks. I don't think we can rely on these plugins being update to filter out non-reusable blocks.
You know this stuff better than me but since there no access checking on the autocomplete for entity reference selection (except nodes?) any existing selection plugins would let you see the labels of non-reusable block. They won't actually render because of the block content access handler but you could figure out labels using the autocomplete suggestions.
But also the plugins would be able to select not reusable block but then on edit the access checking would happen and you would get an error on your previous selection.
So
block_content_query_entity_reference_alter
works for the default selection plugin and for any custom ones we can't know about.5. Because we inspecting the entity in the if we need to add the cacheablity because the fact that we are testing
isReusable()
at all means the access should be cached on the entity.7. Yes I got this suggestion from @Wim Leers to change from
(bool) ...
. Although this is long winded it also seems like this is decent usage ofgetCastedValue()
. Is there a reason to use(bool)
, a cast, when we have an explicit method to get the casted value of our typed data?Comment #102
tedbowAddressing the rest of the review in #98
2. fixed
3. added full definition
4. fixed
5. fixed
8 added @group
9. fixed
10. Thanks for the example, updated the test. Removed the test module
11. It seems weird to move it because it is covers code in the block_content. Also that is
BrowserTestBase
and this is kernel test.12. fixed.
Settings to Needs Review to run the tests.
Comment #103
tim.plunkettIs SqlEntityStorageInterface not good enough here? What's magic about that specific implementation?
Missing some words here? "If the storage is not an instance of..."
Also might be worth moving above the if
Might as well switch to
!==
Other changes look good!
Comment #104
tedbow1. Apparently
SqlContentEntityStorage
is magic!getDataTable()
andgetBaseTable()
are not defined in the interface. The only interface these methods are defined is\Drupal\Core\Entity\EntityTypeInterface
.This issue #2232465: Deprecate table names from entity definitions deals with the methods but doesn't move them to an interface as far as I can tell.
2. Moved the comment up. Also checked after the if/else if
$table
is empty in which case we can't update the views. Not likely but possible.3. fixed
Comment #105
dsnopekSee #2960037: Add a way to get the primary table of an entity type from TableMappingInterface
That's about replacing those methods with a method on
TableMappingInterface
.There's many
SqlContentEntityStorage
-related warts - this meta issue is about fixing them: #2960147: Finalize the entity storageComment #106
amateescu CreditAttribution: amateescu for Pfizer, Inc. commented@tedbow, re #100:
On the contrary, the main purpose of entity reference selection plugins is query-level access checking for all entity reference queries, at least until we get #2909970: Implement a query-level entity access API in core. See the
buildEntityQuery()
method of various selection handlers, for exampleUserSelection
,FileSelection
orCommentSelection
.So I really think that this query access logic should live in a BlockContentSelection plugin, because that should the canonical "source of truth" for any custom block_content selection plugin.
You do bring up a good point about possible pre-existing custom selection plugin implementations, but I would prefer if we can change
block_content_query_entity_reference_alter
to only cater for those cases, i.e. by checking if the selection plugin class is different than (and doesn't extend)Drupal\block_content\Plugin\EntityReferenceSelection\BlockContentSelection
which should be introduced by this patch.The access checking on edit for pre-existing references should not be a problem since #2791269: Allow saving pre-existing references to inaccessible items.
Right, so this means we can remove the
addCacheableDependency($entity)
call from the firstif
, since we're adding it unconditionally below.Also, It would be a bit more clear if we move the line with
$access->addCacheableDependency($entity);
below theif ($entity->isReusable() === FALSE) {
block, since that implies a thinking like "we determined access based on a property of of the entity, so we need to add it as a cacheable dependency". I think that's the common practice.. at least in core.A
(bool)
cast will make that code always return a boolean value, even in cases when the underlying field might change for whatever reason, andgetCastedValue()
would not return a boolean anymore.That test class handles all the existing access-related code from selection handlers (node, user, comment) and taxonomy will soon be added there as well in #2981887: Add a publishing status to taxonomy terms. Having it all in one place makes it easier to maintain, for me at least. As for it being a browser test, I just opened #2987084: Convert EntityReferenceSelectionAccessTest to a kernel test to fix that.
--
About #104.1: This whole area is very confusing, I know. It's because the table names from the entity type definition are about to be deprecated, but we don't yet have an "official" way of getting them, at least not until #2960037: Add a way to get the primary table of an entity type from TableMappingInterface lands.
Comment #107
tedbow@amateescu thanks for the detailed response
This will not work because in the if the dependency is not set we
So in that case the dependency on the entity would not have been added if we are after the if.
I have added comment as to why we are adding the dependency.
(bool) $this->get('reusable')->value;
which it was in #78Creating a follow up issue #2987159: Create an entity reference selection plugin for custom blocks that filters out non-reusable blocks because
block_content_query_entity_reference_alter()
does prevent the selection of non-reusable blocks but a plugin would be better, though we still won't be able to removeblock_content_query_entity_reference_alter()
Comment #108
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI discussed this with @catch and he and I are both ok with this going into 8.6 before beta, so removing the "Needs release manager review" tag. That's still assuming though that #107 or something like it lands by the end of this week. If it does, I'll cherry pick both that commit and the #96 commit to 8.6. If at all possible, I'd love to do that by tomorrow or Thursday instead of Friday, just to give a bit of extra time for anyone who's testing the 8.6 branch tip to report back on if they run into any unanticipated problems with this code prior to beta getting tagged.
Comment #109
tim.plunkettI think between #2987159: Create an entity reference selection plugin for custom blocks that filters out non-reusable blocks and this patch here, @tedbow has sufficiently addressed @amateescu's feedback.
This change will make it that much better for backporting to 8.6
Comment #110
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedFWIW, I'm very happy with the changes from #107 and the followup issue to use a proper selection plugin.
Comment #111
effulgentsia CreditAttribution: effulgentsia at Acquia commentedAdding issue credit to @amateescu. Thanks!
Comment #114
effulgentsia CreditAttribution: effulgentsia at Acquia commentedPushed #107 to 8.7.x and cherry picked #96 and #112 to 8.6. Thanks for the great work and reviews on this!
Comment #115
effulgentsia CreditAttribution: effulgentsia at Acquia commentedAlso published the CR and tagging for the release notes.
Comment #116
penyaskitoThis made one of my upgrade tests fail:
1) Drupal\Tests\lingotek\Functional\Update\LingotekTargetStatusFormatterUpdate8209Test::testUpgrade
Schema key views.view.block_content:display.default.display_options.filters.reusable.group failed with: variable type is string but applied schema class is Drupal\Core\TypedData\Plugin\DataType\IntegerData
Found out the culprit here. The attached patch fixed it. Maybe we are missing some coverage in the upgrade tests?
Comment #117
effulgentsia CreditAttribution: effulgentsia at Acquia commentedThank you for discovering that and reporting it! Setting to Needs Review for evaluation. If the bug and fix are correct, then yes we need a test for it too.
Comment #118
tedbowCreated a follow up #2988435: block_content_post_update_add_views_reusable_filter uses an string instead of int for "group"
It is unclear why LingotekTargetStatusFormatterUpdate8209Test failed and ours didn't
Comment #119
effulgentsia CreditAttribution: effulgentsia at Acquia commentedI raised that followup to Critical. Setting this back to Fixed.
Comment #121
Gábor HojtsyComment #122
xjmIt makes sense to mention this as a highlighted feature, but is there some disruption or important update information that requires a release notes mention?
Comment #123
xjmReading the CR, doesn't look like there's anything to mention in the 8.6.0 release notes for this fix. Thanks!
Comment #124
benjifisherLooking at the last patch committed for this issue, I was confused until I realized that an earlier patch was also committed. I am hiding all the interdiffs and other patches to make it easier for the next person who decides to look at how this issue was implemented.