Migrate Redirect Map builds the 301 redirects a Drupal 7 to Drupal 10/11 migration needs and reports, before launch, which legacy URLs would return 404. It reads the migrate map tables of the node, taxonomy term, user and media migrations (both d7_node_complete:bundle IDs and the upgrade_d7_* IDs written by the Migrate Drupal UI), the legacy url_alias table and the legacy redirect table, resolves each legacy path to the alias the migrated entity actually has on the new site, and classifies it as redirect / identical / already exists / unresolved. A dry run writes the full map to CSV; creation runs through the Batch API (or drush migrate-redirect-map:build) with loop protection and no duplicates. Drupal 7 translation sets (tnid) are followed, and aliases in a language the new site does not have become language-neutral redirects.
How it differs from existing solutions
- The Redirect module's d7_path_redirect migration only copies redirects that already existed on the Drupal 7 site. It does nothing for aliases that changed because entity IDs were renumbered or because Pathauto patterns changed.
- Core's d7_url_alias migration copies the old aliases, which keeps old URLs working only when nothing else changed; it cannot tell you which paths now resolve nowhere.
- This module compares legacy paths with the new site's real aliases after the migrations and alias generation have run, creates a redirect only where the URL actually changed, and gives a list of unresolved paths (views, custom callbacks, file paths) so they can be mapped by hand before launch.
Manual reviews
- https://www.drupal.org/project/projectapplications/issues/3607891#commen... ([1.0.x] JSON-LD Generator)
- https://www.drupal.org/project/projectapplications/issues/3609892#commen... ([1.0.x] Universal AI Provider)
- https://www.drupal.org/project/projectapplications/issues/3573362#commen... ([1.0.x] Emporiqa)
- https://www.drupal.org/project/projectapplications/issues/3623357#commen... ([1.0.x] wxrks TMGMT Connector)
- https://www.drupal.org/project/projectapplications/issues/3627905#commen... ([1.0.x] Smart Redirects Manager)
- https://www.drupal.org/project/projectapplications/issues/3630186#commen... ([1.0.x] Magic HTML Mail)
Comments
Comment #2
shaker-gspikes commentedComment #3
vishal.kadamComment #4
shaker-gspikes commentedComment #5
avpadernoThank you for applying!
Before giving links helpful to understand how the review process works, what to expect from a review, and what to do to avoid a review takes more time than needed, I would like to thank all the reviewers for the work they do.
These applications are volunters-driven, which also means it is not possible to predict when an application will be marked fixed and the applicant will get the permission to opt projects into security advisory policy. While we aim to make an application as quick as possible, it is also important for us that more people review the project used for an application. In this way, we make sure applications do not miss some important points that should be instead reported.
Applications are not meant to be complete debugging sessions that eliminate every existing bug, though. I apologize if sometimes applications seem to go into too-detailed reviews.
Please read Review process for security advisory coverage: What to expect for more details and Security advisory coverage application checklist to understand what reviewers look for. Tips for ensuring a smooth review gives some hints for a smoother review.
The important notes are the following.
Keep in mind that once the project is opted into security advisory coverage, only Security Team members may change coverage.
To the reviewers
Please read How to review security advisory coverage applications, Application workflow, What to cover in an application review, and Tools to use for reviews.
The important notes are the following.
For new reviewers, I would also suggest to first read In which way the issue queue for coverage applications is different from other project queues.
Comment #6
vishal.kadam1. FILE: README.md
The README file is missing the required section - Configuration.
2. FILE: migrate_redirect_map.module
For a new module that aims to be compatible with Drupal 10 and Drupal 11, I would rather implement hooks as class methods as described in Support for object oriented hook implementations using autowired services.
Comment #7
avpadernoComment #8
shaker-gspikes commentedThank you both. The two points from #6 are addressed in 1.0.x at 6456b082; the pipeline on that commit is green.
1. README.md now has a Configuration section between Installation and Usage. It says that nothing is stored and describes the three per-run options (legacy database key, migrations, dry run) and the permission the report page needs.
2. hook_help() is now a method of the new
src/Hook/MigrateRedirectMapHooks.phpclass (#[Hook('help')], string translation injected through autowiring, service declared inmigrate_redirect_map.services.yml). Because the module supports Drupal 10.3,migrate_redirect_map.modulekeeps a proceduralmigrate_redirect_map_help()marked#[LegacyHook]that delegates to the same service, so 10.3 to 11.0 still find the hook and 11.1+ does not run it twice. Checked on Drupal 10.6: the help page and the report page text render once and the route link resolves; the unit tests pass.Setting back to Needs review. The second round of reviews is already linked in the issue summary (#3607891 comment #16802885, #3609892 comment #16802888, #3573362 comment #16802890), so I am adding the review bonus tag back; please remove it if that round does not qualify.
Comment #9
avpadernoReviews needs to be for different project applications; reviewing the same application twice does not count as second round of reviews.
Comment #10
shaker-gspikes commentedI added three more manual reviews to the issue summary. They are of applications I had not reviewed before, since a second round has to cover different applications:
Adding the PAreview: review bonus tag again.
Comment #11
anil_bhutia commentedThank you for the fixes in 6456b08. I reviewed the 1.0.x branch manually; phpcs is clean and the pipeline for that commit is green.
Previously raised points
hook_help()is now a#[Hook('help')]method onMigrateRedirectMapHooks, registered by class name withautowire: true, and the proceduralmigrate_redirect_map_help()has#[LegacyHook]and delegates to the service. That is the pattern from https://www.drupal.org/node/3442349. (Theclass:line in services.yml is redundant when the service ID is the class name.)Checked and fine: all queries use the query builder (no concatenated table names or values); the only route is a form, so no CSRF issue; report cells are plain strings and
#markup/help text use placeholders; the builder, form and Drush command use DI.src/RedirectMapBuilder.php
node/12 → node/345without checking whether node 12 exists on the new site, and a legacy alias may now belong to a different entity or match a real route (for example a D7 alias such asuser/login). Redirect'sRedirectRequestSubscriberruns before routing and appliesprocessInbound()first, so such a redirect would also capture every alias of the new node 12. Could these be reported with a separate "conflict" status and skipped?createRedirect()saves without$redirect->validate(). Redirect has aRedirectUniqueHashconstraint and a unique key onhash, whilefindMatchingRedirect()only finds enabled redirects, so an existing disabled redirect (or two legacy languages that both becomeund) ends in an integrity exception. Please validate and report violations; the legacystatus_code(line 185) should also be checked againstredirect_status_code_options().elseifon line 192 is always true, soSTATUS_EXTERNALis never reported and external legacy targets become ordinary redirects. If that is intended, the dead branch and constant can go; I would still suggest reporting external targets separately (ideally opt-in). Also,LegacyPathMapper::isExternal()says it followsUrlHelper::isExternal(), but it acceptsjavascript:/data:and does not treat//hostas external. Please useUrlHelper::isExternal()plusUrlHelper::isValid($url, TRUE)and allow only http/https.sourceid1/destid1) through the default connection. Iterating the id map viaMigrateIdMapInterface(currentSource()/currentDestination()) avoids relying onSqlplugin internals, and filtering migration IDs beforecreateInstances()would avoid instantiating every migration.source_options/redirect_optionsare not read, so query-specific legacy redirects collapse and target query strings are lost.=,+,-,@(or tab/CR) are not neutralised, and legacy aliases were typed by editors. Please prefix such cells, for example with'.reasonstrings are shown in the UI but are not translatable.src/Form/MigrateRedirectMapForm.php
submitForm()(one alias and one redirect lookup per path); only creation is batched. For the 50,000-alias case in the README, consider building the map in a batch too.temporary://on every submit, never removed, and not downloadable from the UI. Returning it as an attachment from the submit handler would be more useful.migrate_redirect_map.routing.yml
administer redirects. A dedicated permission withrestrict access: trueseems more appropriate.src/Drush/Commands/MigrateRedirectMapCommands.php
--dry-runthe command writes straight away, while the UI defaults to a dry run. Please consider$this->io()->confirm().Tests
LegacyPathMapperis unit tested. Since the resolver is the core of the module, please add a kernel test (for example extendingMigrateTestBase, which provides themigratesource connection) that creates minimalurl_alias/redirecttables and id map rows and asserts each status, including the collision, duplicate and external-target cases above.Setting to Needs work mainly for points 1-3 on RedirectMapBuilder; the rest are recommendations.
Comment #12
anil_bhutia commented