Problem/Motivation

From #1966704: In-place editing for taxonomy terms and custom blocks:

  • Implement hook_preprocess_HOOK() for other entity types as well (currently only implemented for Node). Ideally, we'd have a single preprocess hook for all entity types, but that currently does not yet exist. Using hook_entity_view_alter() is also impossible, because $build['#attributes'] on the page level are never rendered, hence we're condemned to set $variables['attributes'] in hook_preprocess_HOOK(). This is also what the RDFa module does since Drupal 7 (it implements 8 preprocess hooks).
  • As a consequence of the above, it is currently impossible to handle all entity types at once. Hence I chose to support the Comment, Custom Block and Taxonomy Term entity types in this patch.

This means code like this:

/**
 * Implements hook_preprocess_HOOK() for node.tpl.php.
 */
function edit_preprocess_node(&$variables) {
  $node = $variables['elements']['#node'];
  $variables['attributes']['data-edit-entity'] = 'node/' . $node->nid;
}

/**
 * Implements hook_preprocess_HOOK() for taxonomy-term.tpl.php.
 */
function edit_preprocess_taxonomy_term(&$variables) {
  $term = $variables['elements']['#term'];
  $variables['attributes']['data-edit-entity'] = 'taxonomy_term/' . $term->id();
}

/**
 * Implements hook_preprocess_HOOK() for block.tpl.php.
 */
function edit_preprocess_block(&$variables) {
  if (isset($variables['elements']['content']['#custom_block'])) {
    $custom_block = $variables['elements']['content']['#custom_block'];
    $variables['attributes']['data-edit-entity'] = 'custom_block/' . $custom_block->id();
  }
}

// … and so on, for every entity type in core.

Instead of:

/**
 * Implements hook_entity_view_alter().
 */
function edit_entity_view_alter(&$build, EntityInterface $entity, EntityDisplay $display) {
  $build['#attributes']['data-edit-entity'] = $entity->entityType() . '/' . $entity->id();
}

Note: the above means it's IMPOSSIBLE for Edit.module to support entity types that are not in Drupal core, because it cannot know the names of custom entity types!

Proposed resolution

I talked to fubhy about this and there are apparently fundamental architectural constraints to provide hook_preprocess_entity(). Fair enough. Then let's make the #attributes defined in hook_entity_view_alter() actually work?

Remaining tasks

(reviews needed, tests to be written or run, documentation to be written, etc.)

Simpletest failed in ThemeTest.php (line 57). Fix should be implemented and test again.

User interface changes

None.

API changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Title: Impossible to set attributes for all enties » Impossible to set attributes for all entities

I'm stupid.

Wim Leers’s picture

Status: Active » Needs review
Issue tags: +sprint
FileSize
5.89 KB

Working on an initial patch for this has brought to light one very big deficiency in \Drupal\Core\Template\Attribute: its inability to merge!

The patch that got committed at #1290694: Provide consistency for attributes and classes arrays provided by template_preprocess() sidestepped this elegantly AFAICT by simply not using Attribute everywhere, and only starting to use that at the last possible moment. I.e. build the list of attributes "the old school way", and then simply do $attributes = new Attribute($attributes);. However, deeper in the theming layer, when the attributes are in an instance of the new Attribute, you then run into this problem…

So, instead of being able to simply do something like:

if (isset($variables['elements']['#attributes'])) {
  $variables['attributes'] = array_merge_recursive($variables['attributes'], $variables['elements']['#attributes']);
}

I now need to do this:

  // If #attributes are set on the node, then we must reinitialize
  // $variables['attributes'], because merging is not supported by Attribute.
  // The only attribute that is set at this point, is the default "class"
  // attribute as set by template_preprocess().
  if (isset($variables['elements']['#attributes'])) {
    $variables['attributes'] = new Attribute($variables['elements']['#attributes']);
    // To be able to add a class, the class attribute must already be
    // initialized.
    if (!isset($variables['attributes']['class'])) {
      $variables['attributes']['class'] = array();
    }
    $variables['attributes']['class'][] = 'node';
  }

… repeated for every entity type (I'm only doing node/comment/taxonomy term in the attached patch).

Finally: Attribute::value() is broken, though it appears it shouldn't exist at all. I fixed it in this patch.

Wim Leers’s picture

d.o fail

Wim Leers’s picture

This will need to be rewritten once #1982024: Lazy-load Attribute objects later in the rendering process only if needed gets committed, which is probably soon — and all my complaints in #2 will go away :)

Fabianx’s picture

Status: Needs review » Needs work
Wim Leers’s picture

Assigned: Unassigned » Wim Leers
Status: Needs work » Needs review
FileSize
3.31 KB

My suspicion was right, #1982024: Lazy-load Attribute objects later in the rendering process only if needed greatly simplifies this patch :)

Attached patch enables setting attributes on all entities via hook_entity_view_alter(). Only the first two hunks are for making this possible, the last hunk is to make edit.module leverage this :)

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Looks superb, just applying the pattern of #attributes turning into useful things as found elsewhere in Drupal core.

effulgentsia’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests
+++ b/core/includes/theme.inc
@@ -2682,7 +2682,7 @@ function template_preprocess(&$variables, $hook) {
-    'attributes' => array(),
+    'attributes' => isset($variables['elements']['#attributes']) ? $variables['elements']['#attributes'] : array(),

Excellent bug fix. Let's add a test.

+++ b/core/modules/block/block.module
@@ -545,6 +545,11 @@ function template_preprocess_block(&$variables) {
+  // Inherit the attributes set on the content of the block.
+  if (isset($variables['elements']['content']['#attributes'])) {
+    $variables['attributes'] = array_merge_recursive($variables['attributes'], $variables['elements']['content']['#attributes']);
+  }

We need a @todo here to fix this to use $variables['content_attributes']. That requires $variables['content'] to be a render array rather than a string, and that's being fixed in #1927608: Remove the tight coupling between Block Plugins and Block Entities.

Dave Reid’s picture

+++ b/core/includes/theme.incundefined
@@ -2682,7 +2682,7 @@ function template_preprocess(&$variables, $hook) {
   $variables += $default_variables + array(
-    'attributes' => array(),
+    'attributes' => isset($variables['elements']['#attributes']) ? $variables['elements']['#attributes'] : array(),
     'title_attributes' => array(),
     'content_attributes' => array(),
   );

This made me go a little WTF, since obviously we need this same fix for title_attributes and content_attributes. Should we make separate related issues or try and fix them here as well?

aspilicious’s picture

Can we write
'attributes' => isset($variables['elements']['#attributes']) ? $variables['elements']['#attributes'] : array(),
as
'attributes' => isset($variables['elements']['#attributes']) ?: array()

?

effulgentsia’s picture

Just a reroll for HEAD changes. Will follow up with a separate patch and interdiff that incorporates feedback.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
3.28 KB
4.73 KB

This still needs a test added, but CNR for bot.

we need this same fix for title_attributes and content_attributes

I don't think so. There are no core use cases of a #title_attributes or #content_attributes property on render elements. It's possible that in some cases, like for 'block' in this patch, that we want to merge the #attributes of a child element into $variables['title_attributes'] or $variables['content_attributes'], but I think that's always hook-specific, not something that can be done generically in template_preprocess().

In manual testing of this patch, the 'data-edit-entity' attribute is output correctly for custom blocks, but I don't get a "Quick Edit" contextual link for them.

Status: Needs review » Needs work

The last submitted patch, attributes_entity-1972514-12.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
2.38 KB
2.38 KB
7.34 KB

The patch in #12 makes a lot more sense than mine — thanks! :)

Sadly, your changes broke the blocks case. In my patch, I was setting $variables['attributes']. In your patch, that became $variables['content_attributes']. A subtle, but very important difference.


I wrote a test.

Status: Needs review » Needs work

The last submitted patch, test-only-FAIL.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
2.84 KB
2.84 KB
7.79 KB

I forgot to add a new .tpl.php file… Ignore all attachments in #14, look at these instead.

Status: Needs review » Needs work

The last submitted patch, test-only-16-FAIL.patch, failed testing.

Wim Leers’s picture

The changes in #12 broke Drupal\language\Tests\LanguageSwitchingTest. That's why #16's "PASS" patch still fails. Note how the "PASS" patch only fails Drupal\language\Tests\LanguageSwitchingTest but passes the newly added test. For the "FAIL" test, it's the other way around. So all is good, we just need to make the Drupal\language\Tests\LanguageSwitchingTest test pass again :)

Wim Leers’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
FileSize
1.02 KB
2.84 KB
8.75 KB

Status: Needs review » Needs work

The last submitted patch, test-only-19-FAIL.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
3.18 KB
8.68 KB

block.module had changed in between #16 and #19, hence patch didn't apply anymore.

Status: Needs review » Needs work
Issue tags: -sprint, -in-place editing, -Spark

The last submitted patch, attributes_entity-1972514-21-PASS.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work
Issue tags: +sprint, +in-place editing, +Spark

The last submitted patch, attributes_entity-1972514-21-PASS.patch, failed testing.

Damien Tournoud’s picture

+  // When theming a render element, merge its #attributes into
+  // $variables['attributes'].
+  $hooks = theme_get_registry(FALSE);
+  if (isset($hooks[$hook]['render element'])) {
+    $key = $hooks[$hook]['render element'];
+    if (isset($variables[$key]['#attributes'])) {
+      $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], $variables[$key]['#attributes']);
+    }
+  }

Please inject the $info into the preprocess function instead. This completely break encapsulation and is most likely very inefficient.

I mean, transform the signature of those functions (preprocess, process) into:

function xxxx(&$variables, $hook, $info) {
}
Wim Leers’s picture

#25: Are you sure you're posting to the right issue? There's no $info, there's no calling of additional functions.

Damien Tournoud’s picture

@Wim: $info is the name given to $hooks[$hook] inside theme(). This should be passed to the preprocess/process functions by theme(), instead of those functions breaking encapsulation by getting them from the global theme_get_registry().

Wim Leers’s picture

Assigned: Wim Leers » effulgentsia

I'm no theme system expert, so temporarily assigning to effulgentsia to resolve #25/#27.

effulgentsia’s picture

@Damien Tournoud: I agree with you, but I think that's out of scope for this issue. This is copying the same pattern already being used in HEAD for contextual_preprocess(). Can we have a separate issue for changing the signature of preprocess functions?

Damien Tournoud’s picture

Given that it's a simple and backward compatible change, we should do it in here. If you don't like the performance hit, contextual links you can disable, template_preprocess() you cannot.

effulgentsia’s picture

Assigned: effulgentsia » Wim Leers
Status: Needs work » Needs review
FileSize
2.33 KB
10.02 KB

Ok. I also removed the cloning of $hook, because the reasoning in the code comment for it was faulty. I added that code and comment in D7 in a moment of paranoia, but we don't institutionalize that level of distrust about the functions we call in any other Drupal code, and I didn't want to expand that antipattern to $info or treat $hook and $info differently in that regard.

Status: Needs review » Needs work

The last submitted patch, attributes_entity-1972514-31.patch, failed testing.

Nneka’s picture

Working on this at Drupalcon Portland contrib sprint. Filling out the Issue Summary.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
Issue tags: -sprint, -in-place editing, -Spark

#31: attributes_entity-1972514-31.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +sprint, +in-place editing, +Spark

The last submitted patch, attributes_entity-1972514-31.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
10.31 KB

Reroll to apply cleanly to HEAD again. It's almost certain that more is broken than before. It seems that in several related places things have changed, quite possibly due to Twig.

Wim Leers’s picture

Yay, Twig has actually simplified things for us a little bit :)

  • Less mess in template_preprocess(): it no longer adds default classes.
  • The LanguageSwitchingTest hunk is no longer needed: it's already been made less brittle by Twig or elsewhere.

This passes the relevant tests locally; let's see if testbot finds anything else that breaks.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Looks nifty, thanks for the reroll. As far as I see this addresses concerns brought up above. Let's get it in!

Damien Tournoud’s picture

Yep, looks sweet.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

Needs reroll

curl https://drupal.org/files/attributes_entity-1972514-37.patch | git a
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  9992  100  9992    0     0   8484      0  0:00:01  0:00:01 --:--:--  9952
error: patch failed: core/modules/edit/edit.module:14
error: core/modules/edit/edit.module: patch does not apply
ekl1773’s picture

Assigned: Wim Leers » ekl1773

Assigning to self to reroll patch for mentoring office hours.

Gábor Hojtsy’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
8.83 KB

Here is a quick reroll. The patch still applied with some fuzz.

Gábor Hojtsy’s picture

Assigned: ekl1773 » Unassigned

@ekl1773: ups, sorry for stepping on toes! Was not intended.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, attributes_entity-1972514-42.patch, failed testing.

Wim Leers’s picture

#42 lost the twig template it seems :) Easy fix!

ekl1773’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
9.22 KB

Added the twig template from patch at #37 to patch at #42. Maybe this works?

effulgentsia’s picture

Status: Needs review » Reviewed & tested by the community

Thanks. Doing a diff of #37 vs. #46 shows no lines of code that differ, so back to RTBC.

By the way, no need for a "Needs review" tag, since we have that as a status. Tags are useful for things not captured by the other issue attributes.

alexpott’s picture

Title: Impossible to set attributes for all entities » Change notice: Impossible to set attributes for all entities
Priority: Major » Critical
Status: Reviewed & tested by the community » Active

Committed 71a4ab1 and pushed to 8.x. Thanks!

+++ b/core/includes/theme.incundefined
@@ -2582,11 +2581,16 @@ function template_preprocess(&$variables, $hook) {
+  // When theming a render element, merge its #attributes into
+  // $variables['attributes'].
+  if (isset($info['render element'])) {
+    $key = $info['render element'];
+    if (isset($variables[$key]['#attributes'])) {
+      $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], $variables[$key]['#attributes']);
+    }
+  }

I think requires a change notice to describe this functionality...

Wim Leers’s picture

Sure, will do! Thanks!

Wim Leers’s picture

Priority: Critical » Major
Status: Active » Fixed
Issue tags: -sprint

Change notice created: https://drupal.org/node/2018597.

Wim Leers’s picture

Title: Change notice: Impossible to set attributes for all entities » Impossible to set attributes for all entities

.

Status: Fixed » Closed (fixed)

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

star-szr’s picture

star-szr’s picture

Issue summary: View changes

Updated Issue Summary.