Problem/Motivation
During theme initialization, block_theme_initialize() copies over existing blocks and gives them a machine name prefixed with the theme name. It does not check to see if a block exists with this name already.
Steps to reproduce
See #2858897-17: Block name collision on theme creation:
Initial setup
Prepare two themes:
- Theme 1: themeone, Active, default
- Theme 2: themetwo, Disabled
Setup steps:
- Using Theme 1 theme, place a block with id: a_block_with_a_cool_id
- In the same theme, place a block with this id: themetwo_a_block_with_a_cool_id
- Enable Theme 2. In the process, this will copy the blocks from the default theme themeone to themetwo
- and prepend the theme machine name to the final block ID.
The copy process fails because, when trying to copy a_block_with_a_cool_id to themetwo, this will create themetwo_a_block_with_a_cool_id, resulting in a failure for ID duplicate (because already exists in themeone).
This is from memory, so I hope I don't give any wrong indication.
Proposed resolution
Move the logic in \Drupal\block\BlockForm::getUniqueMachineName to a location where it can be shared with block_theme_initialize.
Remaining tasks
Review latest patch in #2858897-29: Block name collision on theme creation, and get core maintainer review to push this forward.
User interface changes
None?
API changes
@tbd
Data model changes
Drupal will add an integer-increasing counter (e.g. _1, _2) to conflicting blocks when processing a cloned block for an enabled theme (in block_theme_initialize()).
| Comment | File | Size | Author |
|---|---|---|---|
| #128 | 2858897_128.patch | 15.83 KB | mpotter |
| #101 | interdiff_84-101.txt | 3.4 KB | schlaukopf |
| #101 | 2858897_101.patch | 17.02 KB | schlaukopf |
Issue fork drupal-2858897
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 11.x
compare
- 2858897-11.x
changes, plain diff MR !4921
- 2858897-10.1.x
changes, plain diff MR !3789
Comments
Comment #2
tim.plunkettComment #3
mxh commentedConfirming this problem, which might result in an exception like this:
exception 'Drupal\Core\Entity\EntityStorageException' with message ''block' entity with ID '...' already exists.'+1 for Aki Tendo's suggestion to re-use the implemented behavior of BlockForm::getUniqueMachineName(), which requires some refactoring first.
Comment #4
Aki Tendo commentedI can't take credit - it was Tim's idea. We discussed it in IRC and he reformatted the lead post.
Comment #6
dakwamineI have prepared a patch regarding this issue. It does not refactor the BlockForm as suggested though. It simply adapts the implementation of
\Drupal\block\BlockForm::getUniqueMachineName()into block.module'sblock_theme_initialize($theme)function.Comment #11
ben.kyriakou commentedThis is still an issue, and something I've run into when enabling a new subtheme. Attached is a rough patch for how this might look with the code pulled out into a separate service (which will undoubtedly break tests). If this is the kind of approach envisioned above I can work this into something more robust and update the associated test coverage.
Comment #13
ben.kyriakou commentedThis should fix BlockFormTest
Comment #15
ben.kyriakou commentedLet's try that again with the correct interface file name and some tidying up
Comment #16
thallesHello everyone!
What the steps to reproduce this issue?
Comment #17
dakwamineHello thalles,
It's been a long time since my last check. But i think I remember the steps to reproduce this issue:
Initial setup
Prepare two themes:
Steps
a_block_with_a_cool_idthemetwo_a_block_with_a_cool_idthemeonetothemetwoand prepend the theme machine name to the final block ID.
a_block_with_a_cool_idtothemetwo, this will createthemetwo_a_block_with_a_cool_id, resulting in a failure for ID duplicate (because already exists in themeone).This is from memory, so I hope I don't give any wrong indication.
Comment #18
thallesThanks @Dakwamine!
Comment #20
Jorge Navarro commentedFor some reason it's required to add yourthememachinename_BLOCKID prefix to avoid the error.
Comment #22
rcodinaPatch on #15 works like a charm!
Comment #24
Webbeh#15 resolved this issue on an installation of 9.2.0 where our campus theme and module package initializes and configures custom blocks.
Comment #25
phjouPatch #15 fixed the issue for me as well. Thanks
Comment #27
rosk0Re-roll of #15 on 9.4.
Comment #28
rosk0Oops. Forgot the patch...
Comment #29
rosk0Fixed code style failures.
Comment #30
joachim commentedIs #3061266: Prefix block machine name suggestions with the theme machine name relevant?
Comment #32
smustgrave commentedReading https://www.drupal.org/project/drupal/issues/3061266 and that may be a duplicate or least trying to accomplish the same thing
Tested
With seven theme uninstalled
Created a block with machine name seven_page_title
Installed seven
Saw there is no seven_page_title block now when there should be.
Am I missing a step?
Comment #33
andypostComment #34
WebbehTo answer #32, please see #2858897-17: Block name collision on theme creation to reproduce the issue. I've added that into the Issue Summary, since it's incredibly important.
Also, moving back to Needs Review, as I don't see NW steps in #32.
Comment #35
WebbehComment #36
smustgrave commentedThe work would be that seven page title block was never created.
Comment #37
smustgrave commentedSo essentially I followed #17
Enabled themeA (claro in my case)
Created a block with machine name seven_page_title which is just a custom block
Enabled seven
seven_page_title is one of sevens block but it doesn't get created so now my theme has no block to add the title.
Comment #38
smustgrave commentedUpdating related issue tickets. I could be wrong but I feel this may be blocked until https://www.drupal.org/project/drupal/issues/3061266 is in?
Comment #40
tobiasbUpdated/Reroll patch for D9.
@smustgrave
When your theme has the block e.g. breadcrumb (initial by installer) and default_themename_breadcrumb (created later), then drupal would create the block seven_breadcrumb and then it tries once again for the block default_themename_breadcrumb, because it replace default_themename with seven. Just take a look at block_theme_initialize.
With the patch the name is seven_breadcrumb_2.
Comment #41
tobiasbPatches applies also for 10.0.x/10.1.x.
Comment #42
smustgrave commentedSo retested #40 and confirmed on
D10.1.x
Created a block called stark_page_title IN the olivero theme
Installed stark
The created page title block for stark was given machine name stark_page_title_2
So the fix seems to be working only thing missing is a test case. The manual steps I did above should be good for one.
Tagged for IS update so it can be included that _2 will be added to conflicting blocks. Easier for the committer later.
Think this is very close good job everyone!
Comment #43
mansi agarwal commentedadded patch against #40 in 10.1.x
Comment #44
smustgrave commentedThank you for your interest but the patch in #40 applies to 10.1 so 43 was not needed
Comment #45
WebbehHiding unnecessary re-roll in #43.
Resolving Issue Summary update to fix feedback in #41.
Comment #46
tobiasbI added the test into
\Drupal\Tests\block\Kernel\NewDefaultThemeBlocksTest::testNewDefaultThemeBlocksand also add native typehint for\Drupal\block\BlockMachineNameGeneratorInterface::getUniqueMachineName.Comment #47
tobiasbI merged the test into the existing test.
Comment #48
_utsavsharma commentedFixed CCF for #47.
Please review.
Comment #49
smustgrave commented@tobiasb thank you for the tests!
FYI for test time you can attach a test-only patch and then a full patch so we can see the test-only fail and the full patch pass.
Issue summary makes sense for what's being accomplished
Tested on 10.1.x by:
With stark uninstalled
Created a block called stark_page_title with matching machine name
Installed stark
Verified custom block stark_page_title was created with machine name stark_stark_page_title
Starks Page title was created wtih machine name stark_page_title_2
+1 from me.
Comment #51
smustgrave commentedRandom ckeditor5 failure.
Comment #52
xjmThanks for working on this data integrity issue!
Just documenting here that it's potentially OK to change the constructor so that the new service is required without a deprecation, since there is a factory method.
This should have a return typehint.
This is a bit grammatically weird. I'd suggest:
I would add "based on a suggested string" to this docblock. It's not a randomly generated ID.
Again, I'd add "based on a suggested string."
"ID" should be capitalized.
I have never seen us add use statements for global PHP functions like this before. The only place anything like this in core exists is in the annotation component, which is copied from Doctrine and not our code. Let's remove them.
I'd expand this to say "with machine names that will collide" to make it clear that this is intentional.
This comment should be wrapped at 80 characters. Also, I'd suggest:
Last two points aside, I think it was actually better to have a dedicated test for the behavior of the two blocks colliding. (In general, we should avoid coupling the test coverage of different aspects of the API when possible.)
Thanks!
Comment #54
xjmI'm actually halfway toward changing my mind on point 1; maybe we should default the services to NULL and raise a deprecation message. Form controllers are internal, but the change will break e.g. CacheBlockForm. So having a change record right there for them would be an improvement. If we do add that, though, it does not require a deprecation test. Reference:
https://www.drupal.org/about/core/policies/core-change-policies/drupal-d...
Either way the issue is minor-only (so 10.1.x-only for now) and needs a change record also.
Thanks!
Crediting @larowlan who helped me think through the consequences of the constructor change.
Comment #55
smustgrave commentedAttempted to address all points in #52
Took a shot at the CR also.
Comment #56
smustgrave commentedphpcs fix
Comment #57
smustgrave commentedComment #58
smustgrave commentedComment #59
smustgrave commentedRemoved line from phpstan that was causing issue.
Comment #61
smustgrave commentedFixed wrong function call.
Comment #62
xjmAs I said above, let's change this so that the Machine name generator defaults to NULL and is loaded from the static container when NULL.
We could also use constructor property promotion here. Do this:
Then we would not need the class member documentation about it.
Comment #63
smustgrave commentedLike this?
Comment #64
xjmLooks like there's a bunch of out-of-scope stuff in the patch, and it's missing the deprecation for the NULL value of the service. I have a colleague who is working on a fix and will upload it shortly.
Comment #65
fabiansierra5191 commentedThanks @xjm @smustgrave
I uploaded the Fail patch and the proper patch with the modifications and the grammar changes that the previous patches don't include
Comment #66
smustgrave commentedCould you upload an interdiff to see the changes please
Comment #67
smustgrave commentedAlso #62 talks about adding
protected ?BlockMachineNameGeneratorInterface $machineNameGenerator = NULLdoes that still need to happen?Comment #68
fabiansierra5191 commentedsorry, I forgot to add the interdiff again the #61 patch because the #63 has a bunch of out-of-scope stuff.
And as I mentioned it applied the grammar changes suggested and the deprecation for the NULL value of the service with the
trigger_errorthat has the change record linkComment #69
fabiansierra5191 commentedComment #70
smustgrave commentedThanks!
Think the only thing missing is the last part of #62
Comment #71
xjmContrary to what I said in #62, for now we should only use constructor promotion in
BlockMachineNameGenerator. We still have the issue that the existing parameters aresnake_caseand the class members are supposed to belowerCamelCaseand we're not supposed to mix both in the same file. There's an open coding standards issue to revisit that policy, but it hasn't been adopted yet. So for now, only wholly new classes should use constructor promotion.Thanks @smustgrave and @fabiansierra5191. I think this is close! A few more things:
This looks good. The one thing we still need to do here is update the change record to mention the new machine name service and that it's now required here.
Sorry, I should have been more clear. This sentence needs to be the other way around:
Is there any particular reasoning behind making this final? Why have an interface if the core implementation is final?
"The entity type manager service."
Another "E.g.". This should be:
I should have caught this before, but as class members, these should use lowerCamelCase.
Nit: "Checks that".
I was reminded today that folks find "i.e." and "e.g." confusing, so we should say "For example" instead.
Reference: #2684683: [meta] Replace i.e. and e.g. with English words
I think the second comment here is mostly just repeating the first, so we can remove the second one.
This isn't still quite there... it needs to be "Checks that a theme block..."
Is there any reason to store
test_themeto a variable, rather than using it as a string literal? It's the exact same number of characters to type either way, and actually more when the variable has to be concatenated into strings."The test_theme_user_login_block machine name is already in use, so therefore..."
Comment #72
xjmOh, and since this defaults to null, it needs to be typed as
BlockMachineNameGeneratorInterface|nullin the docs and typehinted with nullable?BlockMachineNameGeneratorInterfacein the signature.Comment #73
fabiansierra5191 commentedThanks @xjm for the feedback,
I did all changes suggested except for the bullet 11 on the comment #71
test_themevariable is been using to replace the new theme in various places and I think we can keep it.Comment #74
smustgrave commentedFrom what I can see points in #71 and #72 have been addressed
Good job!
Comment #75
alexpottHaving a swappable block machine name generator service feels interesting. I'm not sure about this being part of the API. Also having {@inheritdoc} as a class doc feels wrong. Note sure I've seen that before.
Plus I wonder how this fits in with
#1643386: Strip useless "_" at beginning and end of JS-transliterated machine names which is pointing towards a need to consolidate the various places we generate machine names in core.
Do we need to add an accessCheck() method here?
Comment #76
fabiansierra5191 commentedThanks @alexpott for the feedback!
I addressed your points by changing the comment for
BlockMachineNameGeneratorclass and setting theaccessCheck()method.Hope this one is the one!
Comment #77
smustgrave commentedAppears #75 was addressed
@alexpott looking at #1643386: Strip useless "_" at beginning and end of JS-transliterated machine names seems to be removing a trailing _
Maybe they could adopt the solution we did here but that ticket may stalled as it hasn't been touched in 3 weeks.
Comment #78
fabiansierra5191 commented.
Comment #79
fabiansierra5191 commentedWrong file, so ticket moved to previous status
Comment #80
bnjmnmWhile I'm not 100% which patch is RTBC, all of the patches provided after @alexpott feedback are tripping "Custom commands failed" so that should be addressed before this can again be RTBC'd.
Comment #81
smustgrave commentedI think one of the weekly retests caused a phpstan error. Definitely wouldn't of marked RTBC if it was showing that before. So it's new.
Comment #82
xjm#76 is the correct patch; #78 reverts one of the changes from it.
The failure on #76 is strange:
I think what this actually means is that the baseline is expecting an error there, and this patch is fixing it. So, we should try removing this hunk from the baseline as well and see if that resolves the issue:
I have someone working on this now.
Comment #83
schlaukopf commentedI'm working on this
Comment #84
schlaukopf commentedNew changes were introduced on core/modules/block/src/BlockForm.php, so I did a rebase from #76 and fixed a conflict, and then I removed the line on core/phpstan-baseline.neon that was causing phpstan to fail.
Comment #85
schlaukopf commentedComment #86
xjmThanks @schlaukopf! Testing locally, this seems to resolve the PHPStan issue, and also addresses the merge conflict introduced by #3061266: Prefix block machine name suggestions with the theme machine name.
So we now have the following in
core/modules/block/src/BlockForm.php:Question then is, does the logic of adding the theme prefix belong in
BlockForm, or should we inject the theme into the machine name generator?Comment #87
xjmRegarding #75.1 and whether we should consolidate machine name generation APIs, I think that's out of scope for this issue, since this is still fixing a major-possibly-critical data integrity bug. First of all, we'll need separate implementations in JS and PHP regardless -- the JS generates a suggested machine name for the user, but the PHP has to do its own validation either way.
Tagging for a followup issue to explore how we might consolidate the machine name generation APIs.
Comment #88
smustgrave commentedTest #84
With claro + olivero installed
Created a block with machine name stark_page_title
Installed stark theme and get fatal error
Block is not created for stark theme
Applied patch
Repeated steps
Block got created for stark with machine name stark_stark_page_title
Opened #3345038: Possibly consolidate machine name generation API per #87
Comment #89
xjmAs per #86, I still think the theme prefixing from the other issue needs to be moved out of the form controller and into the new API, or we at least need to discuss it. Thanks!
Comment #90
smustgrave commentedMy mistake think I read #86 and #87 as the same
Comment #91
schlaukopf commentedif we want to move the theme prefixing to the new API, I think we need to move this from block_theme_initialize (core/modules/block/block.module) as well
perhaps the whole if/else statement?
Comment #92
smustgrave commentedSo relooking at #86 and I can't think of a reason not to add
if ($block->getTheme()) {
$suggestion = $block->getTheme() . '_' . $suggestion;
}
To the generator. Would make it the 2nd parameter (maybe default it to NULL?)
For #91
That could probably be included also in the generator. Not sure if it that and the if statement above could be combined to do the same?
Comment #93
schlaukopf commentedI moved the theme name as a function parameter, I didn't move the whole if-statement from #91, because I'm not sure if replacing the old theme prefix should apply to all calls to that function.
Comment #95
schlaukopf commentedThis should fix the error in the pipeline
Comment #96
schlaukopf commentedComment #97
schlaukopf commentedThis should fix the error in the pipeline
Comment #98
schlaukopf commentedComment #99
smustgrave commentedThis seems better by moving. Thanks!
Comment #100
lauriiiThe patch in #97 is over 600kb but the interdiffs are 766 bytes 🤔
Comment #101
schlaukopf commentedI forgot to rebase, so this is after #84 where I move the theme prefixing into the new API.
Comment #102
schlaukopf commentedComment #103
smustgrave commentedFace palming myself for missing that. Thanks @laurii!
Followed the same steps in #88
Comment #105
andypostrandom failure
User.Drupal\Tests\user\Kernel\Migrate\MigrateUserStubTestComment #106
larowlanI'm not sure #75 has been addressed, largely the 'do we want a separate service for this' question
Comment #107
larowlanTagging for #106, @tim.plunkett thoughts?
Comment #108
tim.plunkettIt's probably overkill, but it fits with a standard Drupal level of overkill :D
Reviewed the code overall, and again with the lens of #106, and I think this is good to go
Comment #109
longwaveThis could be a new method on the BlockRepository, instead of a separate service?
Comment #110
longwave#101 no longer applies.
Comment #113
rpayanmI got a conflict with
core/phpstan-baseline.neonfile, I check the patch from #101 and removed those lines.Please review.
Comment #114
smustgrave commentedReroll looks good.
Comment #115
larowlanWe have two core committers here questioning the need for a service and an interface for this (#109, #75) and I tend to agree
I agree, it would make sense to put this on the block repository. If we don't expect people to swap this out, there's no need for an interface either.
Comment #117
xjmSaving some reviewer credits.
Comment #120
ericgsmith commented- Rebased onto 11.x
- Moved method onto Block Repository service and removed block name generator service
- Updated deprecation warning
- Fixed test after rebase
Think that covers concerns from #75, #108 and #115 - setting as needs review.
Comment #121
smustgrave commentedCR will also have to be updated
Comment #122
smustgrave commentedComment #123
ericgsmith commentedI've updated the CR - its also ready for review.
I missed that it looks like the pipeline above failed, but its showing as tests pass on the issue itself. Rebasing and running again.
Comment #124
smustgrave commentedThanks for the CR update!
Comment #125
alexpottCommitted and pushed 8339e0ebe6c to 11.x and 61030f7a4af to 10.2.x. Thanks!
Comment #128
mpotter commentedHere is a roll of this patch for D10.1.x that removes the `core/phpstan-baseline.neon` change that has already been applied to core.