Skip to content

Fatal error: Admin\Notes_Helper not found during plugin update via update-core.php #824

@unfulvio

Description

@unfulvio

Description

Plugins using the framework experience a fatal error when updated via /wp-admin/update-core.php:

Uncaught Error: Class 'SkyVerge\WooCommerce\PluginFramework\v5_15_2\Admin\Notes_Helper' not found

at woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php:825.

The error does not occur when updating via /wp-admin/plugins.php. The site recovers after the update completes (the error is transient).

Steps to Reproduce

  1. Install a gateway plugin that uses the framework (e.g. version A with framework namespace v5_15_X)
  2. Prepare an update that ships a different framework version (namespace v5_15_Y)
  3. Go to /wp-admin/update-core.php
  4. Update the plugin
  5. WordPress sends a "Your Site is Experiencing a Technical Issue" email with the fatal error above

Does NOT occur when updating via /wp-admin/plugins.php.

Root Cause

When updating via update-core.php, the update happens mid-request:

  1. WordPress loads the old plugin code into PHP memory (with old framework namespace, e.g. v5_15_X)
  2. The upgrader replaces all plugin files on disk with the new version (namespace v5_15_Y)
  3. The admin_footer hook fires, calling add_delayed_admin_notices()add_gateway_not_configured_notices()
  4. For a configured gateway, execution reaches line 825 — an unguarded Admin\Notes_Helper::note_with_name_exists() call
  5. The in-memory Composer autoloader tries to load v5_15_X\Admin\Notes_Helper, finds the file on disk, but the file now declares v5_15_Y\Admin\Notes_Helpernamespace mismatch → class not found → fatal \Error

This doesn't happen via plugins.php because that path uses AJAX, so the old plugin code is never loaded in the same request that writes the new files.

Affected Locations

1. woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php:825 (crash site)

} elseif ( $is_enhanced_admin_available && Admin\Notes_Helper::note_with_name_exists( $note_name ) ) {

No class_exists() guard. No try-catch.

2. Same file, line 813

} catch ( \Exception $exception ) {}

Catches \Exception but a "class not found" throws \Error (which is \Throwable, not \Exception). This path (for unconfigured gateways) would also fatal during an update.

3. woocommerce/Plugin/Lifecycle.php:199,205 (deactivation handler)

if ( SV_WC_Plugin_Compatibility::is_enhanced_admin_available() ) {
    Notes_Helper::delete_notes_with_source( ... );
}

No class_exists() guard. Called during plugin deactivation, which is part of the update process.

Suggested Fix

Add class_exists() guards before each unguarded Notes_Helper usage, and widen the catch to \Throwable:

Location 1 (class-sv-wc-payment-gateway-plugin.php:825):

} elseif ( $is_enhanced_admin_available && class_exists( Admin\Notes_Helper::class ) && Admin\Notes_Helper::note_with_name_exists( $note_name ) ) {

Location 2 (same file, line 813):

} catch ( \Throwable $exception ) {}

Location 3 (Plugin/Lifecycle.php:197):

if ( SV_WC_Plugin_Compatibility::is_enhanced_admin_available() && class_exists( Notes_Helper::class ) ) {

ClassName::class resolves at compile time to the FQN string without triggering autoloading. class_exists() then attempts autoloading but returns false gracefully (no fatal error) if the class can't be loaded due to a namespace mismatch.

Versions

  • Confirmed present in 5.15.12 and 6.1.4 (latest)
  • Affects all payment gateway plugins using this framework

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions