Allow nodes to automatically have node paths set on an automated basis similar to path auto.

CommentFileSizeAuthor
#30 Sélection_072.png23.08 KBsteveoriol
#30 Sélection_070.png9.85 KBsteveoriol

Comments

agentrickard’s picture

Perfectly valid but perhaps tricky to implement.

agentrickard’s picture

ron williams’s picture

Status: Closed (duplicate) » Active

Hi agentrickard,
I think this may be a different, yet related, issue to the one you linked. In #1310628: Integration with pathauto -- auto generate different url alias for different domains the user is looking to generate multiple aliases based on patterns for each domain. For example:

  • Node 1 (titled 'about foo')
    • Generate /about-foo for domain A
    • Generate /page/about-foo for domain B
  • Node 2 (titled 'about bar')
    • Generate /about-bar for domain A
    • Generate /page/about-bar for domain B

In my use case I would want to:

  • Node 1 assigned to domain A (titled 'about foo')
    • Generate /about-foo for domain A
    • Skip generation for domain B as node is not assigned to domain
  • Node 2 assigned to domain B (titled 'about foo')
    • Generate /about-foo for domain B
    • Skip generation for domain A as node is not assigned to domain

In this case, the requirement for a domain specific pathauto pattern is not necessary; however, auto assignment for the single domain is necessary.

Feel free to mark as duplicate if this feature is too related to the other issue even with this difference.

ron williams’s picture

agentrickard’s picture

I see. That is different.

Any idea how you want to configure those rules?

ron williams’s picture

For my use case, I would suggest using the built in pathauto patterns while only setting the domain path for the current domain.

grndlvl’s picture

Here is some preliminary code I am working on to generate the path automatically for the source domain if the pathauto path already exists for the current node.

This only takes into account for the source domain and must run before domain_path's validate.

Instead of forcing the domain_source when using DOMAIN_SOURCE_USE_ACTIVE you could check for DOMAIN_SOURCE_USE_ACTIVE and load the current domain and set it that way. The other stuff was already in my system because of the specific use case.

Most of this code comes from pathauto_create_alias() http://drupalcontrib.org/api/drupal/contributions!pathauto!pathauto.inc/...

/**
 * Implements hook_node_validate().
 */
function mymodule_node_validate($node, $form, &$form_state) {
  // Used to force the source the source domain to be the current active domain
  // if the domain source is set to "use active".
  if ($form_state['values']['domain_source'] == DOMAIN_SOURCE_USE_ACTIVE) {
    global $_domain;
    // Check that the active domain is set as one of the sources.
    if (in_array($_domain['domain_id'], $form_state['values']['domains'])) {
      form_set_value($form['domain']['domain_source'], $_domain['domain_id'], &$form_state);
    }
    else {
      form_set_error('domains', t('The active domain must be selected as a publishing option.'));
    }
  }

  // Check if we are using pathauto, domain_path, domain_source.
  if (module_exists('pathauto') && module_exists('domain_path') && module_exists('domain_source')) {
    // This will only create a domain alias if we are auto generating
    // the path for pathauto and we have a source_domain.

    $domain_source = $form_state['values']['domain_source'];

    // Only if we have a source domain.
    if ($domain_source <= 0) return;

    $path = $form_state['values']['path'];

    // Skip processing if the user has disabled pathauto for the node.
    if (isset($path['pathauto']) && empty($path['pathauto'])) {
      return;
    }

    $module = 'node';
    $type = $node->type;
    $language = $path['language'];
    $data = array('node' => $node);
    $source = 'node/' . $node->nid;
    $op = 'update';

    // Retrieve and apply the pattern for this content type.
    $pattern = pathauto_pattern_load_by_entity($module, $type, $language);
    if (empty($pattern)) {
      // No pattern? Do nothing (otherwise we may blow away existing aliases...)
      return;
    }

    // Replace any tokens in the pattern. Uses callback option to clean replacements. No sanitization.
    module_load_include('inc', 'pathauto', 'pathauto');
    $alias = token_replace($pattern, $data, array(
      'sanitize' => FALSE,
      'clear' => TRUE,
      'callback' => 'pathauto_clean_token_values',
      'language' => (object) array('language' => $language),
      'pathauto' => TRUE,
    ));

    // Check if the token replacement has not actually replaced any values. If
    // that is the case, then stop because we should not generate an alias.
    // @see token_scan()
    $pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern);
    if ($alias === $pattern_tokens_removed) {
      return;
    }

    $alias = pathauto_clean_alias($alias);

    // Allow other modules to alter the alias.
    $context = array(
      'module' => $module,
      'op' => $op,
      'source' => &$source,
      'data' => $data,
      'type' => $type,
      'language' => &$language,
      'pattern' => $pattern,
    );
    drupal_alter('pathauto_alias', $alias, $context);

    // If we have arrived at an empty string, discontinue.
    if (!drupal_strlen($alias)) {
      return;
    }

    // New node! pathauto hasn't run for this node just yet. So, lets check it
    // just as pathauto would when creating a new alias.
    if (!drupal_strlen($path['alias'])) {
      $pathauto_unique = $alias;
      pathauto_alias_uniquify($pathauto_unique, $source, $language);

      $path['alias'] = $pathauto_unique;
    }

    // Seems that we already have an alias for that path in the system.
    // Lets go ahead and generate a domain specific one for the source.
    if ($alias != $path['alias']) {
      // Domain path already has an alias for that path so skip.
      if ($existing_alias = domain_path_lookup_path('source', $alias, $domain_source, $language)) {
        return;
      }
      form_set_value($form['domain_path'][$domain_source], $alias, &$form_state);
    }
  }
}
grndlvl’s picture

Status: Active » Postponed

I think in order to do this we will need to require the domain_source module.

riho’s picture

Any movement on this issue? This seems like a popular feature request and it's something that seems to come out of the box with most CMS-s I've worked with, but is giving me quite a headache in Drupal. Seems hard to belive that noone hasn't had the need to solve it so far.

agentrickard’s picture

@riho

Really? Context-specific URL aliases (e.g. multiple aliases for a single page) is an out-of-the-box feature? I don't think so.

I think you are confusing this feature with PathAuto module.

riho’s picture

The out of the box feature I meant is the ability to use the same alias for different nodes on different domains. Like the case Ron Williams described above:

Node 1 assigned to domain A (titled 'about foo')
Generate /about-foo for domain A
Skip generation for domain B as node is not assigned to domain
Node 2 assigned to domain B (titled 'about foo')
Generate /about-foo for domain B
Skip generation for domain A as node is not assigned to domain

Indeed, I understand that Domain Path is not exactly designed with that in mind, but whenever this issue has risen on the forums they get closed down with the link to this module and I have not found a further debate on it so far.

But let me try to visualize a use case. Let's say I have set up Domain Access for a blog site with hundreds of users and each user gets his own domain, but has no access to others data. Now the user shouldn't even have to think about SEO paths, he'll just enter his blog entry and the path gets generated in the background. Let's say John and Peter both post on their blogs with the title "First post". Is there really any sensible reason why john.blog.com/first-post and peter.blog.com/first-post couldn't exist?

If this case is better solved with giving PathAuto some support for Domain Access, fine by me, but from what I've gathered, Domain Path is the only existing module that even remotely addresses this issue.

agentrickard’s picture

Status: Postponed » Active

No, this is one of the scenarios that Domain Path _is_ intended to represent.

This issue is about automating those aliases -- which is not a simple task, partly due to how PathAuto is written, and partly due to the overhead involved in generating. This issue is the proper place to work on automated, multi-domain paths. Patches and reviews are welcome.

What I take issue with is your blatantly false assertion that "other CMSes" do this natively. They do not. In fact, none of the major CMSes even support a Domain Access model.

Raising that point derails work on this issue and is patronizing (if not insulting) to those working on it. Dredging up forum posts is irrelevant. We've already identified this as a need -- that's why there is a feature request for it.

Please be more considerate in the future.

riho’s picture

I really appreciate your work on Domain Access and it's modules. I had no intention of derailing the project or insulting anyone and I'm sorry if I came across that way. But I also have not claimed that all other CMSes have this feature, I only spoke from the experience of the ones I've worked with, which also means that this is something most my clients are used to and expect to get.

I'm more than willing to hop on board and help to work on making this happen. All I needed was to be pointed at the right direction, since it wasn't clear to me where the problem lies or why anyone hasn't found the need to solve this so far.

grndlvl’s picture

johnpitcairn’s picture

Watching. I have the same use-case as Ron in #3, for a couple of installations.

grndlvl’s picture

Status: Postponed » Active

#1425292: domain_path_node_insert() should update existing records rather than deleting/recreating them has been committed it's just waiting on tests. So this is ready to start.

grndlvl’s picture

budda’s picture

Just what we were looking for. Glad to see somebody else has been discussing it for a while.

budda’s picture

@agentrickard is there any scope for potentially financial sponsorship of this feature request maybe ?

agentrickard’s picture

Time is the issue more than money, for me at least.

See also #1412938: Allow domain path-auto support.

budda’s picture

See also #1412938: Allow domain path-auto support. <- that's this ticket. Was there another node intended?

agentrickard’s picture

scotthorn’s picture

Issue summary: View changes

I have a very limited solution working right now in an external module that could potentially be expanded. It's working at the moment for my limited use case, but I could imagine several potential conflicts that I'm not checking for right now and that I think I can avoid through permissions. Thought I'd post this in case it helps someone.

It uses pathauto to generate the path, adds a second checkbox to the path settings section of a node edit page for generating the url based on domains selected to publish, and then during hook_node_presave, if the node is published to all domains it lets pathauto generate a regular core path, but if only specific domains are selected, it fills in the value with pathauto's generation and lets domain_path save it during its hook_node_insert.

/*
 *  Invokes hook_form_FORM_ID_alter
 */
function my_module_form_node_form_alter(&$form, &$form_state) {
  $form['path']['pathauto']['#default_value'] = FALSE;
  $form['path']['pathauto']['#title'] = t('Generate automatic URL alias globally for all domains.');
  $form['path']['pathauto']['#weight'] = -2;

  $form['path']['domain_pathauto'] = array(
    '#type' => 'checkbox',
    '#title' => t('Generate automatic URL alias for each domain to which this node is published.'),
    '#default_value' => TRUE,
    '#weight' => -1,
  );
}


/*
 *  Invokes hook_node_presave
 */
function my_module_node_presave($node) {
  if (!isset($node->path) || !$node->path['domain_pathauto']) {
    return;
  }

  // If published to all domains
  if ($node->domain_site) {
    // Let pathauto do its thing normally
    $node->path['pathauto'] = 1;
    // Set specific domain paths to blank
    foreach ($node->domain_path as $key => $value) {
      if ($key == 'domain_path_delete') continue;
      $value = '';
    }
  }
  // Otherwise set appropriate domain paths to what pathauto generates.
  else {
    module_load_include('inc', 'pathauto');
    $options = array('language' => pathauto_entity_language('node', $node));
    $uri = entity_uri('node', $node);
    $new_path = pathauto_create_alias('node', 'return', $uri['path'], array('node' => $node), $node->type, $options['language']);
    foreach ($node->domain_path as $key => $value) {
      if ($key == 'domain_path_delete') continue;
      $node->domain_path[$key] = $new_path;
    }
  }
}

It required the module's hook to run after pathauto, so I made a .install file to lower my module's weight.

function my_module_install() {
  db_update('system')
    ->fields(array('weight' => 999))
    ->condition('name', 'my_module', '=')
    ->execute();
}
scotthorn’s picture

I realized after posting I neglected to add the line that makes it only set to domains that are published.

foreach ($node->domain_path as $key => $value) {
  if ($key == 'domain_path_delete') continue;
  if ($node->domains[$key] == $key) // Whoops!
    $node->domain_path[$key] = $new_path;
}

The full hook_node_presave function should actually then be

function my_module_node_presave($node) {
  if (!isset($node->path) || !$node->path['domain_pathauto']) {
    return;
  }

  // If published to all domains
  if ($node->domain_site) {
    // Let pathauto do its thing normally
    $node->path['pathauto'] = 1;
    // Set specific domain paths to blank
    foreach ($node->domain_path as $key => $value) {
      if ($key == 'domain_path_delete') continue;
      $value = '';
    }
  }
  // Otherwise set appropriate domain paths to what pathauto generates.
  else {
    module_load_include('inc', 'pathauto');
    $options = array('language' => pathauto_entity_language('node', $node));
    $uri = entity_uri('node', $node);
    $new_path = pathauto_create_alias('node', 'return', $uri['path'], array('node' => $node), $node->type, $options['language']);
    foreach ($node->domain_path as $key => $value) {
      if ($key == 'domain_path_delete') continue;
      if ($node->domains[$key] == $key)
        $node->domain_path[$key] = $new_path;
    }
  }
}
xaqrox’s picture

Thanks @scotthorn, that stuff is an excellent starting place. Here are a couple more moves to make, as far as Pathauto-like behavior is concerned:

  1. If you choose to have domain_path aliases auto-generated, with no regular alias, on save you are redirected to the system path (e.g. node/12345, not the domain_path alias.
  2. There is no memory of what was chosen for the new option "Generate automatic URL alias for each domain to which this node is published." What this means right now is no matter what option you choose, every time you load a node from "Generate automatic URL alias for each domain to which this node is published." is TRUE. Pathauto has a whole table, pathauto_state to store whether or not a given entity has an auto-generated alias, data from which is used to populate the option, see pathauto_entity_load() in pathauto.module (line 344 in version 7.x-1.3)

I'm working on this for a client with a tight timeline; hopefully I'll have time to make a patch that integrates #23 and #24 with this stuff, but if not maybe someone else could pick up where I left off ;)

jlpena123’s picture

Do we have solution in this issue already? I'm using Drupal 8 and I'm in the same situation with #3.

kumkum29’s picture

Hello,

i have the same problem. On several domains I have the same node title (same menu structure). After having set the patterns of pathauto, I get the alias for all my nodes But if the alias already exists i get a number at the end of the alias, e.g:

Node 1 on domain 1:
presentation
Node 2 on domain 2:
presentation-0
Node 3 on domain 3:
presentation-1
...

Is there a solution to create the same alias on all the domains ? I want to get "/presentation" for all domains.
I don't see how to proceed, because Domain Access module use an unique site, and unique pathauto settings...
I have seen the Domain Path module, but it focuse on the same node.

Thanks for your help.

I'm on D8.

steveoriol’s picture

Hello kumkum29,
Have you find a solution ? I have the same problem...

kumkum29’s picture

Bonjour Stève,

I haven't found a solution to resolve this specific case.
For the moment, I add an unique string/ID at the end of the alias (i.e. nid).
Example: www.mysite.com/mypage/125

steveoriol’s picture

StatusFileSize
new9.85 KB
new23.08 KB

Thank you kumkum29,
but your solution doesn't works for me.
The "Domain Path" module works after all caches reseted.
I have:
pathauto
and
Domain Path
and after drush cr

I can switch from :
https://domain1.com/fr/contact
to
https://domain2.com/fr/contact

C'est cool !

mably’s picture

Status: Active » Closed (outdated)

An experimental module as been provided for more recent Drupal versions.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.