Hi all,

In a situation where the reference field is setted on the child node instead of on the parent node is simply to obtain a URL path like "parent-node-title/child-node-title" for child nodes, using the [node:field-ref:title]/[node:title] token in pathauto.

But using this module here we have the opposite: the reference field is setted on the parent node instead of on the child node. In this case tokens in pathauto doesn't works (if you know the existance of a token that can do this, please let me know)

So, how I can obtain the desidered URL path for child nodes?

Thank you very much for any help/suggestion

Comments

bojanz’s picture

Project: Inline Entity Form » Pathauto

You will probably have to add a custom token through some coding.
Moving the issue to the Pathauto issue queue, they might have a better idea.

RogerRogers’s picture

I think I have the same problem.

I have an Entity Reference field that uses Inline Entity Form. The field is for an image gallery, and I want my URL to be something like /type_from_referencing_node/productname_from_referencing_node/imagename, but I'm unable to achieve this. When I look at the image gallery field tokens for PathAuto I get "Nodes with delta 1", "Nodes with delta 2", etc. In each of these delta rows I do have all of the related fields, but none actually generates a token value.

MXT’s picture

No news on this?

Please help!

Thank you

RogerRogers’s picture

I had to create a custom path for this, which was pretty easy really. My code, FWIW:

function hook_token_info() {
    $info['tokens']['node']['machine_name_of_your_path'] = array(
        'name' => t('Friendly Name Of Your Path'),
        'description' => t('...description...'),
    );
    return $info;
}


function hook_tokens($type, $tokens, array $data = array(), array $options = array()) {
    $replacements = array();
    $sanitize = !empty($options['sanitize']);
    $path = '';

    if ($type == 'node' && !empty($data['node'])) {
        $node = $data['node'];

        foreach ($tokens as $name => $original) {
            switch ($name) {
                case 'machine_name_of_your_path':
                  
                    $path = [put your path string here!!! - will probably require an entityfieldquery to get what you want.];
                  
                    $replacements[$original] = str_replace(
                        array('&', '&'),
                        'and',
                        $sanitize ? filter_xss($path) : $path
                    );
                                      
                    break;
            }
        }
    }

    return $replacements;
}

MXT’s picture

Thank you RogerRogers, following your suggestion I've a PARTIAL working solution.

Why PARTIAL?

Well, the problem is that creating the child node trough Inline Entity Form I have to save it 2 times to have the token replacement working. The problem is probably due to the fact that token replacement is executed BEFORE Inline Entity Form creates the child node id in the parent node nodereference field (target_id).

So we probably have:

  1. We are in the parent node form, and click on "add" in Inline Entity Form to add a child node
  2. Compile and save the child node:
    • custom token do its stuff, but cannot find the parent node whose field_ref points to the child node (is not created yet)
    • Inline Entity Form finishes its job and save the new child reference in parent node field reference
    • result: Child node url is not correctly created
  3. Modify and re-save the just created child node:
    • custom token do its stuff, now it can find the parent node whose field_ref points to the child node (it was created in the previous step)
    • Inline Entity Form finishes its job and save the new child reference in parent node field reference
    • result: Child node url is correctly created now

I've tried to change my custom token module weight to 1 in the hope it would execute after Inline Entity Form but without success.

How did you managed this issue?

Thank you very much for your help

RogerRogers’s picture

Hey MXT, unfortunately the requirements changed and I'm not actually using the code I sent before, so it's not too easy for me to test. But, if I remember correctly, I didn't have the problem you mention.

I have commented my code a bit more, because it might be confusing. My IEF is related to nodes, not 'generic' entities.


function hook_token_info() {
    $info['tokens']['node']['machine_name_of_your_path'] = array(
        'name' => t('Friendly Name Of Your Path'),
        'description' => t('...description...'),
    );
    return $info;
}


function hook_tokens($type, $tokens, array $data = array(), array $options = array()) {
    $replacements = array();
    $sanitize = !empty($options['sanitize']);
    $path = '';

    // If your IEF contains non-node entities, then you want to change this conditional to target your IEF base/type
    if ($type == 'node' && !empty($data['node'])) {
      
        //This is the IEF object (in my case, a node)
        $node = $data['node'];

        foreach ($tokens as $name => $original) {
            switch ($name) {
                case 'machine_name_of_your_path':
                  
                    //Find parent node of the IEF node
                    $query = new EntityFieldQuery;
                    $result = $query->entityCondition('entity_type', 'node')
                      ->entityCondition('bundle', array('product'))
                      ->fieldCondition('field_image_gallery', 'target_id', $node->nid) //'field_image_gallery' is my IEF field. I only have one parent per IEF item, so I know if I get a result, it will be right parent.
                      ->range(0,1)
                      ->execute();

                    if (!empty($result)){
                      $product = node_load(key($result['node'])); //This is my parent node, the one that contains the IEF field 'field_image_gallery'
                      $path = drupal_get_path_alias($path = 'node/' . $product->nid); //Now, I grab the aliased path of the parent node, which I will save as the token. I can then combine this aliased path with my IEF node title to get something like /products/product/[gallery/title], where the brackets are the IEF object's path tokens. Note: You combine this custom token and the existing tokens using the admin/config/search/path/patterns administration area, not in code.
                    }
                  
                    $replacements[$original] = str_replace(
                        array('&', '&'),
                        'and',
                        $sanitize ? filter_xss($path) : $path
                    );
                                      
                    break;
                    
            }
        }
    }

    return $replacements;
}
MXT’s picture

Project: Pathauto » Inline Entity Form

Yes RogerRogers my code is very similar to yours (thank you for sharing it) but this is not the point.

The query I / you wrote when runs for the first time returns NO results because the condition:
->fieldCondition('field_image_gallery', 'target_id', $node->nid)
can't match (field_image_gallery with target_id record is not already created by IEF at this time).

The query returns the result if runs for a second time (because at this point IEF has already created the target_id and fieldCondition can match.

I'll try to reassign this to IEF issue queue to hear Bojanz feedback

Thank you very much for collaborating until now ;-)

RogerRogers’s picture

@MXT: gotcha. Post back what you find if you don't mind!

MXT’s picture

I've actually found a workaround to resolve all the issues:

In addition to the custom token above, I've also implemented hook_node_insert() and hook_node_update() for the parent node.

In this implementations I call the pathauto_node_update_alias() for each child node.

In this way, every time the parent node is created or updated, all child paths are refreshed too in the right way.

This resolve all 3 critical situation (remember that my desired path is "parent-node-title/child-node-title" for child nodes):

Critical case 1: suppose you have already a parent node with a number of child nodes with all paths correctly setted. Suppose you need to change the parent node title: saving (updating) the parent node you automatically obtain that all the child nodes paths are recreated reflecting the new parent node title in the url (wow!)

Critical case 2: suppose you are creating for the first time the parent node: you are compiling all input fields and creating the child nodes with IEF: because you haven't already saved the parent node form, all child nodes can't calculate the right url (the parent title is not yet saved at the moment!). But when you finally save the parent node the hook_insert is triggered and all the child nodes paths are correctly recreated.

Critical case 3: this case is described in my #5 and #7. This case is resolved too saving the parent node: in this way you don't need to save 2 times the new created child to obtain the right child path.

I would like to know if all this stuff and workaround can be directly better managed by IEF itself.

MXT’s picture

Issue summary: View changes

Small mistake

dman’s picture

Issue summary: View changes

FYI, I did this with a customizable module of my own,
I've just put it up as a sandbox for reference, though i know the docs need some work, and edge cases probably abound.

https://drupal.org/sandbox/dman/2209055

This module
* provides new tokens to use with pathauto
* allowing you access to "the entity that refers to this one through entity_reference (via a named field)"

I have a number of publication issues - that link to publication 'articles' using inline_entity_form to allow an editor to do the whole thing from the top.

The pattern for my 'article' content type

[node:incoming_references:field_pi_articles:0:url:path]/[node:title]

Which translates to:
* when making a path for an article
* find the url path of
* the first (0th) entity that links to me
* by way of the field_pi_articles entityreference.

In this way, my articles are all given URLs derived from their parent/owner node.

THERE IS a race condition if you try to create all these nodes at once, as the children require the parent to have been given a valid URL before they save (observed above), and inline_entity_form may not always ensure that. But in general, this token that helps you build an URL based on incoming references is what I needed at that time.

I actually added a 'rule' to take care of the rest, including 'if parent is unpublished, all children are unpublished' and vice-versa. And one for 'if the parent path changes, update all child paths' But that was pure 'rules' only IIRC.

kcolwell’s picture

Thank you dman for the sandbox code... saved me a lot of time.

Thanks,
kcolwell

flyke’s picture

I also thank dman for his sandbox code, it works great. My use case was to create a pattern for path aliases. I have node type A that uses inline entity form to reference nodes of node type B. For a node of node type B, I want the url to be path-to-referencing-node-a/title-of-b.

It is hard to believe that this context is not available by default. It really should be. Using dman's code, I was able to use context [node:incoming_references:field_referencingfield:0:url:path]/[node:title] as a pattern which is exactly what I was looking for.

It would be nice if drupal.org had a point system for comments so I could +1 dman's comment here.

I hope somehow this context makes it into a next version of Inline Entity Form, or probably better the entityreference module.

geek-merlin’s picture

geek-merlin’s picture

jphelan’s picture

None of the solutions above were working well for me without saving twice. It seems pathauto was not updating the path until after the hooks. I expanded on MXT's idea and just created the path myself using path_save.

function mymodule_node_insert($node) {
  if(!empty($node->field_sections['und'])) {
    module_load_include('inc', 'pathauto');
    foreach($node->field_sections['und'] as $section_id) {
      $section = node_load($section_id['target_id']);
      $path = array(
        'source' => 'node/' . $section->nid,
        'alias' => pathauto_cleanstring($node->title) . '/' . pathauto_cleanstring($section->title),
      );
      path_save($path);
    }  
  }  
}

function mymodule_node_update($node) {
  if(!empty($node->field_sections['und'])) {
    module_load_include('inc', 'pathauto');
    foreach($node->field_sections['und'] as $section_id) {
      $section = node_load($section_id['target_id']);
      $path = array(
        'source' => 'node/' . $section->nid,
        'alias' => pathauto_cleanstring($node->title) . '/' . pathauto_cleanstring($section->title),
      );
      $current = path_load('node/'. $section->nid);
      if($current) {
        $path['pid'] = $current['pid'];
      }
      path_save($path);
    }  
  }  
}
pachabhaiya’s picture

Solution provided by @jphelan (#15) works good for me.

Sebcbien’s picture

HI,

I try for drupal 8, but I can't adapt the code! i obtain http ajax error 200...

My hierarchy is:

site (ief field: field_secteur) --> secteur (ief fields: field_voie_1_l and field_voie_x_l)

I would like to get some url like that:

mydomain.com/site-name/secteur-name/voie-1-l-name

/**
 * Implements hook_token_info().
 */
function ief_parent_token_token_info() {
  $info = [];
  $info['tokens']['node']['ief_parent_path'][] = 'A token to extract desired characters from Node body to be used in meta descriptions';
  return $info;
}


/**
 * Implements hook_tokens().
 */
function  ief_parent_token_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {
  $replacements = [];
  if ($type == 'node' && !empty($data['node'])) {
  	 $node = $data['node'];

    foreach ($tokens as $name => $original) {
      // Find the desired token by name.
      switch ($name) {
        case 'ief_parent_path':
			//Find parent node of the IEF node
            $query = new EntityFieldQuery;
            $result = $query->entityCondition('entity_type', 'node')
            ->entityCondition('bundle', array('product'))
            ->fieldCondition('field_secteurs', 'target_id', $node->nid) //'field_secteurs' is my IEF field. I only have one parent per IEF item, so I know if I get a result, it will be right parent.
            ->range(0,1)
            ->execute();

            if (!empty($result)){
            $product = node_load(key($result['node'])); //This is my parent node, the one that contains the IEF field 'field_secteur'
            $path = drupal_get_path_alias($path = 'node/' . $product->nid); //Now, I grab the aliased path of the parent node, which I will save as the token. I can then combine this aliased path with my IEF node title to get something like /products/product/[gallery/title], where the brackets are the IEF object's path tokens. Note: You combine this custom token and the existing tokens using the admin/config/search/path/patterns administration area, not in code.
            }
                  
            $replacements[$original] = ief_parent_path_value(
            array('&', '&'),'and', $sanitize ? filter_xss($path) : $path);

          break;
      }
    }
  }
  return $replacements;
}

help me please..

dcam’s picture

Status: Active » Closed (works as designed)

The original question was answered. The same question for D8 was also asked in #2979710: How to obtain "parent-title/child-title" URL path for child nodes for D8 and answered there. Closing.