Edit File by line
/home/zeestwma/richards.../wp-conte.../plugins/woocomme.../src/Internal/Features
File: FeaturesController.php
[500] Fix | Delete
/*
[501] Fix | Delete
* This is not truly a legacy feature (it is not a feature that pre-dates the FeaturesController),
[502] Fix | Delete
* but we wish to handle compatibility checking in a similar fashion to legacy features. The
[503] Fix | Delete
* rational for setting legacy to true is therefore similar to that of the 'order_attribution'
[504] Fix | Delete
* feature.
[505] Fix | Delete
*
[506] Fix | Delete
* @see https://github.com/woocommerce/woocommerce/pull/39701#discussion_r1376976959
[507] Fix | Delete
*/
[508] Fix | Delete
'skip_compatibility_checks' => true,
[509] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[510] Fix | Delete
'is_experimental' => true,
[511] Fix | Delete
),
[512] Fix | Delete
'fulfillments' => array(
[513] Fix | Delete
'name' => __( 'Order Fulfillments', 'woocommerce' ),
[514] Fix | Delete
'description' => __(
[515] Fix | Delete
'Enable the Order Fulfillments feature to manage order fulfillment and shipping.',
[516] Fix | Delete
'woocommerce'
[517] Fix | Delete
),
[518] Fix | Delete
'enabled_by_default' => false,
[519] Fix | Delete
'disable_ui' => true,
[520] Fix | Delete
'is_experimental' => false,
[521] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[522] Fix | Delete
),
[523] Fix | Delete
'mcp_integration' => array(
[524] Fix | Delete
'name' => __( 'WooCommerce MCP', 'woocommerce' ),
[525] Fix | Delete
'description' => $this->get_mcp_integration_description(),
[526] Fix | Delete
'enabled_by_default' => false,
[527] Fix | Delete
'disable_ui' => false,
[528] Fix | Delete
'is_experimental' => true,
[529] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[530] Fix | Delete
'is_legacy' => false,
[531] Fix | Delete
),
[532] Fix | Delete
'destroy-empty-sessions' => array(
[533] Fix | Delete
'name' => __( 'Clear Customer Sessions When Empty', 'woocommerce' ),
[534] Fix | Delete
'description' => __(
[535] Fix | Delete
'[Performance] Removes session cookies for non-logged in customers when session data is empty, improving page caching performance. May cause compatibility issues with extensions that depend on the session cookie without using session data.',
[536] Fix | Delete
'woocommerce'
[537] Fix | Delete
),
[538] Fix | Delete
'enabled_by_default' => false,
[539] Fix | Delete
'is_experimental' => true,
[540] Fix | Delete
'disable_ui' => false,
[541] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[542] Fix | Delete
),
[543] Fix | Delete
'agentic_checkout' => array(
[544] Fix | Delete
'name' => __( 'Agentic Checkout API', 'woocommerce' ),
[545] Fix | Delete
'description' => __(
[546] Fix | Delete
'Enable the Agentic Checkout API for AI-powered checkout experiences (e.g., ChatGPT). This adds REST API endpoints that allow AI agents to create and manage checkout sessions.',
[547] Fix | Delete
'woocommerce'
[548] Fix | Delete
),
[549] Fix | Delete
'enabled_by_default' => false,
[550] Fix | Delete
'is_experimental' => true,
[551] Fix | Delete
'disable_ui' => true,
[552] Fix | Delete
'skip_compatibility_checks' => true,
[553] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[554] Fix | Delete
),
[555] Fix | Delete
PushNotifications::FEATURE_NAME => array(
[556] Fix | Delete
'name' => __( 'Push Notifications', 'woocommerce' ),
[557] Fix | Delete
'description' => __(
[558] Fix | Delete
'Enable push notifications for the WooCommerce mobile apps to receive order notifications and store updates.',
[559] Fix | Delete
'woocommerce'
[560] Fix | Delete
),
[561] Fix | Delete
'enabled_by_default' => false,
[562] Fix | Delete
'is_experimental' => true,
[563] Fix | Delete
'disable_ui' => true,
[564] Fix | Delete
'skip_compatibility_checks' => false,
[565] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[566] Fix | Delete
),
[567] Fix | Delete
'rest_api_caching' => array(
[568] Fix | Delete
'name' => __( 'REST API Caching', 'woocommerce' ),
[569] Fix | Delete
'description' => sprintf(
[570] Fix | Delete
/* translators: %1$s and %2$s are opening and closing <a> tags */
[571] Fix | Delete
__( 'Enable backend caching and cache control headers for REST API responses via the <code>RestApiCache</code> trait. ⚙️ %1$sConfiguration%2$s', 'woocommerce' ),
[572] Fix | Delete
'<a href="' . admin_url( 'admin.php?page=wc-settings&tab=advanced&section=rest_api_caching' ) . '">',
[573] Fix | Delete
'</a>'
[574] Fix | Delete
),
[575] Fix | Delete
'enabled_by_default' => false,
[576] Fix | Delete
'is_experimental' => true,
[577] Fix | Delete
'disable_ui' => false,
[578] Fix | Delete
'skip_compatibility_checks' => true,
[579] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[580] Fix | Delete
),
[581] Fix | Delete
ProductCacheController::FEATURE_NAME => array(
[582] Fix | Delete
'name' => __( 'Cache Product Objects', 'woocommerce' ),
[583] Fix | Delete
'description' => __(
[584] Fix | Delete
'[Performance] Speeds up your store by caching product objects during each request, preventing duplicate product loads. Can improve page load times on product-heavy pages.',
[585] Fix | Delete
'woocommerce'
[586] Fix | Delete
),
[587] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::INCOMPATIBLE,
[588] Fix | Delete
'enabled_by_default' => false,
[589] Fix | Delete
'is_experimental' => true,
[590] Fix | Delete
'disable_ui' => false,
[591] Fix | Delete
),
[592] Fix | Delete
'fraud_protection' => array(
[593] Fix | Delete
'name' => __( 'Fraud protection', 'woocommerce' ),
[594] Fix | Delete
'description' => __(
[595] Fix | Delete
'Enable fraud protection features for your store.',
[596] Fix | Delete
'woocommerce'
[597] Fix | Delete
),
[598] Fix | Delete
'enabled_by_default' => false,
[599] Fix | Delete
'disable_ui' => true,
[600] Fix | Delete
'is_experimental' => true,
[601] Fix | Delete
'skip_compatibility_checks' => true,
[602] Fix | Delete
'default_plugin_compatibility' => FeaturePluginCompatibility::COMPATIBLE,
[603] Fix | Delete
),
[604] Fix | Delete
);
[605] Fix | Delete
[606] Fix | Delete
if ( ! $tracking_enabled ) {
[607] Fix | Delete
// Uncheck the remote logging feature when usage tracking is disabled.
[608] Fix | Delete
$legacy_features['remote_logging']['setting']['value'] = 'no';
[609] Fix | Delete
}
[610] Fix | Delete
[611] Fix | Delete
foreach ( $legacy_features as $slug => $definition ) {
[612] Fix | Delete
$this->add_feature_definition( $slug, $definition['name'], $definition );
[613] Fix | Delete
}
[614] Fix | Delete
[615] Fix | Delete
$this->init_compatibility_info_by_feature();
[616] Fix | Delete
}
[617] Fix | Delete
[618] Fix | Delete
/**
[619] Fix | Delete
* Initialize the compatibility_info_by_feature property after all the features have been added.
[620] Fix | Delete
*/
[621] Fix | Delete
private function init_compatibility_info_by_feature() {
[622] Fix | Delete
foreach ( array_keys( $this->features ) as $feature_id ) {
[623] Fix | Delete
if ( ! isset( $this->compatibility_info_by_feature[ $feature_id ] ) ) {
[624] Fix | Delete
$this->compatibility_info_by_feature[ $feature_id ] = array(
[625] Fix | Delete
FeaturePluginCompatibility::COMPATIBLE => array(),
[626] Fix | Delete
FeaturePluginCompatibility::INCOMPATIBLE => array(),
[627] Fix | Delete
);
[628] Fix | Delete
}
[629] Fix | Delete
}
[630] Fix | Delete
}
[631] Fix | Delete
[632] Fix | Delete
/**
[633] Fix | Delete
* Generate the description for the MCP integration feature.
[634] Fix | Delete
*
[635] Fix | Delete
* @return string The feature description with conditional permalink warning and documentation link.
[636] Fix | Delete
*/
[637] Fix | Delete
private function get_mcp_integration_description() {
[638] Fix | Delete
$base_description = __( 'Enable WooCommerce MCP (Model Context Protocol) for AI-powered store operations. AI-generated results and actions can be unpredictable - please review before executing in your store.', 'woocommerce' );
[639] Fix | Delete
[640] Fix | Delete
// Check permalink structure requirement.
[641] Fix | Delete
$permalink_structure = get_option( 'permalink_structure' );
[642] Fix | Delete
if ( empty( $permalink_structure ) ) {
[643] Fix | Delete
$permalinks_url = admin_url( 'options-permalink.php' );
[644] Fix | Delete
$permalink_warning = sprintf(
[645] Fix | Delete
'<br><br><strong>%s:</strong> %s <a href="%s">%s</a>',
[646] Fix | Delete
__( 'Configuration Required', 'woocommerce' ),
[647] Fix | Delete
__( 'WordPress permalinks must be set to anything other than "Plain" for MCP to work.', 'woocommerce' ),
[648] Fix | Delete
$permalinks_url,
[649] Fix | Delete
__( 'Configure Permalinks', 'woocommerce' )
[650] Fix | Delete
);
[651] Fix | Delete
// Add documentation link to permalink warning.
[652] Fix | Delete
$documentation_link = sprintf(
[653] Fix | Delete
' <a href="%s" target="_blank">%s</a>',
[654] Fix | Delete
'https://github.com/woocommerce/woocommerce/blob/trunk/docs/features/mcp/README.md',
[655] Fix | Delete
__( 'Learn more', 'woocommerce' )
[656] Fix | Delete
);
[657] Fix | Delete
return $base_description . $permalink_warning . $documentation_link;
[658] Fix | Delete
}
[659] Fix | Delete
[660] Fix | Delete
// Add documentation link.
[661] Fix | Delete
$documentation_link = sprintf(
[662] Fix | Delete
' <a href="%s" target="_blank">%s</a>',
[663] Fix | Delete
'https://github.com/woocommerce/woocommerce/blob/trunk/docs/features/mcp/README.md',
[664] Fix | Delete
__( 'Learn more', 'woocommerce' )
[665] Fix | Delete
);
[666] Fix | Delete
[667] Fix | Delete
return $base_description . $documentation_link;
[668] Fix | Delete
}
[669] Fix | Delete
[670] Fix | Delete
/**
[671] Fix | Delete
* Function to trigger the (now deprecated) 'woocommerce_register_feature_definitions' hook.
[672] Fix | Delete
*
[673] Fix | Delete
* This function must execute immediately before the 'before_woocommerce_init'
[674] Fix | Delete
* action is fired, so that feature compatibility declarations happening
[675] Fix | Delete
* in that action find all the features properly declared already.
[676] Fix | Delete
*
[677] Fix | Delete
* @internal
[678] Fix | Delete
*/
[679] Fix | Delete
public function register_additional_features() {
[680] Fix | Delete
if ( $this->registered_additional_features_via_action ) {
[681] Fix | Delete
return;
[682] Fix | Delete
}
[683] Fix | Delete
[684] Fix | Delete
if ( empty( $this->features ) ) {
[685] Fix | Delete
$this->init_feature_definitions();
[686] Fix | Delete
}
[687] Fix | Delete
[688] Fix | Delete
/**
[689] Fix | Delete
* The action for registering features.
[690] Fix | Delete
*
[691] Fix | Delete
* @since 8.3.0
[692] Fix | Delete
*
[693] Fix | Delete
* @param FeaturesController $features_controller The instance of FeaturesController.
[694] Fix | Delete
*
[695] Fix | Delete
* @deprecated 9.9.0 Features should be defined directly in get_feature_definitions.
[696] Fix | Delete
*/
[697] Fix | Delete
do_action( 'woocommerce_register_feature_definitions', $this );
[698] Fix | Delete
[699] Fix | Delete
$this->init_compatibility_info_by_feature();
[700] Fix | Delete
[701] Fix | Delete
$this->registered_additional_features_via_action = true;
[702] Fix | Delete
}
[703] Fix | Delete
[704] Fix | Delete
/**
[705] Fix | Delete
* Initialize the class instance.
[706] Fix | Delete
*
[707] Fix | Delete
* @internal
[708] Fix | Delete
*
[709] Fix | Delete
* @param LegacyProxy $proxy The instance of LegacyProxy to use.
[710] Fix | Delete
* @param PluginUtil $plugin_util The instance of PluginUtil to use.
[711] Fix | Delete
*/
[712] Fix | Delete
final public function init( LegacyProxy $proxy, PluginUtil $plugin_util ) {
[713] Fix | Delete
$this->proxy = $proxy;
[714] Fix | Delete
$this->plugin_util = $plugin_util;
[715] Fix | Delete
[716] Fix | Delete
$this->plugins_excluded_from_compatibility_ui = $plugin_util->get_plugins_excluded_from_compatibility_ui();
[717] Fix | Delete
}
[718] Fix | Delete
[719] Fix | Delete
/**
[720] Fix | Delete
* Get all the existing WooCommerce features.
[721] Fix | Delete
*
[722] Fix | Delete
* Returns an associative array where keys are unique feature ids
[723] Fix | Delete
* and values are arrays with these keys:
[724] Fix | Delete
*
[725] Fix | Delete
* - name (string)
[726] Fix | Delete
* - description (string)
[727] Fix | Delete
* - is_experimental (bool)
[728] Fix | Delete
* - is_enabled (bool) (only if $include_enabled_info is passed as true)
[729] Fix | Delete
*
[730] Fix | Delete
* @param bool $include_experimental Include also experimental/work in progress features in the list.
[731] Fix | Delete
* @param bool $include_enabled_info True to include the 'is_enabled' field in the returned features info.
[732] Fix | Delete
* @returns array An array of information about existing features.
[733] Fix | Delete
*/
[734] Fix | Delete
public function get_features( bool $include_experimental = false, bool $include_enabled_info = false ): array {
[735] Fix | Delete
$features = $this->get_feature_definitions();
[736] Fix | Delete
[737] Fix | Delete
if ( ! $include_experimental ) {
[738] Fix | Delete
$features = array_filter(
[739] Fix | Delete
$features,
[740] Fix | Delete
function ( $feature ) {
[741] Fix | Delete
return ! $feature['is_experimental'];
[742] Fix | Delete
}
[743] Fix | Delete
);
[744] Fix | Delete
}
[745] Fix | Delete
[746] Fix | Delete
if ( $include_enabled_info ) {
[747] Fix | Delete
foreach ( array_keys( $features ) as $feature_id ) {
[748] Fix | Delete
$is_enabled = false;
[749] Fix | Delete
// For deprecated features, use the deprecated_value directly without triggering the deprecation notice.
[750] Fix | Delete
// The deprecation notice should only fire for external code checking feature status, not for internal listing.
[751] Fix | Delete
if ( ! empty( $features[ $feature_id ]['deprecated_since'] ) ) {
[752] Fix | Delete
$is_enabled = (bool) ( $features[ $feature_id ]['deprecated_value'] ?? false );
[753] Fix | Delete
} else {
[754] Fix | Delete
$is_enabled = $this->feature_is_enabled( $feature_id );
[755] Fix | Delete
}
[756] Fix | Delete
$features[ $feature_id ]['is_enabled'] = $is_enabled;
[757] Fix | Delete
}
[758] Fix | Delete
}
[759] Fix | Delete
[760] Fix | Delete
// We're deprecating the product block editor feature in favor of a v3 coming out.
[761] Fix | Delete
// We want to hide this setting in the UI for users that don't have it enabled.
[762] Fix | Delete
// If users have it enabled, we won't hide it until they explicitly disable it.
[763] Fix | Delete
if ( isset( $features['product_block_editor'] )
[764] Fix | Delete
&& ! $this->feature_is_enabled( 'product_block_editor' ) ) {
[765] Fix | Delete
$features['product_block_editor']['disable_ui'] = true;
[766] Fix | Delete
}
[767] Fix | Delete
[768] Fix | Delete
return $features;
[769] Fix | Delete
}
[770] Fix | Delete
[771] Fix | Delete
/**
[772] Fix | Delete
* Get the default plugin compatibility for a given feature.
[773] Fix | Delete
*
[774] Fix | Delete
* @param string $feature_id Feature id to check.
[775] Fix | Delete
* @return string Either 'compatible' or 'incompatible'.
[776] Fix | Delete
* @throws \InvalidArgumentException If the feature doesn't exist.
[777] Fix | Delete
*/
[778] Fix | Delete
public function get_default_plugin_compatibility( string $feature_id ): string {
[779] Fix | Delete
$feature = $this->get_feature_definition( $feature_id );
[780] Fix | Delete
if ( null === $feature ) {
[781] Fix | Delete
throw new \InvalidArgumentException( esc_html( "The WooCommerce feature '$feature_id' doesn't exist" ) );
[782] Fix | Delete
}
[783] Fix | Delete
[784] Fix | Delete
$default_plugin_compatibility = $feature['default_plugin_compatibility'] ?? FeaturePluginCompatibility::COMPATIBLE;
[785] Fix | Delete
[786] Fix | Delete
// Filter below is only fired for backwards compatibility with (now removed) get_plugins_are_incompatible_by_default().
[787] Fix | Delete
/**
[788] Fix | Delete
* Filter to determine if plugins that don't declare compatibility nor incompatibility with a given feature
[789] Fix | Delete
* are to be considered incompatible with that feature.
[790] Fix | Delete
*
[791] Fix | Delete
* @param bool $incompatible_by_default Default value, true if plugins are to be considered incompatible by default with the feature.
[792] Fix | Delete
* @param string $feature_id The feature to check.
[793] Fix | Delete
*
[794] Fix | Delete
* @since 9.2.0
[795] Fix | Delete
*/
[796] Fix | Delete
$incompatible_by_default = (bool) apply_filters( 'woocommerce_plugins_are_incompatible_with_feature_by_default', FeaturePluginCompatibility::INCOMPATIBLE === $default_plugin_compatibility, $feature_id );
[797] Fix | Delete
[798] Fix | Delete
return $incompatible_by_default ? FeaturePluginCompatibility::INCOMPATIBLE : FeaturePluginCompatibility::COMPATIBLE;
[799] Fix | Delete
}
[800] Fix | Delete
[801] Fix | Delete
/**
[802] Fix | Delete
* Get the definition array for a specific feature.
[803] Fix | Delete
*
[804] Fix | Delete
* @param string $feature_id Unique feature id.
[805] Fix | Delete
* @return array|null The feature definition array, or null if the feature doesn't exist.
[806] Fix | Delete
*
[807] Fix | Delete
* @since 10.5.0
[808] Fix | Delete
*/
[809] Fix | Delete
public function get_feature_definition( string $feature_id ): ?array {
[810] Fix | Delete
return $this->get_feature_definitions()[ $feature_id ] ?? null;
[811] Fix | Delete
}
[812] Fix | Delete
[813] Fix | Delete
/**
[814] Fix | Delete
* Check if a given feature is currently enabled.
[815] Fix | Delete
*
[816] Fix | Delete
* Note: This method does not log deprecation notices for deprecated features.
[817] Fix | Delete
* Deprecation logging is handled by FeaturesUtil::feature_is_enabled() which is the public API.
[818] Fix | Delete
*
[819] Fix | Delete
* @param string $feature_id Unique feature id.
[820] Fix | Delete
* @return bool True if the feature is enabled, false if not or if the feature doesn't exist.
[821] Fix | Delete
*/
[822] Fix | Delete
public function feature_is_enabled( string $feature_id ): bool {
[823] Fix | Delete
$feature = $this->get_feature_definition( $feature_id );
[824] Fix | Delete
[825] Fix | Delete
if ( null === $feature ) {
[826] Fix | Delete
return false;
[827] Fix | Delete
}
[828] Fix | Delete
[829] Fix | Delete
// Handle deprecated features - return the backwards-compatible value.
[830] Fix | Delete
if ( ! empty( $feature['deprecated_since'] ) ) {
[831] Fix | Delete
return (bool) ( $feature['deprecated_value'] ?? false );
[832] Fix | Delete
}
[833] Fix | Delete
[834] Fix | Delete
if ( $this->is_preview_email_improvements_enabled( $feature_id ) ) {
[835] Fix | Delete
return true;
[836] Fix | Delete
}
[837] Fix | Delete
[838] Fix | Delete
$default_value = $this->feature_is_enabled_by_default( $feature_id ) ? 'yes' : 'no';
[839] Fix | Delete
$value = 'yes' === get_option( $this->feature_enable_option_name( $feature_id ), $default_value );
[840] Fix | Delete
return $value;
[841] Fix | Delete
}
[842] Fix | Delete
[843] Fix | Delete
/**
[844] Fix | Delete
* Check if a given feature is enabled by default.
[845] Fix | Delete
*
[846] Fix | Delete
* @param string $feature_id Unique feature id.
[847] Fix | Delete
* @return boolean TRUE if the feature is enabled by default, FALSE otherwise.
[848] Fix | Delete
*/
[849] Fix | Delete
private function feature_is_enabled_by_default( string $feature_id ): bool {
[850] Fix | Delete
$features = $this->get_feature_definitions();
[851] Fix | Delete
[852] Fix | Delete
return ! empty( $features[ $feature_id ]['enabled_by_default'] );
[853] Fix | Delete
}
[854] Fix | Delete
[855] Fix | Delete
/**
[856] Fix | Delete
* Change the enabled/disabled status of a feature.
[857] Fix | Delete
*
[858] Fix | Delete
* @param string $feature_id Unique feature id.
[859] Fix | Delete
* @param bool $enable True to enable the feature, false to disable it.
[860] Fix | Delete
* @return bool True on success, false if feature doesn't exist or the new value is the same as the old value.
[861] Fix | Delete
*/
[862] Fix | Delete
public function change_feature_enable( string $feature_id, bool $enable ): bool {
[863] Fix | Delete
if ( ! $this->feature_exists( $feature_id ) ) {
[864] Fix | Delete
return false;
[865] Fix | Delete
}
[866] Fix | Delete
[867] Fix | Delete
return update_option( $this->feature_enable_option_name( $feature_id ), $enable ? 'yes' : 'no', 'on' );
[868] Fix | Delete
}
[869] Fix | Delete
[870] Fix | Delete
/**
[871] Fix | Delete
* Declare (in)compatibility with a given feature for a given plugin.
[872] Fix | Delete
*
[873] Fix | Delete
* This method MUST be executed from inside a handler for the 'before_woocommerce_init' hook.
[874] Fix | Delete
*
[875] Fix | Delete
* The plugin name is expected to be in the form 'directory/file.php' and be one of the keys
[876] Fix | Delete
* of the array returned by 'get_plugins', but this won't be checked. Plugins are expected to use
[877] Fix | Delete
* FeaturesUtil::declare_compatibility instead, passing the full plugin file path instead of the plugin name.
[878] Fix | Delete
*
[879] Fix | Delete
* @param string $feature_id Unique feature id.
[880] Fix | Delete
* @param string $plugin_file Plugin file path, either full or in the form 'directory/file.php'.
[881] Fix | Delete
* @param bool $positive_compatibility True if the plugin declares being compatible with the feature, false if it declares being incompatible.
[882] Fix | Delete
* @return bool True on success, false on error (feature doesn't exist or not inside the required hook).
[883] Fix | Delete
* @throws \Exception A plugin attempted to declare itself as compatible and incompatible with a given feature at the same time.
[884] Fix | Delete
*/
[885] Fix | Delete
public function declare_compatibility( string $feature_id, string $plugin_file, bool $positive_compatibility = true ): bool {
[886] Fix | Delete
if ( ! $this->proxy->call_function( 'doing_action', 'before_woocommerce_init' ) ) {
[887] Fix | Delete
$class_and_method = ( new \ReflectionClass( $this ) )->getShortName() . '::' . __FUNCTION__;
[888] Fix | Delete
/* translators: 1: class::method 2: before_woocommerce_init */
[889] Fix | Delete
$this->proxy->call_function( 'wc_doing_it_wrong', $class_and_method, sprintf( __( '%1$s should be called inside the %2$s action.', 'woocommerce' ), $class_and_method, 'before_woocommerce_init' ), '7.0' );
[890] Fix | Delete
return false;
[891] Fix | Delete
}
[892] Fix | Delete
if ( ! $this->feature_exists( $feature_id ) ) {
[893] Fix | Delete
return false;
[894] Fix | Delete
}
[895] Fix | Delete
[896] Fix | Delete
if ( $this->lazy ) {
[897] Fix | Delete
// Lazy mode: Queue to be normalized later.
[898] Fix | Delete
$this->pending_declarations[] = array( $feature_id, $plugin_file, $positive_compatibility );
[899] Fix | Delete
return true;
[900] Fix | Delete
}
[901] Fix | Delete
[902] Fix | Delete
// Late call: Normalize and register immediately.
[903] Fix | Delete
return $this->register_compatibility_internal( $feature_id, $plugin_file, $positive_compatibility );
[904] Fix | Delete
}
[905] Fix | Delete
[906] Fix | Delete
/**
[907] Fix | Delete
* Registers compatibility information internally for a given feature and plugin file.
[908] Fix | Delete
*
[909] Fix | Delete
* This method normalizes the plugin file path to a plugin ID, handles validation and logging for invalid plugins,
[910] Fix | Delete
* and registers the compatibility data if valid.
[911] Fix | Delete
* It updates the internal compatibility arrays, checks for conflicts (e.g., a plugin declaring both
[912] Fix | Delete
* compatible and incompatible with the same feature), and throws an exception if a conflict is detected.
[913] Fix | Delete
* Duplicate declarations (same compatibility type) are ignored.
[914] Fix | Delete
*
[915] Fix | Delete
* This is an internal helper method and should not be called directly.
[916] Fix | Delete
*
[917] Fix | Delete
* @internal For usage by WooCommerce core only. Backwards compatibility not guaranteed.
[918] Fix | Delete
* @since 10.1.0
[919] Fix | Delete
*
[920] Fix | Delete
* @param string $feature_id Unique feature ID.
[921] Fix | Delete
* @param string $plugin_file Raw plugin file path (full or 'directory/file.php').
[922] Fix | Delete
* @param bool $positive_compatibility True if declaring compatibility, false if declaring incompatibility.
[923] Fix | Delete
* @return bool True on successful registration, false if the feature does not exist.
[924] Fix | Delete
* @throws \Exception If the plugin attempts to declare both compatibility and incompatibility for the same feature.
[925] Fix | Delete
*/
[926] Fix | Delete
private function register_compatibility_internal( string $feature_id, string $plugin_file, bool $positive_compatibility ): bool {
[927] Fix | Delete
if ( ! $this->feature_exists( $feature_id ) ) {
[928] Fix | Delete
return false;
[929] Fix | Delete
}
[930] Fix | Delete
[931] Fix | Delete
// Normalize and validate plugin file.
[932] Fix | Delete
$plugin_id = $this->plugin_util->get_wp_plugin_id( $plugin_file );
[933] Fix | Delete
if ( ! $plugin_id ) {
[934] Fix | Delete
$logger = $this->proxy->call_function( 'wc_get_logger' );
[935] Fix | Delete
$logger->error( "FeaturesController: Invalid plugin file '{$plugin_file}' for feature '{$feature_id}'." );
[936] Fix | Delete
return false;
[937] Fix | Delete
}
[938] Fix | Delete
[939] Fix | Delete
// Register compatibility by plugin.
[940] Fix | Delete
ArrayUtil::ensure_key_is_array( $this->compatibility_info_by_plugin, $plugin_id );
[941] Fix | Delete
[942] Fix | Delete
$key = $positive_compatibility ? FeaturePluginCompatibility::COMPATIBLE : FeaturePluginCompatibility::INCOMPATIBLE;
[943] Fix | Delete
$opposite_key = $positive_compatibility ? FeaturePluginCompatibility::INCOMPATIBLE : FeaturePluginCompatibility::COMPATIBLE;
[944] Fix | Delete
ArrayUtil::ensure_key_is_array( $this->compatibility_info_by_plugin[ $plugin_id ], $key );
[945] Fix | Delete
ArrayUtil::ensure_key_is_array( $this->compatibility_info_by_plugin[ $plugin_id ], $opposite_key );
[946] Fix | Delete
[947] Fix | Delete
if ( in_array( $feature_id, $this->compatibility_info_by_plugin[ $plugin_id ][ $opposite_key ], true ) ) {
[948] Fix | Delete
throw new \Exception( esc_html( "Plugin $plugin_id is trying to declare itself as $key with the '$feature_id' feature, but it already declared itself as $opposite_key" ) );
[949] Fix | Delete
}
[950] Fix | Delete
[951] Fix | Delete
if ( ! in_array( $feature_id, $this->compatibility_info_by_plugin[ $plugin_id ][ $key ], true ) ) {
[952] Fix | Delete
$this->compatibility_info_by_plugin[ $plugin_id ][ $key ][] = $feature_id;
[953] Fix | Delete
}
[954] Fix | Delete
[955] Fix | Delete
// Register compatibility by feature.
[956] Fix | Delete
$key = $positive_compatibility ? FeaturePluginCompatibility::COMPATIBLE : FeaturePluginCompatibility::INCOMPATIBLE;
[957] Fix | Delete
[958] Fix | Delete
if ( ! in_array( $plugin_id, $this->compatibility_info_by_feature[ $feature_id ][ $key ], true ) ) {
[959] Fix | Delete
$this->compatibility_info_by_feature[ $feature_id ][ $key ][] = $plugin_id;
[960] Fix | Delete
}
[961] Fix | Delete
[962] Fix | Delete
return true;
[963] Fix | Delete
}
[964] Fix | Delete
[965] Fix | Delete
/**
[966] Fix | Delete
* Processes any pending compatibility declarations by normalizing plugin file paths
[967] Fix | Delete
* and registering them internally.
[968] Fix | Delete
*
[969] Fix | Delete
* This method is called lazily when compatibility information is queried (via
[970] Fix | Delete
* get_compatible_features_for_plugin() or get_compatible_plugins_for_feature()).
[971] Fix | Delete
* It resolves plugin IDs using PluginUtil and logs errors for unrecognized plugins.
[972] Fix | Delete
* Pending declarations are cleared after processing to avoid redundant work.
[973] Fix | Delete
*
[974] Fix | Delete
* @internal For usage by WooCommerce core only. Backwards compatibility not guaranteed.
[975] Fix | Delete
* @since 10.1.0
[976] Fix | Delete
* @return void
[977] Fix | Delete
*/
[978] Fix | Delete
private function process_pending_declarations(): void {
[979] Fix | Delete
if ( empty( $this->pending_declarations ) ) {
[980] Fix | Delete
return;
[981] Fix | Delete
}
[982] Fix | Delete
[983] Fix | Delete
foreach ( $this->pending_declarations as $declaration ) {
[984] Fix | Delete
list( $feature_id, $plugin_file, $positive_compatibility ) = $declaration;
[985] Fix | Delete
[986] Fix | Delete
// Register internally.
[987] Fix | Delete
$this->register_compatibility_internal( $feature_id, $plugin_file, $positive_compatibility );
[988] Fix | Delete
}
[989] Fix | Delete
[990] Fix | Delete
$this->pending_declarations = array();
[991] Fix | Delete
$this->lazy = false;
[992] Fix | Delete
}
[993] Fix | Delete
[994] Fix | Delete
/**
[995] Fix | Delete
* Check whether a feature exists with a given id.
[996] Fix | Delete
*
[997] Fix | Delete
* @param string $feature_id The feature id to check.
[998] Fix | Delete
* @return bool True if the feature exists.
[999] Fix | Delete
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function