Edit File by line
/home/zeestwma/richards.../wp-conte.../plugins/woocomme.../src/Utilitie...
File: PluginUtil.php
<?php
[0] Fix | Delete
/**
[1] Fix | Delete
* A class of utilities for dealing with plugins.
[2] Fix | Delete
*/
[3] Fix | Delete
[4] Fix | Delete
namespace Automattic\WooCommerce\Utilities;
[5] Fix | Delete
[6] Fix | Delete
use Automattic\WooCommerce\Internal\Features\FeaturesController;
[7] Fix | Delete
use Automattic\WooCommerce\Internal\Utilities\PluginInstaller;
[8] Fix | Delete
use Automattic\WooCommerce\Proxies\LegacyProxy;
[9] Fix | Delete
[10] Fix | Delete
/**
[11] Fix | Delete
* A class of utilities for dealing with plugins.
[12] Fix | Delete
*/
[13] Fix | Delete
class PluginUtil {
[14] Fix | Delete
[15] Fix | Delete
/**
[16] Fix | Delete
* The LegacyProxy instance to use.
[17] Fix | Delete
*
[18] Fix | Delete
* @var LegacyProxy
[19] Fix | Delete
*/
[20] Fix | Delete
private $proxy;
[21] Fix | Delete
[22] Fix | Delete
/**
[23] Fix | Delete
* The cached list of WooCommerce aware plugin ids.
[24] Fix | Delete
*
[25] Fix | Delete
* @var null|array
[26] Fix | Delete
*/
[27] Fix | Delete
private $woocommerce_aware_plugins = null;
[28] Fix | Delete
[29] Fix | Delete
/**
[30] Fix | Delete
* The cached list of enabled WooCommerce aware plugin ids.
[31] Fix | Delete
*
[32] Fix | Delete
* @var null|array
[33] Fix | Delete
*/
[34] Fix | Delete
private $woocommerce_aware_active_plugins = null;
[35] Fix | Delete
[36] Fix | Delete
/**
[37] Fix | Delete
* List of plugins excluded from feature compatibility warnings in UI.
[38] Fix | Delete
*
[39] Fix | Delete
* @var string[]
[40] Fix | Delete
*/
[41] Fix | Delete
private $plugins_excluded_from_compatibility_ui;
[42] Fix | Delete
[43] Fix | Delete
/**
[44] Fix | Delete
* Creates a new instance of the class.
[45] Fix | Delete
*/
[46] Fix | Delete
public function __construct() {
[47] Fix | Delete
add_action( 'activated_plugin', array( $this, 'handle_plugin_de_activation' ), 10, 0 );
[48] Fix | Delete
add_action( 'deactivated_plugin', array( $this, 'handle_plugin_de_activation' ), 10, 0 );
[49] Fix | Delete
[50] Fix | Delete
$this->plugins_excluded_from_compatibility_ui = array( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' );
[51] Fix | Delete
}
[52] Fix | Delete
[53] Fix | Delete
/**
[54] Fix | Delete
* Initialize the class instance.
[55] Fix | Delete
*
[56] Fix | Delete
* @internal
[57] Fix | Delete
*
[58] Fix | Delete
* @param LegacyProxy $proxy The instance of LegacyProxy to use.
[59] Fix | Delete
*/
[60] Fix | Delete
final public function init( LegacyProxy $proxy ) {
[61] Fix | Delete
$this->proxy = $proxy;
[62] Fix | Delete
require_once ABSPATH . WPINC . '/plugin.php';
[63] Fix | Delete
}
[64] Fix | Delete
[65] Fix | Delete
/**
[66] Fix | Delete
* Wrapper for WP's private `wp_get_active_and_valid_plugins` and `wp_get_active_network_plugins` functions.
[67] Fix | Delete
*
[68] Fix | Delete
* This combines the results of the two functions to get a list of all plugins that are active within a site.
[69] Fix | Delete
* It's more useful than just retrieving the option values because it also validates that the plugin files exist.
[70] Fix | Delete
* This wrapper is also a hedge against backward-incompatible changes since both of the WP methods are marked as
[71] Fix | Delete
* being "@access private", so if need be we can update our methods here to preserve functionality.
[72] Fix | Delete
*
[73] Fix | Delete
* Note that the doc block for `wp_get_active_and_valid_plugins` says it returns "Array of paths to plugin files
[74] Fix | Delete
* relative to the plugins directory", but it actually returns absolute paths.
[75] Fix | Delete
*
[76] Fix | Delete
* @return string[] Array of plugin basenames (paths relative to the plugin directory).
[77] Fix | Delete
*/
[78] Fix | Delete
public function get_all_active_valid_plugins() {
[79] Fix | Delete
$local = wp_get_active_and_valid_plugins();
[80] Fix | Delete
[81] Fix | Delete
if ( is_multisite() ) {
[82] Fix | Delete
require_once ABSPATH . WPINC . '/ms-load.php';
[83] Fix | Delete
$network = wp_get_active_network_plugins();
[84] Fix | Delete
} else {
[85] Fix | Delete
$network = array();
[86] Fix | Delete
}
[87] Fix | Delete
[88] Fix | Delete
$all = array_merge( $local, $network );
[89] Fix | Delete
$all = array_unique( $all );
[90] Fix | Delete
$all = array_map( 'plugin_basename', $all );
[91] Fix | Delete
sort( $all );
[92] Fix | Delete
[93] Fix | Delete
return $all;
[94] Fix | Delete
}
[95] Fix | Delete
[96] Fix | Delete
/**
[97] Fix | Delete
* Get a list with the names of the WordPress plugins that are WooCommerce aware
[98] Fix | Delete
* (they have a "WC tested up to" header).
[99] Fix | Delete
*
[100] Fix | Delete
* @param bool $active_only True to return only active plugins, false to return all the active plugins.
[101] Fix | Delete
* @return string[] A list of plugin ids (path/file.php).
[102] Fix | Delete
*/
[103] Fix | Delete
public function get_woocommerce_aware_plugins( bool $active_only = false ): array {
[104] Fix | Delete
if ( is_null( $this->woocommerce_aware_plugins ) ) {
[105] Fix | Delete
// In case `get_plugins` was called much earlier in the request (before our headers could be injected), we
[106] Fix | Delete
// invalidate the plugin cache list.
[107] Fix | Delete
wp_cache_delete( 'plugins', 'plugins' );
[108] Fix | Delete
$all_plugins = $this->proxy->call_function( 'get_plugins' );
[109] Fix | Delete
[110] Fix | Delete
$this->woocommerce_aware_plugins =
[111] Fix | Delete
array_keys(
[112] Fix | Delete
array_filter(
[113] Fix | Delete
$all_plugins,
[114] Fix | Delete
array( $this, 'is_woocommerce_aware_plugin' )
[115] Fix | Delete
)
[116] Fix | Delete
);
[117] Fix | Delete
[118] Fix | Delete
$this->woocommerce_aware_active_plugins =
[119] Fix | Delete
array_values(
[120] Fix | Delete
array_filter(
[121] Fix | Delete
$this->woocommerce_aware_plugins,
[122] Fix | Delete
function ( $plugin_name ) {
[123] Fix | Delete
return $this->proxy->call_function( 'is_plugin_active', $plugin_name );
[124] Fix | Delete
}
[125] Fix | Delete
)
[126] Fix | Delete
);
[127] Fix | Delete
}
[128] Fix | Delete
[129] Fix | Delete
return $active_only ? $this->woocommerce_aware_active_plugins : $this->woocommerce_aware_plugins;
[130] Fix | Delete
}
[131] Fix | Delete
[132] Fix | Delete
/**
[133] Fix | Delete
* Get the printable name of a plugin.
[134] Fix | Delete
*
[135] Fix | Delete
* @param string $plugin_id Plugin id (path/file.php).
[136] Fix | Delete
* @return string Printable plugin name, or the plugin id itself if printable name is not available.
[137] Fix | Delete
*/
[138] Fix | Delete
public function get_plugin_name( string $plugin_id ): string {
[139] Fix | Delete
$plugin_data = $this->proxy->call_function( 'get_plugin_data', WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_id );
[140] Fix | Delete
return $plugin_data['Name'] ?? $plugin_id;
[141] Fix | Delete
}
[142] Fix | Delete
[143] Fix | Delete
/**
[144] Fix | Delete
* Check if a plugin is WooCommerce aware.
[145] Fix | Delete
*
[146] Fix | Delete
* @param string|array $plugin_file_or_data Plugin id (path/file.php) or plugin data (as returned by get_plugins).
[147] Fix | Delete
* @return bool True if the plugin exists and is WooCommerce aware.
[148] Fix | Delete
* @throws \Exception The input is neither a string nor an array.
[149] Fix | Delete
*/
[150] Fix | Delete
public function is_woocommerce_aware_plugin( $plugin_file_or_data ): bool {
[151] Fix | Delete
if ( is_string( $plugin_file_or_data ) ) {
[152] Fix | Delete
return in_array( $plugin_file_or_data, $this->get_woocommerce_aware_plugins(), true );
[153] Fix | Delete
} elseif ( is_array( $plugin_file_or_data ) ) {
[154] Fix | Delete
return '' !== ( $plugin_file_or_data['WC tested up to'] ?? '' );
[155] Fix | Delete
} else {
[156] Fix | Delete
throw new \Exception( 'is_woocommerce_aware_plugin requires a plugin name or an array of plugin data as input' );
[157] Fix | Delete
}
[158] Fix | Delete
}
[159] Fix | Delete
[160] Fix | Delete
/**
[161] Fix | Delete
* Match plugin identifier passed as a parameter with the output from `get_plugins()`.
[162] Fix | Delete
*
[163] Fix | Delete
* @param string $plugin_file Plugin identifier, either 'my-plugin/my-plugin.php', or output from __FILE__.
[164] Fix | Delete
*
[165] Fix | Delete
* @return string|false Key from the array returned by `get_plugins` if matched. False if no match.
[166] Fix | Delete
*/
[167] Fix | Delete
public function get_wp_plugin_id( $plugin_file ) {
[168] Fix | Delete
$wp_plugins = array_keys( $this->proxy->call_function( 'get_plugins' ) );
[169] Fix | Delete
[170] Fix | Delete
// Try to match plugin_basename().
[171] Fix | Delete
$plugin_basename = $this->proxy->call_function( 'plugin_basename', $plugin_file );
[172] Fix | Delete
if ( in_array( $plugin_basename, $wp_plugins, true ) ) {
[173] Fix | Delete
return $plugin_basename;
[174] Fix | Delete
}
[175] Fix | Delete
[176] Fix | Delete
// Try to match by the my-file/my-file.php (dir + file name), then by my-file.php (file name only).
[177] Fix | Delete
$plugin_file = str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $plugin_file );
[178] Fix | Delete
$file_name_parts = explode( DIRECTORY_SEPARATOR, $plugin_file );
[179] Fix | Delete
$file_name = array_pop( $file_name_parts );
[180] Fix | Delete
$directory_name = array_pop( $file_name_parts );
[181] Fix | Delete
$full_matches = array();
[182] Fix | Delete
$partial_matches = array();
[183] Fix | Delete
foreach ( $wp_plugins as $wp_plugin ) {
[184] Fix | Delete
if ( false !== strpos( $wp_plugin, $directory_name . DIRECTORY_SEPARATOR . $file_name ) ) {
[185] Fix | Delete
$full_matches[] = $wp_plugin;
[186] Fix | Delete
}
[187] Fix | Delete
[188] Fix | Delete
if ( ! empty( $file_name ) && false !== strpos( $wp_plugin, $file_name ) ) {
[189] Fix | Delete
$partial_matches[] = $wp_plugin;
[190] Fix | Delete
}
[191] Fix | Delete
}
[192] Fix | Delete
[193] Fix | Delete
if ( 1 === count( $full_matches ) ) {
[194] Fix | Delete
return $full_matches[0];
[195] Fix | Delete
}
[196] Fix | Delete
[197] Fix | Delete
if ( 1 === count( $partial_matches ) ) {
[198] Fix | Delete
return $partial_matches[0];
[199] Fix | Delete
}
[200] Fix | Delete
[201] Fix | Delete
return false;
[202] Fix | Delete
}
[203] Fix | Delete
[204] Fix | Delete
/**
[205] Fix | Delete
* Handle plugin activation and deactivation by clearing the WooCommerce aware plugin ids cache.
[206] Fix | Delete
*
[207] Fix | Delete
* @internal For exclusive usage of WooCommerce core, backwards compatibility not guaranteed.
[208] Fix | Delete
*/
[209] Fix | Delete
public function handle_plugin_de_activation(): void {
[210] Fix | Delete
$this->woocommerce_aware_plugins = null;
[211] Fix | Delete
$this->woocommerce_aware_active_plugins = null;
[212] Fix | Delete
}
[213] Fix | Delete
[214] Fix | Delete
/**
[215] Fix | Delete
* Utility method to generate warning string for incompatible features based on active plugins.
[216] Fix | Delete
*
[217] Fix | Delete
* Additionally, this method will manually print a warning message on the HPOS feature if both
[218] Fix | Delete
* the Legacy REST API and HPOS are active.
[219] Fix | Delete
*
[220] Fix | Delete
* @param string $feature_id Feature id.
[221] Fix | Delete
* @param array $plugin_feature_info Array of plugin feature info, as provided by FeaturesController->get_compatible_plugins_for_feature().
[222] Fix | Delete
*
[223] Fix | Delete
* @return string Warning string.
[224] Fix | Delete
*/
[225] Fix | Delete
public function generate_incompatible_plugin_feature_warning( string $feature_id, array $plugin_feature_info ): string {
[226] Fix | Delete
$incompatibles = $this->get_items_considered_incompatible( $feature_id, $plugin_feature_info );
[227] Fix | Delete
$incompatibles = array_filter( $incompatibles, 'is_plugin_active' );
[228] Fix | Delete
$incompatibles = array_values( array_diff( $incompatibles, $this->get_plugins_excluded_from_compatibility_ui() ) );
[229] Fix | Delete
$incompatible_count = count( $incompatibles );
[230] Fix | Delete
[231] Fix | Delete
$feature_warnings = array();
[232] Fix | Delete
if ( 'custom_order_tables' === $feature_id && 'yes' === get_option( 'woocommerce_api_enabled' ) ) {
[233] Fix | Delete
if ( is_plugin_active( 'woocommerce-legacy-rest-api/woocommerce-legacy-rest-api.php' ) ) {
[234] Fix | Delete
$legacy_api_and_hpos_incompatibility_warning_text =
[235] Fix | Delete
sprintf(
[236] Fix | Delete
// translators: %s is a URL.
[237] Fix | Delete
__( '⚠ <b><a target="_blank" href="%s">The Legacy REST API plugin</a> is installed and active on this site.</b> Please be aware that the WooCommerce Legacy REST API is <b>not</b> compatible with HPOS.', 'woocommerce' ),
[238] Fix | Delete
'https://wordpress.org/plugins/woocommerce-legacy-rest-api/'
[239] Fix | Delete
);
[240] Fix | Delete
} else {
[241] Fix | Delete
$legacy_api_and_hpos_incompatibility_warning_text =
[242] Fix | Delete
sprintf(
[243] Fix | Delete
// translators: %s is a URL.
[244] Fix | Delete
__( '⚠ <b><a target="_blank" href="%s">The Legacy REST API</a> is active on this site.</b> Please be aware that the WooCommerce Legacy REST API is <b>not</b> compatible with HPOS.', 'woocommerce' ),
[245] Fix | Delete
admin_url( 'admin.php?page=wc-settings&tab=advanced&section=legacy_api' )
[246] Fix | Delete
);
[247] Fix | Delete
}
[248] Fix | Delete
[249] Fix | Delete
/**
[250] Fix | Delete
* Filter to modify the warning text that appears in the HPOS section of the features settings page
[251] Fix | Delete
* when both the Legacy REST API is active (via WooCommerce core or via the Legacy REST API plugin)
[252] Fix | Delete
* and the orders table is in use as the primary data store for orders.
[253] Fix | Delete
*
[254] Fix | Delete
* @param string $legacy_api_and_hpos_incompatibility_warning_text Original warning text.
[255] Fix | Delete
* @returns string|null Actual warning text to use, or null to suppress the warning.
[256] Fix | Delete
*
[257] Fix | Delete
* @since 8.9.0
[258] Fix | Delete
*/
[259] Fix | Delete
$legacy_api_and_hpos_incompatibility_warning_text = apply_filters( 'woocommerce_legacy_api_and_hpos_incompatibility_warning_text', $legacy_api_and_hpos_incompatibility_warning_text );
[260] Fix | Delete
[261] Fix | Delete
if ( ! is_null( $legacy_api_and_hpos_incompatibility_warning_text ) ) {
[262] Fix | Delete
$feature_warnings[] = $legacy_api_and_hpos_incompatibility_warning_text . "\n";
[263] Fix | Delete
}
[264] Fix | Delete
}
[265] Fix | Delete
[266] Fix | Delete
if ( $incompatible_count > 0 ) {
[267] Fix | Delete
if ( 1 === $incompatible_count ) {
[268] Fix | Delete
/* translators: %s = printable plugin name */
[269] Fix | Delete
$feature_warnings[] = sprintf( __( '⚠ 1 Incompatible plugin detected (%s).', 'woocommerce' ), $this->get_plugin_name( $incompatibles[0] ) );
[270] Fix | Delete
} elseif ( 2 === $incompatible_count ) {
[271] Fix | Delete
$feature_warnings[] = sprintf(
[272] Fix | Delete
/* translators: %1\$s, %2\$s = printable plugin names */
[273] Fix | Delete
__( '⚠ 2 Incompatible plugins detected (%1$s and %2$s).', 'woocommerce' ),
[274] Fix | Delete
$this->get_plugin_name( $incompatibles[0] ),
[275] Fix | Delete
$this->get_plugin_name( $incompatibles[1] )
[276] Fix | Delete
);
[277] Fix | Delete
} else {
[278] Fix | Delete
$feature_warnings[] = sprintf(
[279] Fix | Delete
/* translators: %1\$s, %2\$s = printable plugin names, %3\$d = plugins count */
[280] Fix | Delete
_n(
[281] Fix | Delete
'⚠ Incompatible plugins detected (%1$s, %2$s and %3$d other).',
[282] Fix | Delete
'⚠ Incompatible plugins detected (%1$s and %2$s plugins and %3$d others).',
[283] Fix | Delete
$incompatible_count - 2,
[284] Fix | Delete
'woocommerce'
[285] Fix | Delete
),
[286] Fix | Delete
$this->get_plugin_name( $incompatibles[0] ),
[287] Fix | Delete
$this->get_plugin_name( $incompatibles[1] ),
[288] Fix | Delete
$incompatible_count - 2
[289] Fix | Delete
);
[290] Fix | Delete
}
[291] Fix | Delete
[292] Fix | Delete
$incompatible_plugins_url = add_query_arg(
[293] Fix | Delete
array(
[294] Fix | Delete
'plugin_status' => 'incompatible_with_feature',
[295] Fix | Delete
'feature_id' => $feature_id,
[296] Fix | Delete
),
[297] Fix | Delete
admin_url( 'plugins.php' )
[298] Fix | Delete
);
[299] Fix | Delete
[300] Fix | Delete
$feature_warnings[] = sprintf(
[301] Fix | Delete
/* translators: %1$s opening link tag %2$s closing link tag. */
[302] Fix | Delete
__( '%1$sView and manage%2$s', 'woocommerce' ),
[303] Fix | Delete
'<a href="' . esc_url( $incompatible_plugins_url ) . '">',
[304] Fix | Delete
'</a>'
[305] Fix | Delete
);
[306] Fix | Delete
}
[307] Fix | Delete
[308] Fix | Delete
return str_replace( "\n", '<br>', implode( "\n", $feature_warnings ) );
[309] Fix | Delete
}
[310] Fix | Delete
[311] Fix | Delete
/**
[312] Fix | Delete
* Filter plugin/feature compatibility info, returning the names of the plugins/features that are considered incompatible.
[313] Fix | Delete
* "Uncertain" information will be included or not depending on the value of the value of the 'plugins_are_incompatible_by_default'
[314] Fix | Delete
* flag in the feature definition (default is true).
[315] Fix | Delete
*
[316] Fix | Delete
* @param string $feature_id Feature id.
[317] Fix | Delete
* @param array $compatibility_info Array containing "compatible', 'incompatible' and 'uncertain' keys.
[318] Fix | Delete
* @return array Items in 'incompatible' and 'uncertain' if plugins are incompatible by default with the feature; only items in 'incompatible' otherwise.
[319] Fix | Delete
*/
[320] Fix | Delete
public function get_items_considered_incompatible( string $feature_id, array $compatibility_info ): array {
[321] Fix | Delete
$incompatible_by_default = wc_get_container()->get( FeaturesController::class )->get_plugins_are_incompatible_by_default( $feature_id );
[322] Fix | Delete
[323] Fix | Delete
return $incompatible_by_default ?
[324] Fix | Delete
array_merge( $compatibility_info['incompatible'], $compatibility_info['uncertain'] ) :
[325] Fix | Delete
$compatibility_info['incompatible'];
[326] Fix | Delete
}
[327] Fix | Delete
[328] Fix | Delete
/**
[329] Fix | Delete
* Get the names of the plugins that are excluded from the feature compatibility UI.
[330] Fix | Delete
* These plugins won't be considered as incompatible with any existing feature for the purposes
[331] Fix | Delete
* of displaying compatibility warning in UI, even if they declare incompatibilities explicitly.
[332] Fix | Delete
*
[333] Fix | Delete
* @return string[] Plugin names relative to the root plugins directory.
[334] Fix | Delete
*/
[335] Fix | Delete
public function get_plugins_excluded_from_compatibility_ui() {
[336] Fix | Delete
return $this->plugins_excluded_from_compatibility_ui;
[337] Fix | Delete
}
[338] Fix | Delete
}
[339] Fix | Delete
[340] Fix | Delete
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function