Problem/Motivation

In #2111887: Regression: Only title (of base fields) on nodes is marked as translatable and other discussions about making node base field widgets use the real widget system, it was decided that the widgets should appear on the top level of the form and not nested under fieldsets while the form is being built. The structural wrapping should be added later, like fieldgroup does. This is a pre-requisite to both #2111887: Regression: Only title (of base fields) on nodes is marked as translatable being able to identify elements to add label markers to (on translatability) and also the base fields to be able to have configurable widgets.

Proposed resolution

Move all base fields to the top level and have them equal names to the base fields.

details

core/modules/node/lib/Drupal/node/NodeFormController.php form()

change was is new group
moved $form['revision_information']['revision']['revision'] $form['revision'] revision_information
moved $form['revision_information']['revision']['log'] $form['log'] revision_information
renamed and moved $form['author']['name'] $form['uid'] author
renamed and moved $form['author']['date'] $form['created'] author
moved $form['options']['promote'] $form['promote'] options
moved $form['options']['sticky'] $form['sticky'] options

Remaining tasks

(done) Code it.
(done) Make sure it works.
Review.

User interface changes

None. It should work/look the same.

API changes

The node form structure would drastically change. All configurable fields are already on the top level with their field names. The base fields would have the same setup.

CommentFileSizeAuthor
#100 2177469-move-node-base-widgets-top-100.patch20.07 KBGábor Hojtsy
#100 interdiff.txt744 bytesGábor Hojtsy
#98 interdiff.txt2.49 KBGábor Hojtsy
#98 2177469-move-node-base-widgets-top-98.patch20.02 KBGábor Hojtsy
#96 interdiff.txt530 bytesGábor Hojtsy
#96 2177469-move-node-base-widgets-top-96.patch21.56 KBGábor Hojtsy
#92 2177469-move-node-base-widgets-top-92.patch21.19 KBGábor Hojtsy
#92 interdiff.txt3.45 KBGábor Hojtsy
#86 2177469-move-node-base-widgets-top-86.patch18.75 KBandypost
#86 interdiff.txt2.28 KBandypost
#81 interdiff.txt1.9 KBGábor Hojtsy
#81 2177469-move-node-base-widgets-top-81.patch20.44 KBGábor Hojtsy
#68 interdiff.txt632 bytesGábor Hojtsy
#68 2177469-move-node-base-widgets-top-68.patch19.43 KBGábor Hojtsy
#67 interdiff.txt1.42 KBGábor Hojtsy
#67 2177469-move-node-base-widgets-top-67.patch19.22 KBGábor Hojtsy
#52 2177469-move-node-base-widgets-top-52.patch18.53 KBGábor Hojtsy
#50 2177469-move-node-base-widgets-top-50.patch17.69 KBGábor Hojtsy
#48 interdiff.txt2.87 KBGábor Hojtsy
#48 2177469-move-node-base-widgets-top-48.patch17.39 KBGábor Hojtsy
#43 interdiff.txt1007 bytesGábor Hojtsy
#43 2177469-move-node-base-widgets-top-43.patch16.61 KBGábor Hojtsy
#39 2177469-move-node-base-widgets-top-39-interdiff.txt3.62 KBBerdir
#39 2177469-move-node-base-widgets-top-39.patch16.51 KBBerdir
#37 2177469-move-node-base-widgets-top-36.patch15.25 KBGábor Hojtsy
#35 interdiff.txt2.36 KBGábor Hojtsy
#35 2177469-move-node-base-widgets-top-34.patch15.26 KBGábor Hojtsy
#32 2177469-move-node-base-widgets-top-32.patch13.18 KBGábor Hojtsy
#25 2177469-move-node-base-widgets-top-25.patch13.18 KBAron Novak
#19 2177469-move-node-base-widgets-top-19.patch13.18 KBAron Novak
#18 2177469-move-node-base-widgets-top-18.patch14.87 KBAron Novak
#14 interdiff.txt5.39 KBswentel
#13 2177469-13.patch6 KBswentel
#9 2177469-move-node-base-widgets-top.patch4.53 KBAron Novak
#1 node-base-fields-top-form-elements.patch4.06 KBGábor Hojtsy
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Gábor Hojtsy’s picture

Status: Active » Needs review
FileSize
4.06 KB

I talked to several people but I think the challenge of the whole scope of this issue is under-estimated :) So anyway, here is a starter patch to talk about.

1. I've added a pre_render callback to the form to group the elements into the sections. So this is called after form alters, so all form alters see the form as-is with the base fields on the top level.
2. I left the section definitions in the base form generation code so (a) they can be altered (b) the process functions run on them, so they actually end up in the advanced section (ie. they get their parents/etc. properly set).

Now the problems :)

I countered a problem with the date field, since somehow the $node->date object gets all the way to the value="" printing in Attribute.php and since the date class as no printed() method, it will not work. I worked around this for now by casting the date to a string right in the form generation. It is unclear to me why this is not happening if we define the date field one level deep but happens if we define on the top level.

The containers are used as access checking as well on the form. Now that the elements are "divorced" from their containers, I'm wondering if this will be 100% ok for access checking. Eventually it gets together well obviously, but there is no compound structure in the form process now.

Finally, the pre-render callback looks quite ugly :D I added checks for all elements, so if they are altered out, we don't want to put them into the sections. And then just lots of repetitive moving things around.

Feedback needed please!

Gábor Hojtsy’s picture

Note that I also did not rename the form elements yet to avoid needing more changes while we discuss this base solution. Then we can rename, which is the easier part hopefully :)

Gábor Hojtsy’s picture

Issue tags: +D8MI, +sprint, +language-content

Adding D8MI tags because this is an important dependency for node property multilingual changes.

Status: Needs review » Needs work

The last submitted patch, 1: node-base-fields-top-form-elements.patch, failed testing.

sushyl’s picture

Status: Needs work » Needs review
Berdir’s picture

  1. +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
    @@ -184,12 +184,12 @@ public function form(array $form, array &$form_state) {
         );
    ...
    +    $form['date'] = array(
           '#type' => 'textfield',
           '#title' => t('Authored on'),
           '#maxlength' => 25,
           '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => !empty($node->date) ? date_format(date_create($node->date), 'Y-m-d H:i:s O') : format_date($node->getCreatedTime(), 'custom', 'Y-m-d H:i:s O'), '%timezone' => !empty($node->date) ? date_format(date_create($node->date), 'O') : format_date($node->getCreatedTime(), 'custom', 'O'))),
    -      '#default_value' => !empty($node->date) ? $node->date : '',
    +      '#default_value' => !empty($node->date) ? (string) $node->date : '',
    

    The reason this is weird is because you haven't converted datetime_form_node_form_alter() yet. $node->date is an object if it is enabled and it then changes the #type of this.

  2. +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
    @@ -208,22 +208,64 @@ public function form(array $form, array &$form_state) {
    +  public function preRenderStructure($form) {
    +    // Promotion options.
    +    if (isset($form['promote'])) {
    +      $form['options']['promote'] = $form['promote'];
    +      unset($form['promote']);
    +    }
    

    Unsetting a form element to hide is wrong. What shold be done is setting #access.

    Not sure if we want to make the code complicated by supporting that things can be unset.

Status: Needs review » Needs work

The last submitted patch, 1: node-base-fields-top-form-elements.patch, failed testing.

Aron Novak’s picture

Assigned: Unassigned » Aron Novak
Aron Novak’s picture

Status: Needs work » Needs review
FileSize
4.53 KB

let's give it a try, field renaming and some beauty fixes are still needed

Aron Novak’s picture

Issue tags: +SprintWeekend2014
swentel’s picture

Lik berdir said, the unsetting is really something we shouldn't be doing here.

What if we explore whether form_process_group() can also work on the details type, that way elements can tell themselves in which group they belong ?

Berdir’s picture

Yeah, just being able to set a property to define where it should be moved to would be great.

swentel’s picture

FileSize
6 KB

Much saner solution. We could add this 'group' rendering to any element basically. This gives us more or less programmatically field group functionality.

Rendering and submission of node works fine.

swentel’s picture

FileSize
5.39 KB

And the interdiff. Forgot to update the date element, but want to hear first whether this approach is what we want or not.

yched’s picture

@Gabor's code was not hiding an element but moving it to a different place in the $form structure - thus unset() rather than #access IMO.

But anyway, +(a lot) for a form_process_group-like approach :)

What date module is doing is really weird BTW. Just enabling a field-type module switches in a different type for an existing form element :-/. Anyone knows whether we have an issue to clean that up ?

swentel’s picture

Don't think there's an issue for that. I think that switch happened after the decoupling of datetime and node module. They used to be dependant on each other.

To me it feels like datetime module should only be the field, all other stuff should just move to core.

Berdir’s picture

We did that to avoid a hardcoded dependency on datetime from node. No issue to change it, but there *is* an issue to fix it, because it's completely broken and untested right now.

Maybe we can find a nicer solution through widgets.

Aron Novak’s picture

Status: Needs review » Needs work
FileSize
14.87 KB

Thanks for the inputs.
Here's a patch what takes care of renaming the items, as Gabor requested in the initial issue description, to match the base fields, also it uses the #group for relocating, taking care of datetime too.
It needs work as it fails some node tests even at localhost.

I double checked, Drupal\content_translation\ContentTranslationController\addTranslatabilityClue() is executed before the group processing kicks in, so it should be fine.

Aron Novak’s picture

FileSize
13.18 KB

the previous patch was with unix coloring codes...

yched’s picture

@Berdir, @swentel : Yeah, in the end we'd want the date to be simply handled by a widget. The problem is then the different datetime / timestamp field types.

The last submitted patch, 9: 2177469-move-node-base-widgets-top.patch, failed testing.

jibran’s picture

Status: Needs work » Needs review

For testbot.

The last submitted patch, 13: 2177469-13.patch, failed testing.

Aron Novak’s picture

If someone wants to proceed, here are some hints:
the tests fail, because the promote bit's default value is not handled properly when the class in AggregatorBase.php creates test nodes, nodes are unpromoted. It cannot be reproduced by creating nodes at the UI however...
So couple of tests fail because of this, i started to debug, what i see so far is that it goes wrong at ContentEntityFormController::buildEntity(), where promote 0 is set. Not sure why at the moment, as form_state contains good value.

Aron Novak’s picture

Status: Needs review » Needs work
FileSize
13.18 KB

the tests won't pass, so NW still
rerolled because of latest 8.x commits, to make it apply cleanly

The last submitted patch, 19: 2177469-move-node-base-widgets-top-19.patch, failed testing.

Berdir’s picture

The reason for some of those test fails is now that the process stuff happens after the form parents have been set, so the form element is no longer date[date] but just date.

I'd be ok with saying that is by design now, then we simply need to fix those tests :)

yched’s picture

Yup, it's totally by design. The #process approach means form values are now at the top level of $form_state['values'], independently of presentational layout.

That way it's the same for fields handled by widgets and fields handled by custom code. Hopefully it should also simplify some code at submit time ?

Berdir’s picture

In case of date/author not really, because both are a virtual widget (no direct mapping to a field but needs prepare and submit special handling anyway). Maybe we can clean but probably not here :)

But yes, I agree, makes sense to me.

Gábor Hojtsy’s picture

Status: Needs work » Needs review

I think the tests should be updated yup :) Also setting to review to get testbot on this.

Status: Needs review » Needs work

The last submitted patch, 25: 2177469-move-node-base-widgets-top-25.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
13.18 KB

Rerolled because the patch did not apply cleanly anymore.

Status: Needs review » Needs work

The last submitted patch, 32: 2177469-move-node-base-widgets-top-32.patch, failed testing.

The last submitted patch, 32: 2177469-move-node-base-widgets-top-32.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
15.26 KB
2.36 KB

Removed two items as value fields from the form since the other top level fields now overwrite them anyway. (This may be related to some misterious fails, which however will not be resolved by this change).

Fixed two more tests with the date => created field change.

Status: Needs review » Needs work

The last submitted patch, 35: 2177469-move-node-base-widgets-top-34.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
15.25 KB

And here I think I have an up to date checkout (30 minutes old). Not up to date enough anymore for this baby :D

Status: Needs review » Needs work

The last submitted patch, 37: 2177469-move-node-base-widgets-top-36.patch, failed testing.

Berdir’s picture

So, the problem is that the form elements themself don't have #access definitions anymore. So when displaying the form, they're kind of visible but are then moved to the group where they aren't displayed. Then the non-submitted value is preferred over the default value, because the form system assumes they're visible, as it knows nothing about pre_render stuff.

Also removed a bunch of unecessary code that confused me when debugging, as we seem to be cleaning up that form anyway ;)

The only thing that I'm not sure about is if someone is still relying on those old value properties in the form, removing it anyway because I want to know...

Status: Needs review » Needs work

The last submitted patch, 39: 2177469-move-node-base-widgets-top-39.patch, failed testing.

sun’s picture

Hm. A few thoughts on this:

  1. Form element keys/names are seemingly renamed to match their base field names + entity object location (top-level).

    It is not clear to me whether this is a hidden attempt at establishing a 1:1 mapping and dependency between form element names and entity/domain object data models.

    If this is done for convenience reasons only, then that's fine. But if this turns into a requirement, then it is not.

    The form structure and presentation is decoupled from the internal data model. The default assumption for user input in a form is that it is not compatible with the data model and has to be mapped and processed and transformed into the expected data model location and format. That's the job of #value_callback.

    That not only applies to the data model, it also applies to the internal form structure: There is no enforced constraint for the internal location of a form element to be in the same location as the form input value. That's the job of #parents (+ #name + #tree for that matter).

    So if this is an attempt to establish a 1:1 binding, then I cannot support that for architectural reasons and we should seek for alternative solutions.

  2. All form elements are moved to the top-level and there is no more form element hierarchy in the $form structure.

    In other words, the form constructor, #process, and hook_form_alter() are operating on a flat $form array and need to manually decipher the final hierarchy of form elements by introspecting all #group properties that possibly exist (anywhere) in the form.

    It appears that this concept was originally introduced in #1838114: Change node form’s vertical tabs into details elements and further advanced on in #1856178: '#group' Form API property only works with <details> elements.

    I'm very concerned about the DX of constructing, altering, #after_build'ing, #pre_render'ing, theming of forms, because the #group mechanism causes the $form structure to highly vary and be completely different, depending on in which hook/callback facility you're introspecting and accessing it. — Adjusting/enhancing/manipulating forms is still the first and foremost use-case of developers working on custom sites.

    We essentially turned the $form array into a flat, one-dimensional array and moved the entire concept of any kind of hierarchy into #group processing.

    $form->addElement('title', array(...));
    $form->addElement('body', array(...));
    $form->addElement('created', array(...));
    $form->addElement('submit', array(...));
    // It's flat until rendered.
    $form->render();
    

    → The entire hierarchical recursion concept of FormBuilder::doBuildForm() as well as drupal_render() is obsolete with that, because we're operating on a flat array; there are no element children anymore.

    I guess, changing that concept is fine, but what really concerns me is the inconsistency. The node form is the one and only form that implements this concept. All other forms are still using legacy/hierarchical definitions.

    It's great that we're polishing the node form more and more to get it right, but I think we should be seriously concerned about the inconsistent state of the rest of the system.

    → It would be a very good idea to start an effort to convert all forms throughout core to the new concept, so as to ensure and guarantee a good and consistent DX.

  3. Each time I'm looking at the node form code and patches like this here, I have a hard time in figuring out where the group elements themselves are actually defined and coming from.

    I think we should also think about that.

  4. This patch adds #process and #pre_render callbacks to additional form element #type definitions.

    Given that this concept is turned into a de-facto standard approach and algorithm to handle grouping of form elements, it does not make sense to leave that processing to some fragile callbacks that may or may not be defined.

    → The form_process_group() and form_pre_render_group() callbacks should be removed and the concept should be refactored directly into FormBuilder.

I'm aware that parts of this review are beyond the scope of this issue. But given what we're doing here, I have to raise awareness for the architectural big picture — especially since we're shooting for a release at some point.

My impression is that we have good ideas and we're making good progress on the silo that is node form, but I guess what I'm missing is an architectural plan.

I'm not really able to see how we can release D8 in the current, very inconsistent state.

yched’s picture

it is not clear to me whether this is a hidden attempt at establishing a 1:1 mapping and dependency between form element names and entity/domain object data models.

That's what it is, it's not hidden :-). Form elements for *content entity fields* need to be placed at the root of the $form, notwithstanding presentational considerations.
That's :
- if they want to be translatable
- if they want to use Field Widgets
That's the cost of providing generic features - cannot be done if the elements can live at any random place in the $form or $form_state['values'] structure.

That's nothing new either, CCK D6 / core D7 field widgets have always been generated at the top level of the form, with field_groups or similar doing presentational reshuffling only at render time. This never seemed to bug anyone ?
It is also much more friendly for _alter() code, since you don't need to wonder whether some module has already moved the element you're looking for at some other place, depeding on some random configuration.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
16.61 KB
1007 bytes

The individual elements in the revision information group should then have the same access condition. Ie. make the fields available when adding a new revision even if no node admin access. (Same logic as for $form['revision_information'] itself). This fixes at least the page revision test for me.

Status: Needs review » Needs work

The last submitted patch, 43: 2177469-move-node-base-widgets-top-43.patch, failed testing.

tstoeckler’s picture

That's nothing new either, CCK D6 / core D7 field widgets have always been generated at the top level of the form, with field_groups or similar doing presentational reshuffling only at render time. This never seemed to bug anyone ?

There are different use-cases here that need to be consider. If I just want to change the #default_value or the #description of a form element this patch makes that easier because I can just blindly to that in $form[$field_name]['#foo'] and don't need to bother with possible nesting.

Another use-case, however, is changing the *output* structure of the form, i.e. moving form elements into different groups, adding form elements into existing groups, perhaps nesting groups into each other, etc. With fieldgroup.module in conrib this is currently a non-trivial task that requires deep knowledge about the different structures and array keys that fieldgroup module uses. It's not as easy as simply setting/altering $form[$field_name]['#group'] or anything similar.

I have not reviewed the patch in detail but we should consider the latter use-case as well, and make that as easy as possible.

tstoeckler’s picture

In other words: Yes that has been bugging me for a long time!

Gábor Hojtsy’s picture

There are only two sets of test fails now. One is around JS files appearing for anonymous users. Not sure what to do about this, because now even simple text fields get the group behavior and that adds extra JS. We may be able to make that process function more intelligent.

The other set are node translation tests where translations are appear to not be saved with the right author/created date. I'm looking into that, the asserts are bugos to start with :)

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
17.39 KB
2.87 KB

All right, I think I fixed all the bugs:

1. The JS tests were failing since now the group process functions run on elements like textfields and checkboxes. The login form has 2 textfields. The group process function added the form JS to the page right away. This made the "no JS for anonymous" test fail obviously. What I did here is I simply moved the addition of the JS to the pre_render (similar to how other elements already do this), and only add it if the element *had* a group. This works in my manual testing on the node form well (which is full of such groups :D) and now passes the JS test, since the login form has no groups, so no JS is added.

2. The changes in the content translation code were a little bit over-zealous. The content_translation form elements are not renamed in the patch from their current form (not in the scope of this patch!), so the element name for the author is 'name' there still, not 'uid'. So we need to assign from the uid to name in the content translation form processing. Added a comment to make this clear.

3. The change in the test is just cleanup, no behaviour change. The assertEqual() with a value and the assert message was not really doing what it was supposed to (the message was never printed, it was only passing since it also evaluated to TRUE -- the value expected :D).

This passes the tests locally that used to fail here. Let's see :)

Status: Needs review » Needs work

The last submitted patch, 48: 2177469-move-node-base-widgets-top-48.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
17.69 KB

Of course this did not apply anymore, lol. Rerolled.

Status: Needs review » Needs work

The last submitted patch, 50: 2177469-move-node-base-widgets-top-50.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
18.53 KB

#48/#50 did not include the form.inc hunk that was in the interdiff in #48. That causes the remaining fail. The fix was already in the interdiff. So rolled that into the patch finally. This should now pass (if it applies that is).

Berdir’s picture

Looks pretty good. Note that the created/date field on nodes currently has various bugs and almost zero test coverage, see #2031183: Improve test coverage for node authored on widget. We probably don't need to worry about it here and just update that issue once this is in, but I wanted to mention it in case someone does manual tests.

Status: Needs review » Needs work

The last submitted patch, 52: 2177469-move-node-base-widgets-top-52.patch, failed testing.

Gábor Hojtsy’s picture

I don't think that fail is related: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'drupaltestbotmysql.simpletest420182key_value' doesn't exist: DELETE FROM {key_value}...

Gábor Hojtsy’s picture

Status: Needs work » Needs review
YesCT’s picture

@@ -177,7 +164,7 @@ public function form(array $form, array &$form_state) {
-    $form['author']['name'] = array(
+    $form['uid'] = array(

@@ -316,11 +311,11 @@ public function validate(array $form, array &$form_state) {
-      $this->setFormError('name', $form_state, $this->t('The username %name does not exist.', array('%name' => $form_state['values']['name'])));
+      $this->setFormError('uid', $form_state, $this->t('The username %name does not exist.', array('%name' => $form_state['values']['uid'])));

@@ -423,15 +418,15 @@ public function buildEntity(array $form, array &$form_state) {
-    if (!empty($form_state['values']['name']) && $account = user_load_by_name($form_state['values']['name'])) {
+    if (!empty($form_state['values']['uid']) && $account = user_load_by_name($form_state['values']['uid'])) {

is uid really the uid, a number, or a name?

if we are using uid now, then the message might change to:
The user %uid does not exist.
or the %name arg should look up the name from the uid.

-
woah. it's using user_load_by_name... and passing in from the 'uid' element?

(#48 2. bit related)

maybe we should keep it as 'uid'?

YesCT’s picture

So @Berdir said in irc, that it's part of the point of this issue:

Move all base fields to the top level and have them equal names to the base fields.

How do I look up the name of the base field for the 'name' 'uid' in node form?

I see

    $form['uid'] = array(
      '#type' => 'textfield',
      '#title' => t('Authored by'),
      '#maxlength' => 60,
      '#autocomplete_route_name' => 'user.autocomplete',
      '#default_value' => $node->getOwnerId()? $node->getOwner()->getUsername() : '',
      '#weight' => -1,
      '#description' => t('Leave blank for %anonymous.', array('%anonymous' => $user_config->get('anonymous'))),
      '#group' => 'author',
      '#access' => user_access('administer nodes'),
    );

in NodeFormController
But not sure where to look for the "base" of that.
Didn't see anything promising in EntityFormController.

well node_schema() in node.install isn't the place either.

In Node.php in baseFieldDefinitions() is

    $fields['uid'] = FieldDefinition::create('entity_reference')
      ->setLabel(t('User ID'))
      ->setDescription(t('The user ID of the node author.'))
      ->setSettings(array(
        'target_type' => 'user',
        'default_value' => 0,
      ));

That's the "base" field.

Still iffy on this.

YesCT’s picture

Issue summary: View changes

starting to summarize the changes.

Gábor Hojtsy’s picture

@YesCT: yes, the base fields are in the Node entity class in baseFieldDefinitions(). The point of the patch is to have all "widgets" for them use the same name in the form (and be on the top level) to equal the names of the base fields. Configurable fields (any field you add on nodes) are already using this same "widget name equals field name and at top level of the form" setup. This just unifies the approach with base fields.

@Berdir, @yched, @fago: any one of you want to RTBC this while it still applies? :)

Gábor Hojtsy’s picture

BTW wrote a draft change notice at https://drupal.org/node/2189199

Gábor Hojtsy’s picture

yched’s picture

  1. +++ b/core/modules/node/lib/Drupal/node/NodeTranslationController.php
    @@ -56,8 +56,10 @@ public function entityFormEntityBuild($entity_type, EntityInterface $entity, arr
    +      // $form['content_translation']['name'] is the equivalent field
    +      // for translation author uid.
    +      $translation['name'] = $form_state['values']['uid'];
    

    Somewhat ironic :-)

  2. +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
    @@ -34,11 +34,6 @@ protected function prepareEntity() {
         $this->settings = $type->getModuleSettings('node');
    -    $this->settings += array(
    -      'options' => array('status', 'promote'),
    -      'preview' => DRUPAL_OPTIONAL,
    -      'submitted' => TRUE,
    -    );
    

    I don't get how this is related ?

  3. +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
    @@ -87,15 +80,6 @@ public function form(array $form, array &$form_state) {
    -    // Basic node information.
    -    // These elements are just values so they are not even sent to the client.
    -    foreach (array('nid', 'vid', 'uid', 'created', 'type') as $key) {
    -      $form[$key] = array(
    -        '#type' => 'value',
    -        '#value' => isset($node->$key) ? $node->$key : NULL,
    -      );
    -    }
    -
    

    Dunno if it's really needed either, but yay for cleanup, we're not in D5 anymore :-)
    (Field UI still has a bunch of those...)

  4. +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
    @@ -153,6 +138,8 @@ public function form(array $form, array &$form_state) {
    +      '#group' => 'revision_information',
    +      '#access' => $node->isNewRevision() || user_access('administer nodes'),
    

    Hm. So each individual field needs to duplicate the same #access boolean logic than the "group container" it goes into. Not ideal :-/

    - We could say '#access' => $form['revision_information']['#access']

    - We could bake that logic into process_group() and make #access automatically inherit the one set to the group container ? Probably not, magic #access messing sounds dangerous

    - Inherently, the #access really belongs to each and every one of the individual fields, and the group container should just render to nothing if it has no visible children left.

    The latter would IMO be the soundest approach: access is inherently by field, grouping is presentational.

    Not sure how easy it would be - but that might be something field_groups has already had to solve in contrib. #access rules are by field in D7, and I'd think the module tries to not display empty fieldsets if all the fields it contains are hidden ?

  5. +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
    @@ -423,15 +418,15 @@ public function buildEntity(array $form, array &$form_state) {
         // A user might assign the node author by entering a user name in the node
         // form, which we then need to translate to a user ID.
    -    if (!empty($form_state['values']['name']) && $account = user_load_by_name($form_state['values']['name'])) {
    +    if (!empty($form_state['values']['uid']) && $account = user_load_by_name($form_state['values']['uid'])) {
           $entity->setOwnerId($account->id());
    

    The comment could use an update.

    Also, is this really needed now that names match ?
    validate() has already validated that user_load_by_name($form_state['values']['uid']) exists, and the parent::buildEntity() automatically assigns field values from form values if names match.

    Similarly, processing for 'created' below could be simplified ?
    (isn't there an issue to have smart 'created' fields defaulting to REQUEST_TIME or something ? @Berdir ?)

    Also, what if the patch didn't remove the current
    $node->setOwnerId(\Drupal::currentUser()->id());
    $node->setCreatedTime(REQUEST_TIME);
    in prepareEntity() ? Wouldn't that simplify things here as well ?
    I.e. no need to set defaults at submit time if missing from the submitted form values, fill in defaults directly in the entity before building the form - which is more or less what "Entity Field default values" do.

  6. +++ b/core/modules/datetime/datetime.module
    @@ -46,7 +46,8 @@ function datetime_element_info() {
       $types['datetime'] = array(
         '#input' => TRUE,
         '#element_validate' => array('datetime_datetime_validate'),
    -    '#process' => array('datetime_datetime_form_process'),
    +    '#process' => array('datetime_datetime_form_process', 'form_process_group'),
    +    '#pre_render' => array('form_pre_render_group'),
    

    So, this is the part that bugs me most.
    Basically, for this to work, we need to add form_process_group & form_pre_render_group to any form #type that has a potential to be moved into a #group.

    So far, only 'container' types had those: fieldset, details, container
    Now we need to add it to: datetime, textfield, textarea, checkbox
    as well, which sounds a bit arbitrary, and not too scalable.

    Not sure how to adress that, but ideally #group should "just work" on any element type, not just on the ones that provision for it.
    That's probably better left for a separate issue, but I'd think form_process_group() & form_pre_render_group() should just run on every sub-element in a form / render array.
    No biggie for form_pre_render_group(), it's an early opt-out if $element['#group'] is not set. Might be more problematic for form_process_group(), at least as is.

Gábor Hojtsy’s picture

@yched:

1. This should be handled later when #2111887: Regression: Only title (of base fields) on nodes is marked as translatable is handled, so the special storage for these values can be removed from content translation.

2. This was removed by @Berdir in #39 with Also removed a bunch of unecessary code that confused me when debugging, as we seem to be cleaning up that form anyway ;) I'm fine adding it back if we don't want to do this.

3. :)

4. Currently the group would be rendered as a container even if it ends up not having elements. I'm not sure this kind of form API change should belong in a node form refactor, but if you feel strongly about this, we can do it here.

5. I don't see what to update in the comment, it still explains what is happening. As for further cleaning this up, I'd fire back with the same question you put up in (2) :D If we cut back on unrelated cleanup there, why add more here? :) The goal of this patch is to move items to different parts of the form, not to change their submission handling. That the keys are found elsewhere now does not in itself implicate changing processing on them :)

6. I agree this would be a great followup too. I don't think we should do form API rearchitecting here. Same feeling as in (4).

yched’s picture

@Gabor #64:

2. Not opposed to cleaning that up, was just wondering about the status of the change (side cleanup vs. consequence of the other changes)

4; Actually, 'uid', 'name', etc.. are Fields, and as such, are subject to the field access API. It can probably qualify as a bug that the node form currently bypasses field-level access rules and assigns hardcoded #access values for 'uid', 'created', etc... (on the parent container in current HEAD, on the parent container + the $element itself in the patch)
That's going to be an issue when converting those $elements to actual widgets, since widgets assign #access based on field_access API.

We don't necessarily have to fix this here for each of the individual $elements touched by the patch, but yeah, we should IMO at least try to avoid having the group container making assumptions about the #access rules of its children, and instead not render itself if it has no visible children.

5. - simplification of the submit flow for 'uid' & 'created':
Right I was going a little ahead of things. Cleaning this is a topic for if/when those fields get converted to using actual entity_ref & date widgets.

6. Opened #2190333: Make #group FAPI / render feature work on all form/render #types out of the box
Meanwhile, I'm wondering whether the patch here would be better off adding 'form_process_group' & 'form_pre_render_group' manually to each individual $element where a '#group' property is added ('uid', 'created', etc...), rather than adding it to a couple arbitrary element types in hook_element_info() ?

Gábor Hojtsy’s picture

@yched:

2. I think that is not required for this patch, but its a short cleanup.

4. Sounds like the field_access stuff is not in scope here. Again the scope of this issue is not to *clean up all the problems with base fields* :D I understand you'd like the group processing to not render the group if there was no content.

6. I think adding them to the individual elements is also a hack, because then after this patch we should say: if you want to add elements to this group, you need to manually add the process functions to your elements. At least with the current patch we could say: if you want to add your elements to the groups, you can add any of these element types: X, Y, Z.

Gábor Hojtsy’s picture

Assigned: Aron Novak » Unassigned
FileSize
19.22 KB
1.42 KB

Now skips rendering details elements if there are no children. Works in my testing. :) No need to have access on the container then, it will just not render when it does not make sense.

Gábor Hojtsy’s picture

The author group can also loose the #access.

The last submitted patch, 67: 2177469-move-node-base-widgets-top-67.patch, failed testing.

yched’s picture

Status: Needs review » Reviewed & tested by the community

Thanks Gabor! This looks good to me.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 68: 2177469-move-node-base-widgets-top-68.patch, failed testing.

andypost’s picture

sun’s picture

#57 + #58 pretty much re-expresses exactly my concerns.

How will this work for e.g. the author fields in CommentFormController?

  1. +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
    @@ -316,11 +308,11 @@ public function validate(array $form, array &$form_state) {
         // Validate the "authored by" field.
    -    if (!empty($form_state['values']['name']) && !($account = user_load_by_name($form_state['values']['name']))) {
    +    if (!empty($form_state['values']['uid']) && !($account = user_load_by_name($form_state['values']['uid']))) {
    
    @@ -423,15 +415,15 @@ public function buildEntity(array $form, array &$form_state) {
         // A user might assign the node author by entering a user name in the node
         // form, which we then need to translate to a user ID.
    -    if (!empty($form_state['values']['name']) && $account = user_load_by_name($form_state['values']['name'])) {
    +    if (!empty($form_state['values']['uid']) && $account = user_load_by_name($form_state['values']['uid'])) {
           $entity->setOwnerId($account->id());
         }
    

    Are you sure that these changes do not cause anonymous users to be able to set an arbitrary comment author? (impersonation)

    Where and how is the differentiation between trusted author information and untrusted author information made now?

  2. +++ b/core/modules/system/system.module
    @@ -469,8 +470,8 @@ function system_element_info() {
    +    '#process' => array('form_process_checkbox', 'ajax_process_form', 'form_process_group'),
    +    '#pre_render' => array('form_pre_render_checkbox', 'form_pre_render_group'),
    

    Did we verify whether the #process and #pre_render callbacks for #group should not actually come first instead of last? I'm concerned whether earlier callbacks might depend on the group adjustments?

Gábor Hojtsy’s picture

@sun: anonymous users don't have access to that field (unless provided permission), so the hardcoded form value is the only one available. If they are provided with the permission to change it, then they can, but then its by design.

yched’s picture

#57 + #58 pretty much re-expresses exactly my concerns.

I don't see how "have form $elements be named the same that the underlying base field they edit, rather than some arbitrary name" could be something that adds confusion ?

#57 + #58 is IMO mostly about 'uid' being an extremely confusing name for the "node author" field in D8. It's no longer an id, it's an entity_reference field.
The field should be named 'author' or 'owner', because that's what it actually is.

Code like:
user_load_by_name($form_state['values']['uid'])
array('%name' => $form_state['values']['uid'])
does look weird. But that's because 'uid' is the wrong name for the field to begin with.
But renaming the field is outside the scope of this patch.

Also, the goal is to move "node author" to an actual entity_reference widget - then that custom code disappears, the form element is automatically generated under $form[$field_name] like all other widgets, and this doesn't bug anyoine anymore :-)

Gábor Hojtsy’s picture

And this is the first step to make sure the top level widgets work with the form structure :) So later on widgets / field access, etc. can be applied as appropriate :)

sun’s picture

  1. I'm still (very) concerned about the $form structure data binding, but I do not want to hold up progress.

  2. If possible, it would be great to see an explanation for the question I asked in #73 already: How does this work for the author fields in CommentFormController?

    → Happy to have that in a separate issue, since CommentFormController is the next best conversion in light of the changes here. Though I'm fairly confident that we'll run into hard problems.

The last submitted patch, 68: 2177469-move-node-base-widgets-top-68.patch, failed testing.

yched’s picture

Gábor Hojtsy’s picture

@sun: Re #77/2, this patch does not change the comment form controller, so how would that work is out of scope here. Also the scope of the proposed changes here is to rename and move elements in the form. That should not change behaviour of them at all, they would just be taken from a different place. I don't see how renaming and/or moving elements in the form would change how anonymous users would access things or be able to do things?! Either way, once again the comment form is not in the scope of this patch.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
20.44 KB
1.9 KB

Rerolled with the twig changes in mind. The form.inc changes applied with an offset, so you can see in the interdiff I'm removing it from the wrong place :D The twig change looks more but its just wrapping the whole file in an if on the children. Still works fine.

Anybody wanna RTBC or are we waiting for something else to invalidate it? :D

Status: Needs review » Needs work

The last submitted patch, 81: 2177469-move-node-base-widgets-top-81.patch, failed testing.

Gábor Hojtsy’s picture

The last submitted patch, 81: 2177469-move-node-base-widgets-top-81.patch, failed testing.

yched’s picture

Hm, this breaks the admin/modules page, which uses 'details' with just a '#description' & no children - the description is the content.

I guess both use cases are valid:
- display a 'details' with its description even if empty
- setup a 'details' container, possibly with a description, but hide it if all its children turn out to be hidden.

So maybe we should add an explicit #hide_if_empty boolean param ?

andypost’s picture

Reverted #67 allowing details to be rendered always, I see no reason to hide them.
The same as introduce #hide_if_empty that will confuse much more.
Suppose better to apply the same #access as their children have.

Agree with @sun $41.4

→ The form_process_group() and form_pre_render_group() callbacks should be removed and the concept should be refactored directly into FormBuilder.

this needs separate issue

PS: comment form would be really nightmare to convert, but I think better prepare widgets ant try to apply them to form, there's not so much left to make it

YesCT’s picture

I checked the extend page, manage fields on article, created a node, edited it, and did something with revisions.

Is there any specific places to check this manually?
Is there any other place with a known empty group that we can check?

(haven't read all the comments yet, or the whole patch)

yched’s picture

Reverted #67 allowing details to be rendered always, I see no reason to hide them.
The same as introduce #hide_if_empty that will confuse much more.
Suppose better to apply the same #access as their children have.

Sorry, but, as explained in #63.4 and #65.4 , this is inherently wrong.
It's a bug that the $elements for 'author', 'created'... currently hardcode an #access without going through the Entity Field Access API.
Therefore, the "details" container cannot hardcode assumptions on which #access its children have (and each child might have different ones, too).

Now, we can consider that this bug of hardcoded #access exists in HEAD already and that we leave it unresolved here to move forward, but that's something we'll need to solve in a major followup.

We *do* have cases of :
a) 'details' that need to be displayed even if they have no children (the 'details' on admin/modules)
*and*
b) 'details' that need to be hidden if they have no visible children (the 'details' in the node edit form)
So I don't see how we avoid a #flag property to specify which behavior is wanted case by case.

Agree with @sun $41.4

→ The form_process_group() and form_pre_render_group() callbacks should be removed and the concept should be refactored directly into FormBuilder.

this needs separate issue

Yes, that's #2190333: Make #group FAPI / render feature work on all form/render #types out of the box.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

@YesCT: we need the optional display property on the detail element, even though @andypost did not want to implement it. I really hoped someone else can help with this issue so it does not become stale while I am unable to work on it this week. Sad that it did not move forward :(

andypost’s picture

Now, we can consider that this bug of hardcoded #access exists in HEAD already and that we leave it unresolved here to move forward, but that's something we'll need to solve in a major followup.

Does it makes sense to postpone the issue on #2190333: Make #group FAPI / render feature work on all form/render #types out of the box

Gábor Hojtsy’s picture

We already postponed several layers of other issues on this, so would be good to not add up on the postpone chain.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
3.45 KB
21.19 KB

Introducing the #optional argument on details fields. If that was not provided or the children are not empty, the elements are printed. Reviews / manual testing welcome. Would be good to finally converge on a solution instead of pulling this apart, that will not lead us to resolving all that depends on this.

yched’s picture

Status: Needs review » Reviewed & tested by the community

Works for me if green. Thanks Gabor.

YesCT’s picture

so it is as if everything has a default optional false, that means always show the group, whether or not there are children?

and we are only specifying optional true where we want details hidden when there are no children?

I looked at the interdiff, but I dont see where optional is documented as being a possible array value.

YesCT’s picture

Issue summary: View changes

1.
I'm not sure. This is "fapi documentation"?

Maybe something like

diff --git a/core/includes/form.inc b/core/includes/form.inc
index 74c66fd..e31566e 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -990,7 +990,10 @@ function theme_fieldset($variables) {
  *   An associative array containing:
  *   - element: An associative array containing the properties of the element.
  *     Properties used: #attributes, #children, #collapsed, #collapsible,
- *     #description, #id, #title, #value.
+ *     #description, #id, #optional, #title, #value.
+ *
+ * Note: Leave #optional not set if the details should always be shown. Set
+ * #optional to TRUE if the details should be hidden when empty.
  *
  * @ingroup themeable
  */

and an update to the doc git repo that (should) document details. #1892968: Fieldsets vs. Details: When to use what and why?
Maybe the "Note:" part the kind of information that should not be in form.inc but should be in https://api.drupal.org/api/drupal/developer%21topics%21forms_api_referen...

---
2.
a. I tried manually testing the #optional by commenting out (and uncommenting) the optional = true lines for authoring info and promotion, and trying to create an article as anonymous (giving anon permission to create article). And I can see it makes sense to have authoring and promotion optional as that user cannot see what are in those details anyway. Good! (I could not figure out what situation might make the revision info detail empty/childless.)

b. I was going to try putting optional on another form (like the admin/modules) just to see if it would hide those details, but I got as far as ModulesListForm and couldn't figure out where to put it.

Gábor Hojtsy’s picture

Opened #2199321: Document #optional on details for the documentation which is in a different project. Adding the #optional argument to the list on the phpdoc is a good idea. Should keep this RTBC I think since just a minor doc change.

sun’s picture

Status: Reviewed & tested by the community » Needs work

Sorry, but why are we proceeding into the full theme layer and uglifying the theme template, if nothing is to be rendered in the first place?

The case of #optional with no element children should be caught by the #pre_render function already. Simply setting #printed to TRUE should cut it.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
20.02 KB
2.49 KB

@sun: there you go, any other concerns?

Status: Needs review » Needs work

The last submitted patch, 98: 2177469-move-node-base-widgets-top-98.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
744 bytes
20.07 KB

Not all details have parents.

sun’s picture

Thanks, that looks better.

yched’s picture

Status: Needs review » Reviewed & tested by the community

Cool. Back to RTBC then ?

webchick’s picture

Status: Reviewed & tested by the community » Fixed

This all looks like really great clean-up to me, apart from this gem:

+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
@@ -155,17 +155,17 @@ protected function doTestAuthoringInfo() {
-        'name' => $user->getUsername(),
...
+        'uid' => $user->getUsername(),

Um. Wat? :)

When I asked about this in IRC, Gábor explained that basically this patch makes it so that $form['X'] <====> $node->x and the property that's being set is $node->uid, not $node->name. And we don't typically name fields after their widgets; e.g. a field_timestamp never meant it would have an integer number widget, so this is the same. I guess this is fair enough, but it looks wack-tastical. I'd recommend a follow-up issue, except I'm not really sure what we would do to change it. :\

Anyway, since that's just status quo, not going to hold this patch up on that. Especially since it unblocks at least 3-4 other things.

Committed and pushed to 8.x. Thanks!

xjm’s picture

Issue tags: +Entity Field API
Gábor Hojtsy’s picture

Issue tags: -sprint

Yayay, thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.