Problem/Motivation
Notice: Undefined index: #item in user_user_view_alter() (line 409 of /Users/jungle/repo/drupal/web/core/modules/user/user.module) #0
The line 409 of the file core/modules/user/user.module
:
$item = $build['user_picture'][$key]['#item'];
The background is that I was Implementing the avatar_field_formatter module, found that the #item in user_user_view_alter() dose not exist. As a custom image formatter, what the module does is if the image is empty, display an avatar letter generated instead.
The user_user_view_alter implementation in core assuming that the returned non-empty render array of user picture's field formatter always has #item key(s). Considering another use case, for an empty user picture, one wants to display a plain text: "No avatar" by using a custom field formatter. It may throw the same error too.
The whole function relevant:
/**
* Implements hook_ENTITY_TYPE_view_alter() for user entities.
*
* This function adds a default alt tag to the user_picture field to maintain
* accessibility.
*/
function user_user_view_alter(array &$build, UserInterface $account, EntityViewDisplayInterface $display) {
if (user_picture_enabled() && !empty($build['user_picture'])) {
foreach (Element::children($build['user_picture']) as $key) {
$item = $build['user_picture'][$key]['#item'];
if (!$item->get('alt')->getValue()) {
$item->get('alt')->setValue(\Drupal::translation()->translate('Profile picture for user @username', ['@username' => $account->getAccountName()]));
}
}
}
}
Steps to reproduce
- Apply patch: 3072305-11-test-only.patch in #11
- Add
$settings['extension_discovery_scan_tests'] = TRUE;
to settings.php - Install Image test module (module machine name: image_module_test)
- Login as user 1
- Visit /admin/config/people/accounts/display
- Select Dummy image formatter for Picture field
- Visit /user/1, 500 error, reproduced.
Proposed resolution
Check the existence of #item first, and make sure it's an instance of ImageItem for further processing
+++ b/core/modules/user/user.module
@@ -416,9 +417,14 @@ function user_user_view(array &$build, UserInterface $account, EntityViewDisplay
foreach (Element::children($build['user_picture']) as $key) {
+ if (!isset( $build['user_picture'][$key]['#item'])) {
+ continue;
+ }
$item = $build['user_picture'][$key]['#item'];
- if (!$item->get('alt')->getValue()) {
- $item->get('alt')->setValue(\Drupal::translation()->translate('Profile picture for user @username', ['@username' => $account->getAccountName()]));
+ if ($item instanceof ImageItem) {
+ if (!$item->get('alt')->getValue()) {
+ $item->get('alt')->setValue(\Drupal::translation()->translate('Profile picture for user @username', ['@username' => $account->getAccountName()]));
+ }
}
Remaining tasks
Needs review
How to test the patch manually
- Apply patch: 3072305-11.patch in #11
- Add
$settings['extension_discovery_scan_tests'] = TRUE;
to settings.php - Install Image test module (module machine name: image_module_test)
- Login as user 1
- Visit /admin/config/people/accounts/display
- Select Dummy image formatter for Picture field
- Visit /user/1, you should see text Dummy which provides by the Dummy image formatter in Image test module.
User interface changes
No
API changes
No
Data model changes
No
Comment | File | Size | Author |
---|---|---|---|
#54 | interdiff-51-54.txt | 1023 bytes | jungle |
#54 | 3072305-54.patch | 3.92 KB | jungle |
Comments
Comment #2
jungleComment #3
jungleComment #4
jungleComment #5
jungleComment #6
jungleComment #7
lawxen CreditAttribution: lawxen commentedCall to a member function get() on null in user_user_view_alter() (line 410 of /app/www/cmd/web/core/modules/user/user.module)
My error is on line 410, not 409 .
Comment #8
lawxen CreditAttribution: lawxen commentedBTW @jungle thanks for writting the module [avatar_field_formatter](https://www.drupal.org/project/avatar_field_formatter), very useful.
But
This description on [avatar_field_formatter](https://www.drupal.org/project/avatar_field_formatter) is not accurate.
Because "If we want display user picture on user entity view page" is the only condition that we should apply this patch.
Even if we use views to display user's picture and use module
[avatar_field_formatter](https://www.drupal.org/project/avatar_field_formatter)'s widget, we don't have need to apply the patch either.
Comment #10
aleix CreditAttribution: aleix commentedIt's true, children 'alt' property must be checked before getting the value.
Comment #11
jungleComment #12
jungleUpdated issue summary
Comment #14
jungleFix coding standard
Comment #15
jungleComment #16
longwave#2907329: Add test that user_user_view_alter works with different formatters seems to be the same issue and has a similar fix but simpler test.
Comment #17
jungleThanks for reviewing! They not the same.
The
dummy field formatter
for theimage field type
, return a simple text. which simulates what my module avatar_field_formatter does, when the image is empty, it returns a render array does not relevant to an image at all. So nothing related to thealt
attribute of an image here.Comment #18
jungleTwo scopes, the patch of #2907329 does not fix this one, and patch here does not fix #2907329 either.
Comment #20
jungleRe-rolled the patch against 9.1.x, tagging "Bug Smash Initiative". Re-formatted "Steps to reproduce".
Comment #21
jungleUploading a new(re-rolled) test-only patch.
Comment #22
thursday_bw CreditAttribution: thursday_bw as a volunteer commentedGave that a quick once over. It's neat and clean and straight forward.
Only some small things to consider.
Possible to refactor this into a kernel test?
If your test implements formInterface, you can generate inputs the the build() method and submit it, in a kernel test
Relexively I bork at the need for instanceOf, we are in a procdural function though. I imagine the refactoring necessary to avoid it would be heavy
Comment #23
thursday_bw CreditAttribution: thursday_bw as a volunteer commentedComment #24
jungleThanks, @thursday_bw for your review.
Re:
instanceof
against a class instead of an interface, however, no applicable interface to replace it here.After chatting with @thursday_bw in slack, he agreed to set back to NR.
Comment #25
andypostExcept nitpick looks good to go
It's more readable when
Comment #26
larowlanwe can do this as
and avoid instanceof, which is generally a code-smell that indicates a missing abstraction.
But in this case, we have that abstraction, its getProperties
Which is the same thing @thursday_bw flagged too
Comment #27
thursday_bw CreditAttribution: thursday_bw as a volunteer commentedThe vulnerable aspect of testing against instanceof is it prevents imageItem from being extended or replaced by a new class. eg
You do have access to all the methods of the fieldItemInterface. That opens up some possibilities.
You can also easily add a new interface.
Should someone do something like this, then test against instanceof 'ImageItem' will fail, and produce an error.
Recommendation 1
If you instead test against the object's capabilities then the person extending imageField has more control over how your
code deals with. But it wont' break it purely by overriding the class.
You'll need to dig into what data you have available for you to most flexibly test against.
Non functional, non tested example:
Recommendation 2
Another option, that may have similar drawbacks is the following:
Add an interface for imageItem that extends fieldItemInterface
Create the interface:
Implement the interface:
Then you could test that the object implements the ImageItemInterface. like this:
Now ImageInterface can still be happily extended, and you're not writing ugly code that analyses the field settings.
Comment #28
thursday_bw CreditAttribution: thursday_bw as a volunteer commentedComment #29
thursday_bw CreditAttribution: thursday_bw as a volunteer commentedAnd @larowlan has honed in on exactly what we needed to know. With much less words too ;)
Comment #30
jungleSwapped the order,
!empty($build['user_picture']
looks less expensivethe above code snippet is from
\Drupal\Core\TypedData\ComplexDataInterface::get()
Meanwhile,
interface FieldItemInterface extends ComplexDataInterface {
As
$item
is an instance ofFieldItemInterface
, so calling$item->get('alt')
directly.if ($item && array_key_exists('alt', $item->getProperties)) {
Somehow,
$item->getProperties
returnsNULL
, which makes thearray_key_exists()
call complaining that the 2nd parameter could not be NULLThanks, @lawxen for reviewing.
if ($item->get('alt')) {
works.Thanks, @thursday_bw for reviewing again.
Comment #32
larowlanLooking nice.
micro-optimisation, but we're making two requests to the same URL here because $this->drupalLogin submits to user/login, which is UserLoginForm, which in term does this
i.e. goes to the $this->webUser->toUrl(), which we then do again.
So if we setup the view display first, then did the login, and then asserted we were on the user page (to ensure we are on the correct page) we'd avoid one extra http request.
this is a change in behaviour now.
We need
$item->get('alt') && !$item->get('alt')->getValue()
the original code only runs if there is no value, now we're running it in all cases.
Comment #33
jungleAddressing #32 and fixing the testing. Thanks for your feedback @lawxen.
Comment #34
andypostI'd extended a test to make sure that "Dummy" is displayed
Comment #35
jungleRe #34, good point. addressing #34. Thanks @andypost!
Comment #36
jungleA test-only patch inlines with the patch in #35.
Comment #37
jungleHidden the patch file unintentionally, displaying it.
Comment #39
andypostThanks, test patch just should not be last
Comment #40
longwaveNit: inheritDoc -> inheritdoc
This isn't a great description, should we explain why we actually need a dummy formatter test?
Comment #41
jungleFixing #40, thanks @longwave!
Comment #42
jungleAdjusted a little bit further for #40.2
Comment #43
andypostTo address #32.2 - no reason to change it if we sure that element instanceof
ImageItem
which is only defined by standard profile.Maybe be can move it there or refactor as
user_picture_enabled()
not used anymore, looks needs follow-upPatch installs the testing module for only this case (to not affect existing)
Comment #44
andypostwrong patch and interdiff (other test cases were disabled for debug)
Comment #45
andypoststill needs better wording(
Comment #46
jungleThis change is great.
Tagging needs follow-up.
Comment #47
andypostComment #48
jungleTests dummy image formatter with the user picture field
? and changetestUserViewAlter()
totestDummyImageFormatter()
?I doubt that the change should be reverted. See interdiff-30-33.txt, in #33
Comment #49
andypostFiled another task follow-up #3151555: Deprecate user_picture_enabled() reconsider user picture field
Also I see similar changes discussed in #2844302-19: Move Field Layout data model and API directly into \Drupal\Core\Entity\EntityDisplayBase (last 11 point)
Comment #50
longwave"Tests user picture field with a non-standard field formatter"?
Comment #51
jungle@longwave, thanks for your review and suggestion. Changed to what you suggested.
Comment #52
longwaveLooks great!
Comment #53
larowlannit: 'User picture field is provided by standard profile install, skip if the render using non-image field item which may miss alt property' is not grammatically correct.
Suggest 'User picture field is provided by standard profile install. If the display is configured to use a different formatter, the #item render key may not exist, or may not be an image field.'
Or similar
Feel free to re-RTBC if the patch only changes the comment.
Comment #54
jungleThanks @lawxen. Changed as suggested, re-RTBC-ing.
Comment #55
larowlanSaving review credits
Comment #59
larowlanCommitted aece631 and pushed to 9.1.x. Thanks!
As this is a bugfix, and there is no disruption or string changes and we have green runs on 9.0 and 8.9, cherry-picked to those branches too.
Thanks everyone