Problem/Motivation

The post-update views_data_export_post_update_remap_display_filesystem introduced in #3200974 renames the legacy store_in_public_file_directory option to export_filesystem, but it filters by exact plugin id:

if (($display['display_plugin'] ?? NULL) !== 'data_export') {
  continue;
}

Subclass display plugins that inherit the same option are skipped. The most common case is data_export_excel from the views_data_export_excel module (its ExcelExport display extends this module's DataExport). Any view with such a display still carries the legacy store_in_public_file_directory key after the post-update has run, and Configuration Inspector reports it as "missing schema".

Steps to reproduce

  1. Have a site with views_data_export and views_data_export_excel, that ran views_data_export_post_update_remap_display_filesystem when it shipped.
  2. Open any view with a data_export_excel display in Configuration Inspector (admin/config/development/configuration/inspect).
  3. Observe a "missing schema" entry on display.{id}.display_options.store_in_public_file_directory.

Proposed resolution

Add a follow-up post-update views_data_export_post_update_remap_subclass_display_filesystem that re-runs the same remap logic but matches on the presence of the legacy store_in_public_file_directory key rather than on a specific plugin id, so it covers data_export_excel and any other in-tree or out-of-tree subclass.

The original post-update is left alone. Sites that haven't yet run it get the standard behaviour; sites that already ran it pick up the missed subclass displays from the new follow-up.

Remaining tasks

  • Review the MR.
  • Ship.

Test coverage

No new test added. The change is a follow-up post-update only and is verified by Configuration Inspector returning no errors against affected views after running drush updb.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mably created an issue. See original summary.

mably’s picture

Status: Active » Needs review
mably’s picture

If you don't want to wait for this to land in a release, three options for fixing existing sites manually.

Option A: one-shot script via devel/php

Replicates exactly what the proposed follow-up post-update does, scanning every view:

$config_factory = \Drupal::configFactory();
$updated = [];
foreach ($config_factory->listAll('views.view.') as $name) {
  $config = $config_factory->getEditable($name);
  $displays = $config->get('display') ?? [];
  $changed = FALSE;
  foreach ($displays as $display_id => $display) {
    $options = $display['display_options'] ?? [];
    if (!array_key_exists('store_in_public_file_directory', $options)) {
      continue;
    }
    $legacy = $options['store_in_public_file_directory'];
    $options['export_filesystem'] = ($legacy === NULL || !empty($legacy)) ? 'public' : 'private';
    unset($options['store_in_public_file_directory']);
    $displays[$display_id]['display_options'] = $options;
    $changed = TRUE;
  }
  if ($changed) {
    $config->set('display', $displays);
    $config->save();
    $updated[] = $name;
  }
}
print_r($updated);

After running, export the active config (drush cex -y) so the cleaned-up yml lands in version control. Otherwise the next drush cim re-introduces the legacy key from the on-disk file.

Option B: edit the on-disk config yml directly

In each affected views.view.{id}.yml under config/sync/, find the offending display's display_options block and either:

  • delete the store_in_public_file_directory: false line, or
  • replace it with export_filesystem: private (or public) to match what the original post-update would have written.

Then run drush cim -y. Reproducible across environments and shows up cleanly in git history.

Option C: re-save the affected views in the UI

For each view, edit any display, click Save. The save round-trip drops keys not declared in the plugin's current option set, so the legacy key disappears. Quick fix for one or two views, painful at scale.

steven jones’s picture

Sorry about this one!

Let's get it sorted and roll a new release...

mably’s picture

Great news, thanks!

steven jones’s picture

I was going to tighten up the post update hook to check to see if the display plugin is actually a sub-class of the base VDE one. Is that worth it do you think?

mably’s picture

Claude suggests to keep the key-presence check:

Worth noting upfront: VDE doesn't currently expose a marker interface — the base class Drupal\views_data_export\Plugin\views\display\DataExport extends core's RestExport directly, and the only known subclass (views_data_export_excel's ExcelExport) just extends that concrete class. So a "subclass check" would have to be is_subclass_of($plugin, DataExport::class), which:

  • hardcodes the class name into a post-update;
  • still requires the plugin to be discoverable at update time — a sister module disabled at the moment of drush updb means its orphaned config (which is exactly what we're cleaning up) gets skipped;
  • misses anything built via trait composition, a future refactor that swaps the base, or a third-party module that re-implements the same data-export semantics.

If at some point you introduce a DataExportDisplayInterface, an instanceof against it would be the right gate going forward — but for this one-shot cleanup, those points all argue for the data-driven approach already in MR !99.

The other thing: store_in_public_file_directory is a VDE-specific name, so the odds of an unrelated display plugin happening to have that exact key are basically zero — the practical false-positive risk is theoretical. The migration logic is already idempotent and same-semantics as the original post-update, so even in an unlikely collision the worst case is one extra config write that maps NULL to public, which matches core's default for that config anyway.

If you want a belt-and-braces compromise, a str_starts_with($display['display_plugin'], 'data_export') guard is cheap and string-only — no plugin manager calls, no class autoload — but I don't think even that's needed.

mably’s picture

But we are probably the only ones subclassing the VDE class for now with our Excel module.

So it should also probably be ok to use an instanceof here 😉

steven jones’s picture

Thanks for the thoughts, I've re-worked the update hook, do you think that looks okay to you? It worked on my machine :D

mably’s picture

Status: Needs review » Reviewed & tested by the community

Looks ok to me.

  • steven jones committed 211603ea on 8.x-1.x authored by mably
    fix: #3589219 Post-update remap_display_filesystem skips data_export...
steven jones’s picture

Status: Reviewed & tested by the community » Fixed

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.

Status: Fixed » Closed (fixed)

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