Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
The form_select_options() function will generate optgroup tags if it's $choices
parameter contains nested arrays, but this functionality is not documented.
Proposed resolution
Improve the docblock for the form_select_options()
function.
Remaining tasks
- Write a patch for 8.x
- Review and RTBC
- Commit to 8.x
- Write a patch for 7.x
- Review and RTBC
- Commit to 7.x
User interface changes
None.
API changes
None.
Comment | File | Size | Author |
---|---|---|---|
#29 | drupal7_document_how_optgroups-2283675-29.patch | 3.05 KB | er.pushpinderrana |
#21 | document_how_optgroups-2283675-21.patch | 3.09 KB | er.pushpinderrana |
#21 | interdiff-2283675-19-21.txt | 1.04 KB | er.pushpinderrana |
#19 | interdiff-2283675-14-19.txt | 5.97 KB | amitgoyal |
#19 | document_how_optgroups-2283675-19.patch | 3.09 KB | amitgoyal |
Comments
Comment #1
jmmarquez CreditAttribution: jmmarquez commentedI start with this
Comment #2
jmmarquez CreditAttribution: jmmarquez commentedComment #3
jmmarquez CreditAttribution: jmmarquez commentedUploading patch.
Comment #4
mparker17This is a good step forward, but there are two problems:
template_preprocess_select()
(D8) ortheme_select()
(D7)), or they could not read the code (e.g.: they saw the documentation in an editor tooltip), references to the#options
array would not make sense.This function takes two parameters,
$element
and$choices
. While it's possible to pass an#options
array in the$element
parameter, that functionality is not documented, and$element['#options']
is only used if the$choices
parameter is empty.EDIT: this argument is pretty weak now that I re-read the full documentation for the function. You may ignore it. Sorry :S
This is especially important because it works opposite to the way normal options work: normally the key is a hidden machine-name and the value is the human-friendly name. For optgroups, however, the key is the human-friendly name and the value is an array:
Plus, the optgroup tag isn't very widely-known, so people might not even know it needs a label, meaning they might try to write something like this (which would fail):
Comment #5
mparker17This patch attempts to address my concerns in #4.
Comment #6
jhodgdonThanks for the patches! A few things to fix:
a)
Should be "an optgroup"... but... maybe it should be "each optgroup within the list of options"?
b)
Boolean should be capitalized. Also, there is a specific list syntax for optional items: https://www.drupal.org/node/1354#lists -- and in lists like this, we normally don't put the element keys inside ''.
c)
This second line should be wrapped in the same paragraph as the first. Later down in this section, there's another wrapping problem. Wrap it all as one paragraph; do not have a line break after each sentence in the paragraph.
d)
This doesn't explicitly say that the string is used as the label attribute of the group... not sure if that is too obvious to mention? Seems like a good idea to mention?
e)
Needs serial comma before "or".
f)
Should say (optional) at the beginning of the description here. And at the end "passed" ==> "passed in". I think it should also say that $choices should be the same format as $element['#options'], right?
Hmmm....
So looking at the code for this function, it looks to me as though $choices is initially set to $element['#options'], the first time through (assuming $choices isn't passed in explicitly, which it shouldn't be). Then for each recursive call, it goes through the $choices array
1. If $choice is an array (indicating an opt group), then $choice is passed in as $choices in the recursive call.
2. If $choice is an object with an ->option property, then it calls form_select_options($element, $option->option)
3. Otherwise, $choice should be a string, and it makes an option with this as the label.
So. Given that, it seems like we should document that:
- $element['#options'] has to be an key/value array
- The array values can either be strings, arrays, or objects with ->option properties
- If the array value is itself an array, each element in the array has to have the same format as $element[#options]
- if the array value is an object, then the ->option property has to have the same format as $element[#options]
- $choices has to have the same format as $element[#options], but should be omitted in the initial call
Note: I do not think $choices can be an object. It has to be an array or omitted/NULL.
g) It looks like the eventual optgroup labels, select keys, and select labels, are passed through String::checkPlain(). This should be noted, because it's important to avoid double-sanitization.
Comment #7
joachim CreditAttribution: joachim commentedThis recent SA https://www.drupal.org/SA-CORE-2014-003 suggests that we should specify that the group labels, if they are translated, should NOT use placeholders that sanitize data. Doing so will lead to double-escaping.
EDIT: the 7.29 release notes confirm this: group labels should not be sanitized, as core takes care of this.
Comment #8
mparker17Here's a new patch that fixes everything mentioned in #6 and #7. Feedback welcome!
Comment #9
mparker17Whoops, needs review.
Comment #10
joachim CreditAttribution: joachim commentedLooking good, but a few things to clear up and clarify:
A*n* associative
*an* array
by 'mapped to', do you mean that the value is a translated string? That's not terminology I've seen used for PHP associative arrays.
How about: 'A [...] key whose value is [...]'
Also, 'string or integer' is redundant surely. Array keys can only be strings or integers. And the option type that allows an optgroup... a translated string is still just a string. It could even be a number!
'which', not 'to'. The ! placeholder is acceptable and may be needed.
I'd say 'sanitize it if based on user input'. Most of the time it's not -- it's things like node ids, entity type names, etc, which are defined in code.
Should say, just to be totally clear, that the array itself follows the syntax of #options. Or does it? I presume you can't nest groups, so it can only be the plain type?
We don't need to say 'the function encounters'. It's unnecessarily wordy. Also, it doesn't follow the pattern of the list items above it.
If the key is ignored, what ends up being the key for the option? What happens to other items that are in #options? Is that just something you shouldn't do? ie, if you use the object version, that should be the only item in #options?
Is that right? Isn't it #default_value? IIRC you get #value only when the form is being validated or something like that.
Also, #value and #dv are both standard across FormAPI elements, so do they need repeating here?
Comment #11
mparker17Thanks for the grammar review. A few responses...
#options
array, a malicious user could still edit the DOM or formulate a malicious POST request. Browsers don't enforce that the value of the POSTed data match the options in the original HTML page, and Drupal doesn't enforce that the value of a select box match the set of options it sent. I can provide an example if you wish.#options
.' is more clear....would that be better? Suggestions for a better way to communicate this case would be very much welcome (see also point 8, next)
$value->option
in the same way it does when$value
is an array (in the optgroup case) EXCEPT that it doesn't create anoptgroup
: it inserts the options from the array at the same level.#value
instead of#default_value
— it does not work the way you expect. See http://cgit.drupalcode.org/drupal/tree/core/includes/form.inc#n876 . Also note that this not a FormAPI element itself, it's a helper function used by a Form API element for building part of the HTML.... I'll make a new patch with the above changes soon.
Comment #12
joachim CreditAttribution: joachim commented> and Drupal doesn't enforce that the value of a select box match the set of options it sent
Really? I thought that was one of the major points of FormAPI -- that you can be sure that form values are from the options in the form.
Comment #13
mparker17Whoops, you're right: see providerTestPerformRequiredValidation() in https://api.drupal.org/api/drupal/core!tests!Drupal!Tests!Core!Form!Form...
Comment #14
mparker17This fixes #10 points 1 through 6. It doesn't address points 7 and 8 because I don't really know how to word that.
Comment #15
joachim CreditAttribution: joachim commentedSo about the object malarkey -- have I understood this correctly:
will give me 3 options, at the same level, which are foo, bar, biz?
(BTW, I've never seen this object syntax for options in practice; I wonder what its use case is!)
Comment #16
mparker17Yes, that's correct. The code for the object case is:
... while the code for the array case is nearly the same, except the result of the recursive call are wrapped in an optgroup tag:
Comment #17
joachim CreditAttribution: joachim commentedWeird!
Also, I can't find a single use of it in core -- at least, "ack '\->option\b'" only comes up with tests. I'm betting this is some weird archaeological remain from something like D5!
EDIT: Bingo! https://www.drupal.org/node/24023#comment-329942
Git blame is a wonderful thing :)
Also: https://www.drupal.org/node/24023#comment-329940 HAHAHAHA.
Comment #18
jhodgdonSorry, this review got lost in the shuffle...
So, I took a look at the preceding comments, and the text in the patch. I think the text adequately explains this weirdness.
A few minor problems in the patch to clear up:
a) List formatting -- in the sub-lists:
The subsequent lines here need to be indented 2 more spaces. This is true for all of the lists at this level.
b)
which -> that
c)
Um... I'm not sure what this last sentence is trying to say. The previous sentence is saying something about not sanitizing, and then here you are saying it needs to be sanitized... probably that last bit about "sanitize it before use" could just be left out (isn't that just generic "how to program in PHP with user input" advice? Doesn't belong here).
d)
This second line should be wrapped with the previous line... or omitted, isn't it redundant?
e)
Shouldn't that be $element['#options']?
Comment #19
amitgoyal CreditAttribution: amitgoyal commentedPlease review revised patch with fixes in #18.
Comment #20
jhodgdonThanks!
One more list formatting update:
The text in the 2nd, 3rd, ... lines should line up under # -- it's a bit to the right of where it should be.
Otherwise, looks perfect!
Comment #21
er.pushpinderrana CreditAttribution: er.pushpinderrana commentedPlease review updated patch with #20 changes.
Comment #24
jhodgdonThanks! Sorry for the delay in reviewing; I was away...
Comment #26
jhodgdonThanks all! Committed to 8.0.x.
Comment #28
jhodgdonOh, looks like we should probably backport this to 7. Does it work the same there?
Comment #29
er.pushpinderrana CreditAttribution: er.pushpinderrana commentedPlease review attached patch for D7 as it appears to be same because the same element exist.
Comment #30
jhodgdonAgreed, D7 appears to be exactly the same. Thanks for the patch!
Comment #31
jhodgdonThanks again everyone! Committed to 7.x.