Skip to content

First-party Woo plugin detection for product customizations#4552

Open
strangerkir wants to merge 16 commits into
dev/developfrom
dev/PCP-6704-woo-plugin-detection-for-product-customizations
Open

First-party Woo plugin detection for product customizations#4552
strangerkir wants to merge 16 commits into
dev/developfrom
dev/PCP-6704-woo-plugin-detection-for-product-customizations

Conversation

@strangerkir

@strangerkir strangerkir commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Description

Provides two services for:

  • detecting which plugins of the listed below are active
  • finding all products related to one of those plugins

Steps to Test

  1. Install and activate plugins from the list below
  2. Import products from the attached file
  3. Install and activate the attached plugin
  4. Run wp ppcp-compat-products scan
  5. Make sure that all 8 plugins are reported as active, and 8 products are reported as affected - one per plugin

Plugins list

Products to import
wc-product-export-22-7-2026-1784711315948.csv

Plugin for testing
ppcp-compat-products.zip

Documentation

  • This PR needs documentation (has the "Documentation" label).
    So far, only internal documentation is needed. This PR doesn't produce any visible changes; it only registers services which aren't used yet.

Notes

  • Failing CI was a pre-existing issue, so I'm ignoring it here to avoid scope creep

@strangerkir
strangerkir marked this pull request as ready for review July 22, 2026 13:17
*/
public function test_all_false_when_no_plugin_is_active(): void {
$this->plugin_detector->shouldReceive( 'scan' )->once()->andReturn( $this->all_inactive() );
$this->logger->shouldNotReceive( 'warning' );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Remove this

Reason: Log output should not be tested/expected; when we add or change log output, it should not impact the test

Usual mock for the logger:

// Define the logger with shouldIgnoreMissing in the setUp() method, then forget about it:
$this->logger = Mockery::mock(LoggerInterface::class)->shouldIgnoreMissing();

@strangerkir strangerkir Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still keep it tested from a practical point of view. If a 3rd-party API changes and the function no longer exists, this warning is the only way to know that the check itself was a false negative. So I'd say this is an important part of the logic, not just regular logging.

But I agree that testing for a particular string in logs may be too much, so I'm leaving just a warning assertion, without any message checks.

UPD: removed here, this one is useless, as we never examine real 3rd-party functions here. My comment above related to the next warning assertion.

/**
* Interface PluginDetectorInterface
*/
interface PluginDetectorInterface {

@stracker-phil stracker-phil Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Delete this interface

Reason: I cannot see any hint that there will be more than one implementation of this interface. Instead of adding it right away, we generally try starting with a single class and add an interface once it's really needed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed 👍

Comment on lines +17 to +19
/**
* @inheritDoc
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Remove the docblock

Reason: it's 100% noise, adding no value. PhpStorm expands the parent docblock automatically.

Of course, if the interface is removed (as I suggested), move the original docblock here 😉

@strangerkir strangerkir Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The interface was removed, and I only left the file header here, removing the class docblock. It looks like in this project we often prefer file headers, so I followed the same pattern.

Comment on lines +33 to +37
/**
* Checks whether WooCommerce Subscriptions is active.
*
* @return bool
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Remove all the docblocks in this file.

Reason: the return value is documented in code. And the function names are excellent, they do not need any explanation

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, this is nothing more than noise. Removed now.

* @package WooCommerce\PayPalCommerce\Compat\PluginDetector
*/

declare(strict_types=1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: apply code style rules to this file

Reason: The project style expects spaces around operators and parentheses

Sample:

declare( strict_types = 1 );

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, thanks

Comment on lines +16 to +18
/**
* Class ProductCustomizationDetector
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Remove this docblock

😉

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +21 to +23
/**
* @var PluginDetectorInterface
*/

@stracker-phil stracker-phil Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Remove the docblock, and the others that just re-state what's visible in the code

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed completely :)

Comment on lines +31 to +36
/**
* Cached result of $plugin_detector->scan(), since plugin activation
* cannot change within a request but scan() may be called once per product.
*
* @var array<string, bool>|null
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: This @var annotation is good, it this docblock should stay

(just want to point out that this is different/useful in contrast to the comments above)

Comment on lines +22 to +29
'woocommerce-subscriptions' => $this->is_woocommerce_subscriptions_active(),
'woocommerce-gift-cards' => $this->is_woocommerce_gift_cards_active(),
'woocommerce-product-bundles' => $this->is_woocommerce_product_bundles_active(),
'woocommerce-product-addons' => $this->is_woocommerce_product_addons_active(),
'woocommerce-min-max-quantities' => $this->is_woocommerce_min_max_quantities_active(),
'woocommerce-composite-products' => $this->is_woocommerce_composite_products_active(),
'woocommerce-shipping-per-product' => $this->is_woocommerce_shipping_per_product_active(),
'woocommerce-deposits' => $this->is_woocommerce_deposits_active(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idea: turn these strings into public consts

Reasons:

  • Allows auto-completion in the IDE
  • Avoids typos
  • Gives us a single place with the full detection-catalog

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks! Done.


$addons = \WC_Product_Addons_Helper::get_product_addons( $product->get_id(), false, false, false );

return array() !== $addons;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Rewrite as count( $addons ) > 0 or ! empty( $addons )

Reason: makes the decision a bit easier to understand

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +78 to +86
$this->logger->shouldReceive( 'warning' )
->once()
->with(
Mockery::on(
static function ( string $message ) use ( $plugin ): bool {
return false !== strpos( $message, $plugin ) && false !== strpos( $message, 'does not exist' );
}
)
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Remove this logger expectation

Reason: Same as above, It's not helping with plugin stability and does not prevent a bug. If devs need additional or different logs, it should not require unit test changes.

The ->assertFalse() check below is exactly what this test should verify

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explained above why I'd rather keep this. But I'm open to a discussion if you really believe it shouldn't be here.

/**
* Class RuntimeException
*/
class RuntimeException extends \RuntimeException {

@stracker-phil stracker-phil Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change: Find a unique name for this

Reason: This class shadows the native RuntimeException which can (and likely will) cause confusion. In the worst case, someone might think "we do not need this class" and delete it, breaking the ProductCustomizationDetector try-catch

(Claude suggests PluginApiChangedException or DetectionMethodNotFoundException)

@strangerkir strangerkir Jul 22, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, renamed.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can be previewed and tested using a WordPress Playground instance.
WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

🔗 Test this pull request with WordPress Playground

What's included:

  • ✅ WordPress (latest)
  • ✅ WooCommerce (latest)
  • ✅ PayPal Payments plugin v4.1.1-pr4552-30003370841-gdc0467a (built from this PR)

Login credentials:

  • Username: admin
  • Password: password

Plugin Details:

  • Version: 4.1.1-pr4552-30003370841-gdc0467a
  • Commit: 98123db
  • Artifact: woocommerce-paypal-payments-4.1.1-pr4552-30003370841-gdc0467a

💡 The demo environment resets each time you refresh. Perfect for testing!

🔄 This link updates automatically with each new commit to the PR.

⚠️ This URL is valid for 30 days from when this comment was last updated.


🤖 Auto-generated for commit 98123db • Last updated: 2026-07-23T11:34:00.549Z

* @param PluginDetectorInterface $plugin_detector The plugin presence detector.
* @param LoggerInterface $logger The logger.
* @param PluginDetector $plugin_detector The plugin presence detector.
* @param LoggerInterface $logger The logger.

@Narek13 Narek13 Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also suggest to remove these comments as they do not convey any info

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, they are useless. Removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants