For example, I have a complex field (field_multiple_nids) that contains two columns, reference_a and reference_b that both contain referenced node IDs. The code in ctools_entity_from_field only supports the first unique-by-entity-type relationship column found:

  // Get the schema information for every field.
  $fields_info = field_info_fields();
  foreach ($fields_info as $field_name => $field) {
    ...
          $foreign_keys = ctools_field_foreign_keys($field_name);
          foreach ($foreign_keys as $key => $info) {
            if (isset($info['table'])) {
              foreach ($entities as $to_entity => $to_entity_info) {
                $from_entity_info = $entities[$from_entity];
                ...

                if (isset($to_entity_info['base table']) && $to_entity_info['base table'] == $info['table'] && array_keys($info['columns'], $to_entity_info['entity keys']['id'])) {
                  $name = $field_name . '-' . $from_entity . '-' . $to_entity;
                  $plugin_id = $parent . ':' . $name;

                  ...

                  // We check for every bundle; this plugin may already have
                  // been created, so don't recreate it.
                  if (!isset($plugins[$plugin_id])) {
                    $plugin = $parent_plugin;
                    $replacements = array(
                      '@to_entity' => $to_entity_info['label'],
                      '@from_entity' => $from_entity_info['label'],
                      '@field_name' => $field_name,
                      '@field_label' => ctools_field_label($field_name),
                    );
                    $plugin['title'] = t('@to_entity from @from_entity (on @from_entity: @field_label [@field_name])', $replacements);
                    $plugin['keyword'] = $to_entity;
                    $plugin['context name'] = $name;
                    $plugin['name'] = $plugin_id;
                    $plugin['description'] = t('Creates a @to_entity context from @from_entity using the @field_name field on @from_entity.', $replacements);
                    $plugin['from entity'] = $from_entity;
                    $plugin['to entity'] = $to_entity;
                    $plugin['field name'] = $field_name;
                    $plugin['join key'] = $key;
                    $plugin['source key'] = current(array_keys($info['columns']));
                    ...

The above code means that the $name for both columns would both be entity_from_field:field_multiple_nids-node-node and the first column wins (since $plugin['source key'] == 'column_a'), and the second column loses, and does not get a relationship handler. But it should.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Dave Reid’s picture

I'm just not understanding at all why the actual column names weren't used in the plugin ID. Because now I have no idea how this could be fixed without breaking existing installs.

KeyboardCowboy’s picture

Took a stab at this one. Basically, if a given foreign key has multiple columns, create a child plugin for each one. Otherwise, proceed as normal.

I used a vertical bar to append the column key name to the field name for plugin id purposes.

Early regression testing passed. Definitely needs another set of eyes, but looks promising.

Dave Reid’s picture

@KeyboardCowboy: This would still break existing relationships for fields though? If my field had more than one column referring to the same entity type, it now has a changed $name value.

I think for the *first* column that refers to a specific entity type:
$name = $field_name . '|' . $column . '-' . $from_entity . '-' . $to_entity;

But for any remaining columns that refer to the same entity type:
$name = $field_name . '|' . $column . '-' . $from_entity . '-' . $to_entity . '-' . $column;

Or maybe we just leave the existing support for $name = $field_name . '|' . $column . '-' . $from_entity . '-' . $to_entity; plugins, but also add each column as $name = $field_name . '|' . $column . '-' . $from_entity . '-' . $to_entity . '-' . $column; as separate plugins. This would allow us to "deprecate" the existing plugins assuming we could hide them from selection, but still allow them to work.

KeyboardCowboy’s picture

@davereid I think I like the third option. Let me see if I have this correct.

Given:
Custom field schema

...
       'foreign keys' => array(
          'node' => array(
            'table'   => 'node',
            'columns' => array(
              'rel_a' => 'nid',
              'rel_b' => 'nid',
              'rel_c' => 'nid',
            ),
          ),
        ),

The current implementation would provide a plugin named entity_from_field-node-node which joined on rel_a only. The label would look like Node from Node (on Node: My Multi Field [field_my_multi_field]).

You're suggesting we keep that plugin as is but also add the derivatives, leaving us with, in this example, 4 plugins:

entity_from_field-node-node => Node from Node (on Node: My Multi Field [field_my_multi_field])
entity_from_field|rel_a-node-node => Node from Node (on Node: My Multi Field [field_my_multi_field:rel_a])
entity_from_field|rel_b-node-node => Node from Node (on Node: My Multi Field [field_my_multi_field:rel_b])
entity_from_field|rel_c-node-node => Node from Node (on Node: My Multi Field [field_my_multi_field:rel_c])

This way we keep the existing relationships intact but still provide support for the multiple columns. Is that right?

KeyboardCowboy’s picture

Here's a patch for #4. I'm not sure if there is a good way to remove the deprecated item from the dropdown list (or whether it should be) so it simply marks it as deprecated.

KeyboardCowboy’s picture

Status: Active » Needs review
KeyboardCowboy’s picture

FileSize
4.5 KB

Cleaning up white space in patch.

Chris Matthews’s picture

The 4 year old patch in #7 to entity_from_field.inc still applies cleanly to the latest 7.x-1.x-dev, but still needs to be reviewed & tested.

Applied patch plugins/relationships/entity_from_field.inc cleanly.