Checkout Field Editor for WooCommerce Autoloader Processes Unrelated Classes and Has a Broken Fallback Path

Two issues exist in the custom PHP autoloader used by Checkout Field Editor (Checkout Manager) for WooCommerce by ThemeHigh.

The affected file is:

includes/class-thwcfd-autoloader.php

The problems are:

1. The autoloader handles class names
   that do not belong to the plugin.

2. The final fallback path concatenates
   the plugin directory and filename
   without a slash.

Both issues can be confirmed directly in the current plugin source. The latest WordPress.org release is 2.2.1, while the autoloader itself has remained unchanged since its earlier 2.1.0 implementation.

Issue 1: The Autoloader Processes Unrelated Classes

The plugin registers:

spl_autoload_register( array( $this, 'autoload' ) );

PHP can have multiple registered autoloaders. When an undefined class is referenced, PHP calls registered autoloaders in sequence and gives each one an opportunity to load it.

An individual plugin autoloader should therefore normally determine whether the requested class belongs to that plugin before attempting to resolve a file.

The current THWCFD_Autoloader::autoload() method does not do that.

It begins by converting every requested class to lowercase:

$class = strtolower( $class );
$file  = $this->get_file_name_from_class( $class );

It then checks several known ThemeHigh prefixes:

thwcfd_admin
thwcfd_public
thwcfd_utils
wcfe_checkout_field
wcfe_checkout_section
thwcfd_block

But if none match, the code falls through to:

$path = $this->include_path . '/includes/';

The current source shows this generic else path explicitly.

That means an unrelated request such as:

class_exists( 'ForeignClass' );

is transformed into:

foreignclass

and then:

class-foreignclass.php

before the plugin checks:

/plugin/includes/class-foreignclass.php

Why That Is Wrong

ForeignClass does not belong to Checkout Field Editor.

Its autoloader should simply return and allow PHP to continue to the next registered autoloader.

Instead, this plugin attempts to resolve the class itself.

The PHP manual specifically describes spl_autoload_register() as maintaining a queue of autoload callbacks. That makes early rejection important when multiple plugins, themes or libraries coexist in one WordPress request.

The problem becomes visible if another file happens to exist at:

includes/class-foreignclass.php

because ThemeHigh’s autoloader can load that file even though ForeignClass is unrelated to ThemeHigh.

This Is Primarily an Autoloader-Scope Bug

It is important not to exaggerate the security impact.

This behavior does not, by itself, let a remote attacker choose arbitrary filesystem paths or upload arbitrary PHP files.

The filename is constructed from the requested PHP class name and loaded only when an expected PHP file already exists and is readable.

The main concerns are instead:

  • autoloader ownership conflicts;
  • accidental file loading;
  • unnecessary filesystem checks;
  • unpredictable behavior when multiple plugins use similar filenames;
  • harder debugging;
  • violation of normal autoloading isolation.

So this is best categorized as an autoloading correctness and interoperability issue, rather than automatically as a security vulnerability.

The Autoloader Already Has Clear Class Families

The source makes the plugin’s naming conventions clear.

Its internal classes predominantly belong to two families:

THWCFD_
WCFE_

Examples handled by the existing rules include:

THWCFD_Admin_...
THWCFD_Public_...
THWCFD_Utils_...
THWCFD_Block_...

WCFE_Checkout_Field_...
WCFE_Checkout_Section_...

Therefore, the safest minimal change is to reject everything else before constructing a filename.

For compatibility with the plugin’s PHP requirements, this should not depend on modern functions such as str_starts_with().

A straightforward check is:

$class = strtolower( $class );

$is_thwcfd_class = strpos( $class, 'thwcfd_' ) === 0;
$is_wcfe_class   = strpos( $class, 'wcfe_' ) === 0;

if ( ! $is_thwcfd_class && ! $is_wcfe_class ) {
	return;
}

This preserves both class families already represented by the current autoloader while allowing other registered autoloaders to handle unrelated names.

Why the Check Should Happen Early

The prefix check should happen before:

$file = $this->get_file_name_from_class( $class );

and before any filesystem lookup.

Conceptually:

Undefined class requested
        ↓
Does it begin THWCFD_ or WCFE_?
        ↓
       No
        ↓
Return immediately
        ↓
Let next autoloader handle it

Only plugin-owned classes should continue through:

class name
    ↓
filename conversion
    ↓
directory selection
    ↓
is_readable()
    ↓
require_once

That also avoids unnecessary filesystem calls for every unrelated class PHP attempts to autoload.

Issue 2: Missing Slash in the Fallback Path

The second problem is simpler.

The constructor explicitly sets:

$this->include_path = untrailingslashit( THWCFD_PATH );

WordPress documents untrailingslashit() as removing trailing / and \ characters.

For example:

Before:
.../woo-checkout-field-editor-pro/

After:
.../woo-checkout-field-editor-pro

That means every filename appended directly to $this->include_path must supply its own separator.

Most paths in the autoloader do this correctly:

$this->include_path . '/admin/';
$this->include_path . '/public/';
$this->include_path . '/includes/utils/';
$this->include_path . '/includes/model/';
$this->include_path . '/block/';
$this->include_path . '/includes/';

But the final fallback does not.

The current code contains:

$this->load_file( $this->include_path . $file );

Because $include_path has no trailing slash, the result is malformed.

Example of the Broken Path

Suppose:

$this->include_path =
/wp-content/plugins/woo-checkout-field-editor-pro

and:

$file =
class-thwcfd-example.php

The current code produces:

/wp-content/plugins/woo-checkout-field-editor-proclass-thwcfd-example.php

instead of:

/wp-content/plugins/woo-checkout-field-editor-pro/class-thwcfd-example.php

The fallback therefore cannot find a legitimate file in the plugin root.

The Minimal Fix

Change:

$this->load_file( $this->include_path . $file );

to:

$this->load_file( $this->include_path . '/' . $file );

This is consistent with the rest of the autoloader’s path construction.

Another theoretical option would be to keep $include_path with a trailing slash using WordPress’s trailingslashit(). WordPress provides that function specifically for constructing paths.

However, that would require reviewing the rest of this class because most current paths already prepend /.

For a bug fix, adding the missing slash at the fallback is the smaller and safer change.

Recommended Combined Patch

A minimal update to autoload() can preserve the existing directory rules while fixing both problems:

public function autoload( $class ) {
	$class = strtolower( $class );

	$is_thwcfd_class = strpos( $class, 'thwcfd_' ) === 0;
	$is_wcfe_class   = strpos( $class, 'wcfe_' ) === 0;

	if ( ! $is_thwcfd_class && ! $is_wcfe_class ) {
		return;
	}

	$file      = $this->get_file_name_from_class( $class );
	$path      = '';
	$file_path = '';

	if ( isset( $this->class_path[ $class ] ) ) {
		$file_path = $this->include_path . '/' . $this->class_path[ $class ];

	} else {
		if ( strpos( $class, 'thwcfd_admin' ) === 0 ) {
			$path = $this->include_path . '/admin/';

		} elseif ( strpos( $class, 'thwcfd_public' ) === 0 ) {
			$path = $this->include_path . '/public/';

		} elseif ( strpos( $class, 'thwcfd_utils' ) === 0 ) {
			$path = $this->include_path . '/includes/utils/';

		} elseif ( strpos( $class, 'wcfe_checkout_field' ) === 0 ) {
			$path = $this->include_path . '/includes/model/fields/';

		} elseif ( strpos( $class, 'wcfe_checkout_section' ) === 0 ) {
			$path = $this->include_path . '/includes/model/';

		} elseif ( strpos( $class, 'thwcfd_block' ) === 0 ) {
			$path = $this->include_path . '/block/';

		} else {
			$path = $this->include_path . '/includes/';
		}

		$file_path = $path . $file;
	}

	if (
		empty( $file_path ) ||
		( ! $this->load_file( $file_path ) && strpos( $class, 'thwcfd_' ) === 0 )
	) {
		$this->load_file( $this->include_path . '/' . $file );
	}
}

The two functional changes are only:

1. Reject classes outside THWCFD_ / WCFE_.

2. Add "/" to the root fallback path.

The existing routing logic remains intact.

Suggested Regression Tests

The fix should be accompanied by focused tests.

1. Unrelated Classes Must Be Ignored

Given:

ForeignClass

the ThemeHigh autoloader should not check or load:

/includes/class-foreignclass.php

Expected result:

ThemeHigh autoloader → return
Other autoloaders → continue normally

2. THWCFD Classes Must Still Load

Test representative classes from:

THWCFD_Admin_
THWCFD_Public_
THWCFD_Utils_
THWCFD_Block_

Their existing directory mappings must remain unchanged.

3. WCFE Classes Must Still Load

At minimum test:

WCFE_Checkout_Field_...
WCFE_Checkout_Section_...

This guards against accidentally fixing the first issue by accepting only the THWCFD_ prefix.

4. Root Fallback Must Contain the Separator

A failed primary lookup for a THWCFD_ class should generate:

/plugin/class-thwcfd-example.php

not:

/pluginclass-thwcfd-example.php

5. Unknown THWCFD/WCFE Classes Should Fail Safely

A plugin-owned prefix with no corresponding file should simply leave the class unresolved.

It should not generate warnings or load unrelated files.

Could the First Problem Affect Other WordPress Plugins?

Potentially, yes.

A WordPress installation commonly has many autoloaders registered by:

  • WooCommerce;
  • payment gateways;
  • Composer dependencies;
  • themes;
  • other plugins.

When PHP encounters an undefined class, multiple registered autoloaders may be asked to resolve it. PHP explicitly supports this autoloader queue.

That makes namespace or prefix ownership important.

A WooCommerce checkout plugin should not attempt to resolve:

ForeignClass
Vendor_Class
AnotherPlugin_Service

unless those classes belong to its own supported class family.

Returning early is cleaner and reduces the chance of one plugin interfering with another plugin’s loading process.

Why This May Have Gone Unnoticed

Both bugs can remain invisible under normal operation.

The unrelated-class issue requires an external class request whose generated filename also happens to exist in ThemeHigh’s includes directory before it becomes functionally obvious.

The missing-slash issue only matters when execution reaches the root fallback.

Normal ThemeHigh classes that successfully resolve through:

/admin/
/public/
/includes/
/includes/utils/
/includes/model/
/block/

never need that fallback.

So a store can run successfully for years while both defects remain present.

Current Plugin Status

The affected plugin is Checkout Field Editor (Checkout Manager) for WooCommerce by ThemeHigh.

The current WordPress.org changelog lists version 2.2.1, adding WooCommerce 11.2 compatibility and PHP 8.2 improvements.

The public source mirror shows that class-thwcfd-autoloader.php still contains the same implementation and identifies the file’s last substantive change with the 2.1.0-era code.

Therefore, these are not merely observations against an obsolete archived version.

They remain relevant to the current source.

Recommended Fix

The maintainers should make two small changes:

$class = strtolower( $class );

if (
	strpos( $class, 'thwcfd_' ) !== 0 &&
	strpos( $class, 'wcfe_' ) !== 0
) {
	return;
}

and:

$this->load_file( $this->include_path . '/' . $file );

The first keeps the plugin’s autoloader scoped to its own class families.

The second restores the root fallback path that the existing code clearly intends to use.

Neither change requires altering the established mappings for THWCFD_ or WCFE_ classes.

About the author

Tahrim Naziat

WordPress and Server Troubleshooting Specialist

Tahrim Naziat is a senior WordPress and JavaScript developer with more than 14 years of experience specializing in WordPress troubleshooting, WooCommerce, PHP compatibility, plugin conflicts, malware cleanup, performance optimization, Nginx, Redis, and production server issues. He documents practical solutions based on real WordPress debugging, technical investigations, and client projects.

Leave a Comment