Problem/Motivation

There is no migration for menu links.

Proposed resolution

Write a migration for menu links.

Remaining tasks

Move the path validator resolution into a process plugin.

User interface changes

None.

API changes

In order to generate the route name and parameters, we need to make a call to PathValidator::getUrlIfValid(). But, we need to do it in a way that doesn't depend on user access to the path, so we need to update the PathValidator and PathValidatorInterface to have methods that skip access checking. This very small change is achieved by adding new methods.

Original report by @chx

Menu links are entities so this won't be too hard. The router_path is new but it's calculated by MenuLink::preSave so it's not something we need to bother with. I fully except this to be a 1:1 copy.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

penyaskito’s picture

Assigned: Unassigned » penyaskito
penyaskito’s picture

Status: Active » Needs work
FileSize
5.29 KB

Partial work.
Migration is done, but untested. Call it a day.

penyaskito’s picture

FileSize
12.57 KB
17.37 KB

Still WIP, almost done. Works quite well.

penyaskito’s picture

FileSize
23.53 KB

Added tests for the menu links source.
The migration test is still not working because the menu_name is not set to the menu link, or gets unset somewhere.

ultimike’s picture

Project: IMP » Drupal core
Version: » 8.x-dev
Component: Code » migration system
Related issues: +#2212211: Write D6 migration for menu links
jp.stacey’s picture

Should this issue have #2221733: [Meta] Test individual D6 > D8 content migrations as its parent, so all D6->D8 migration development/testing can continue to be marshalled into one checklist?

ultimike’s picture

joemoraca’s picture

I have tried this and get no menu links - do I need to apply one of the above patches (seem really old) or is it just not working yet?

penyaskito’s picture

Assigned: penyaskito » Unassigned

It won't work, too old and D8 menu_links were buggy at that time. I will try to get to this soonish, but unassigned so noone is stopped to work on this one.

ultimike’s picture

FileSize
22.86 KB

I started updating this patch - it's not complete:

  • I'm pretty sure a new Migrate Destination needs to be written for menu links.
  • I didn't look at the MigrateLinkSourceTest.

-mike

ultimike’s picture

FileSize
22.99 KB
2.47 KB

penyaskito and I were talking on IRC and think that we probably don't need to migrate all menu links - perhaps just where menu_links.module = 'menu' and menu_links.customized = 1. Thoughts?

I made some more progress on this patch tonight, but still some work to do in mapping fields and updating MigrateLinkSourceTest.

-mike

chx’s picture

I think that's a good idea. You have books in there which are migrated separately, you have the generated links there etc.

ultimike’s picture

FileSize
23.69 KB
8.73 KB

Ok - making steady progress now. Updated patch and interdiff attached.

At this point, the functional test is mostly there, I still haven't started updating the unit test.

According to a previous version of the patch, the route_name and route_parameters should be calculated on preSave() - but I don't see a preSave() method for menu_link_content...

I'm also not sure if we need to do anything with the p1...p9 D6 fields.

-mike

ultimike’s picture

FileSize
2.17 KB
23.65 KB

I just updated the unit test. Interdiff and patch attached.

@chx - I think you gave me a hint a couple of weeks ago on how to proceed on the route_name, route_parameters, and p1-p9 fields, but I don't remember what it was (sorry!) Can you point me in the right direction on what I need to do to finish this up?

Thanks,
-mike

ultimike’s picture

FileSize
25.35 KB
6.29 KB

chx gave me some guidance on how to move forward with this patch, I've made some progress, but I've hit a wall.

chx and I talked about using $pathValidator->getUrlIfValid() in Drupal\migrate_drupal\Plugin\migrate\source\d6\MenuLink::prepareRow() to set the route_name and route_parameters values for each link.

Unfortunately, when testing this, regardless of the path that I set up to migrate in MigrateMenuLinkTest, $pathValidator->getUrlIfValid() always returns false ($pathValidator->getPathAttributes() is actually what returns false). What am I missing?

Also - should I inject the $pathValidator into Drupal\migrate_drupal\Plugin\migrate\source\d6\MenuLink? I looked into doing this, but didn't see any other source classes that were using dependency injection, so I didn't have anything to model the code on.

I went ahead and added another groups of tests for an external URL.

-mike

chx’s picture

@ultimike what URLs did you try to feed to $pathValidator->getUrlIfValid() ? Could you try drush ev 'var_dump(Drupal::service("path.validator")->getUrlIfValid("something");'?

ultimike’s picture

@chx,

The URLs I tried passing to $pathValidator->getUrlIfValid() are the ones in the Drupal6MenuLink dump (the "link_path" field):

  1. admin
  2. admin/module
  3. http://drupal.org

Running drush ev 'var_dump(Drupal::service("path.validator")->getUrlIfValid("admin");' (or "admin/modules" or "https://drupal.org") returns nothing. Nada. Zip. Zilch. http://note.io/1tfftur

When I step through MigrateMenuLinkTest with my debugger, $pathValidator->getUrlIfValid() (in MenuLink::prepareRow()) returns false for the internal paths and a $url object for the external path.

Any ideas?

-mike

ultimike’s picture

Issue summary: View changes
ultimike’s picture

Status: Needs work » Needs review
FileSize
9.92 KB
28.36 KB

I chatted with chx about this, and he suggested that we add an access_check boolean to PathValidator and PathValidatorInterface that will allow us to check paths without also checking access to those paths (via #2346189: Denormalizing paths into route names/parameters is brittle / broken).

The attached patch incorporates these changes.

-mike

Status: Needs review » Needs work

The last submitted patch, 19: 2178703-19.patch, failed testing.

chx’s picture

Status: Needs work » Needs review
FileSize
29.55 KB

Although the path validator code in #19 is mine after a discussion with dawehner it's now clear that it's not a nice and secure experience. Let's add some new methods instead. However the test still fails, and with good reason. Stay tuned.

Status: Needs review » Needs work

The last submitted patch, 21: 2178703_20.patch, failed testing.

chx’s picture

Issue summary: View changes
ultimike’s picture

Status: Needs work » Needs review
FileSize
31.83 KB
9.61 KB

Based on a conversation with chx, we decided to:

  1. Move the pathValididator stuff from the MenuLink source to a process plugin. In fact, I created two Migrate (not Migrate Drupal) process plugins - one for route_name and one for route_parameters. I wasn't sure if there was a good way to calculate two destination fields from one source field using a single process plugin. If there is a better way, let me know and I'll be happy to implement.
  2. I removed an obsolete test and some comments.
  3. I updated the migrate_drupal.source.schema.yml with the new d6_menu_link stuff.

Updated patch and interdiff attached.

-mike

chx’s picture

Plugins have ContainerFactoryPluginInterface so that you can implement create() and inject the path validator instead. See how the Migration process plugin does it.

Please rename the plugin to Route (hopefully you have PHPstorm, that makes PSR-compatible renames easy) and set $value to ['route_name' => $route_name, 'route_parameters' => $route_parameters']. Then the YAML can be this:

  route:
    plugin: route
    source: link_path
  route_name: @route/route_name
  route_parameters: @route/route_parameters

Noone particularly cares you will end with a "route" property set that is not saved by the destination anyways...

chx’s picture

I was wrong: the Route plugin must always return an array of not two but four elements: url where the value is NULL for internal paths and the url for externals and then route_name (NULL), router_parameters ([]) and options ([]) which are empty for external, set for internal. You can find the code in MenuLinkContentForm::extractFormValues (what does that code do in a form? don't ask) just replace the getUrlIfValid with a getUrlIfValidWithoutAccessCheck.

ultimike’s picture

FileSize
32.05 KB
9.88 KB

chx showed me the way on IRC on this one. Changes include:

  1. Combined "route_name" and "route_parameters" Migrate process plugins into a single "route" plugin.
  2. New "route" plugin now takes "options" as a second source field.
  3. New "route" plugin now outputs "url" and "options" in addition to "route_name" and "route_parameters".
  4. New "route" plugin will support D6 and D7 "options['query']" variable.
  5. MigrateMenuLinkTest now includes a test with a link that has a query string.

Patch and interdiff attached.

-mike

benjy’s picture

This patch looks great, nice work guys. Few super tiny nitpicks below related to comments. I also think we might need some unit tests for the new path validator methods?

  1. +++ b/core/modules/migrate_drupal/config/install/migrate.migration.d6_menu_links.yml
    @@ -0,0 +1,50 @@
    +  route:
    +    plugin: route
    +    source:
    +      - link_path
    +      - options
    +  route_name: @route/route_name
    +  route_parameters: @route/route_parameters
    +  url: @route/url
    

    Nice approach :)

  2. +++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/MenuLink.php
    @@ -0,0 +1,107 @@
    +  public function prepareRow(Row $row) {
    +    $row->setSourceProperty('options', unserialize($row->getSourceProperty('options')));
    +    $row->setSourceProperty('enabled', !$row->getSourceProperty('hidden'));
    

    Missing comment.

  3. +++ b/core/modules/migrate_drupal/src/Tests/Dump/Drupal6MenuLink.php
    @@ -0,0 +1,289 @@
    + * Contains \Drupal\migrate_drupal\Tests\Dump\Drupal6Menu.
    

    Wrong comment.

  4. +++ b/core/modules/migrate_drupal/src/Tests/Dump/Drupal6MenuLink.php
    @@ -0,0 +1,289 @@
    + * Database dump for testing menu migration.
    

    Wrong comment.

chx’s picture

FileSize
34.32 KB
3.52 KB
benjy’s picture

Status: Needs review » Reviewed & tested by the community

Looks great!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Path/PathValidator.php
    @@ -81,7 +81,28 @@ public function isValid($path) {
    +  public function isValidWithoutAccessCheck($path) {
    +    return (bool) $this->getUrlIfValidWithoutAccessCheck($path);
    +  }
    

    Why are we adding this? I guess for parity with the IfValid() methods but the only usecase is in a test. Seems superfluous.

  2. +++ b/core/lib/Drupal/Core/Path/PathValidatorInterface.php
    @@ -24,6 +24,19 @@
    +   * Returns an URL object, if the path is valid.
    +   *
    +   * Unlike getUrlIfValid(), access check is not performed.
    +   *
    +   * @param string $path
    +   *   The path to check.
    +   *
    +   * @return \Drupal\Core\Url|false
    +   *   The url object, or FALSE if the path is not valid.
    +   */
    +  public function getUrlIfValidWithoutAccessCheck($path);
    

    Do we need to document the security concerns with using this method? Like we should only be using this if the path is not going to be presented to a user - right?

chx’s picture

Status: Needs work » Needs review
FileSize
2.53 KB
33.06 KB

Thanks for reviewing this so quickly and for pointing out the missing doxygen. However, the superfluous argument is the classic case of bikeshed when Drupal 8 contains so many atomic plants. Regardless, I removed it.

benjy’s picture

Status: Needs review » Reviewed & tested by the community

All points in #31 have been addressed.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This issue is a unfrozen change (migrate) as per https://www.drupal.org/core/beta-changes and it's benefits outweigh any disruption. Committed 92c2514 and pushed to 8.0.x. Thanks!

  • alexpott committed 92c2514 on 8.0.x
    Issue #2178703 by ultimike, chx, penyaskito: Migrate D6 menu links.
    
eliza411’s picture

FileSize
9.74 KB

I just ran a migration from a fairly simple legacy D6 site to a clean 8.0.x site and I'm getting the following error. The full stack trace is attached.

Running d6_menu_links                                                  [ok]
exception 'PDOException' with message 'SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'menu_name' cannot be null' in                                   [error]
/var/www/tag1eight/drupal/core/lib/Drupal/Core/Database/Statement.php:61
benjy’s picture

Hmm, that's interesting menu_name is a look-up from the menu migration. Did that run first without any errors?

Relevant yaml:

  menu_name:
    plugin: migration
    migration: d6_menu
    source: menu_name

We could add a process to skip the row on null menus but i'm more interested in how the menu_name ended up null in the first place.

chx’s picture

Status: Fixed » Closed (fixed)

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