/**
* WC_Payments_Dependency_Service class
*
* @package WooCommerce\Payments
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use WCPay\Database_Cache;
/**
* Validates dependencies (core, plugins, versions) for WCPAY
* Used in the plugin main class for validation.
*/
class WC_Payments_Dependency_Service {
const WOOCORE_NOT_FOUND = 'woocore_disabled';
const WOOCORE_INCOMPATIBLE = 'woocore_outdated';
const WOOADMIN_NOT_FOUND = 'wc_admin_not_found';
const WOOADMIN_INCOMPATIBLE = 'wc_admin_outdated';
const WP_INCOMPATIBLE = 'wp_outdated';
const DEV_ASSETS_NOT_BUILT = 'dev_assets_not_built';
/**
* Initializes this class's WP hooks.
*
* @return void
*/
public function init_hooks() {
add_filter( 'admin_notices', [ $this, 'display_admin_notices' ] );
}
/**
* Checks if all the dependencies needed to run WooPayments are present
*
* @return bool True if all required dependencies are met.
*/
public function has_valid_dependencies() {
if ( defined( 'WCPAY_TEST_ENV' ) && WCPAY_TEST_ENV ) {
return true;
}
return empty( $this->get_invalid_dependencies( true ) );
}
/**
* Render admin notices for unmet dependencies. Called on the admin_notices hook.
*
* @return null.
*/
public function display_admin_notices() {
// Do not show alerts while installing plugins.
if ( self::is_at_plugin_install_page() ) {
return;
}
// Show a message when assets are not built in a dev build.
if ( ! $this->are_assets_built() ) {
WC_Payments::display_admin_error( $this->get_notice_for_invalid_dependency( self::DEV_ASSETS_NOT_BUILT ) );
}
$invalid_dependencies = $this->get_invalid_dependencies();
if ( ! empty( $invalid_dependencies ) ) {
WC_Payments::display_admin_error( $this->get_notice_for_invalid_dependency( $invalid_dependencies[0] ) );
}
}
/**
* Returns an array of invalid dependencies
*
* @param bool $check_account_connection - if should bypass dependency version validation when an account is connected.
*
* @return array of invalid dependencies as string constants.
*/
public function get_invalid_dependencies( bool $check_account_connection = false ) {
$invalid_dependencies = [];
// Either ignore the account connection check or check if there's a cached account connection.
$ignore_when_account_is_connected = $check_account_connection && self::has_cached_account_connection();
if ( ! $this->is_woo_core_active() ) {
$invalid_dependencies[] = self::WOOCORE_NOT_FOUND;
}
if ( ! $ignore_when_account_is_connected && ! $this->is_woo_core_version_compatible() ) {
$invalid_dependencies[] = self::WOOCORE_INCOMPATIBLE;
}
if ( ! $this->is_wc_admin_enabled() ) {
$invalid_dependencies[] = self::WOOADMIN_NOT_FOUND;
}
if ( ! $ignore_when_account_is_connected && ! $this->is_wc_admin_version_compatible() ) {
$invalid_dependencies[] = self::WOOADMIN_INCOMPATIBLE;
}
if ( ! $ignore_when_account_is_connected && ! $this->is_wp_version_compatible() ) {
$invalid_dependencies[] = self::WP_INCOMPATIBLE;
}
return $invalid_dependencies;
}
/**
* Checks if WooCommerce is installed and activated.
*
* @return bool True if WooCommerce is installed and activated.
*/
public function is_woo_core_active() {
// Check if WooCommerce is installed and active.
return class_exists( 'WooCommerce' );
}
/**
* Checks if the version of WooCommerce is compatible with WooPayments.
*
* @return bool True if WooCommerce version is greater than or equal the minimum accepted
*/
public function is_woo_core_version_compatible() {
$plugin_headers = WC_Payments::get_plugin_headers();
$wc_version = $plugin_headers['WCRequires'];
// Check if the version of WooCommerce is compatible with WooPayments.
return ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, $wc_version, '>=' ) );
}
/**
* Checks if the WooCommerce version has WooCommerce Admin bundled (WC 4.0+)
* but it's disabled using a filter.
*
* @return bool True if WC Admin is found
*/
public function is_wc_admin_enabled() {
// Check if the current WooCommerce version has WooCommerce Admin bundled (WC 4.0+) but it's disabled using a filter.
if ( ! defined( 'WC_ADMIN_VERSION_NUMBER' ) || apply_filters( 'woocommerce_admin_disabled', false ) ) {
return false;
}
return true;
}
/**
* Checks if the version of WC Admin is compatible with WooPayments.
*
* @return bool True if WC Admin version is greater than or equal the minimum accepted
*/
public function is_wc_admin_version_compatible() {
// Check if the version of WooCommerce Admin is compatible with WooPayments.
return ( defined( 'WC_ADMIN_VERSION_NUMBER' ) && version_compare( WC_ADMIN_VERSION_NUMBER, WCPAY_MIN_WC_ADMIN_VERSION, '>=' ) );
}
/**
* Checks if the version of WordPress is compatible with WooPayments.
*
* @return bool True if WordPress version is greater than or equal the minimum accepted
*/
public function is_wp_version_compatible() {
$plugin_headers = WC_Payments::get_plugin_headers();
$wp_version = $plugin_headers['RequiresWP'];
return version_compare( get_bloginfo( 'version' ), $wp_version, '>=' );
}
/**
* Checks some of the asset files to confirm scripts and styles have been correctly built.
*
* @return bool TRUE if assets have been built or FALSE otherwise.
*/
public function are_assets_built() {
return ( file_exists( WCPAY_ABSPATH . 'dist/index.js' ) && file_exists( WCPAY_ABSPATH . 'dist/index.css' ) );
}
/**
* Get the error constant of an invalid dependency, and transforms it into HTML to be used in an Admin Notice.
*
* @param string $code - invalid dependency constant.
*
* @return string HTML to render admin notice for the unmet dependency.
*/
private function get_notice_for_invalid_dependency( $code ) {
$plugin_headers = WC_Payments::get_plugin_headers();
$wp_version = $plugin_headers['RequiresWP'];
$wc_version = $plugin_headers['WCRequires'];
$error_message = '';
switch ( $code ) {
case self::WOOCORE_NOT_FOUND:
$error_message = WC_Payments_Utils::esc_interpolated_html(
sprintf(
/* translators: %1$s: WooPayments, %2$s: WooCommerce */
__( '%1$s requires %2$s to be installed and active.', 'woocommerce-payments' ),
'WooPayments',
'WooCommerce'
),
[ 'a' => '' ]
);
if ( current_user_can( 'install_plugins' ) ) {
if ( is_wp_error( validate_plugin( 'woocommerce/woocommerce.php' ) ) ) {
// WooCommerce is not installed.
$activate_url = wp_nonce_url( admin_url( 'update.php?action=install-plugin&plugin=woocommerce' ), 'install-plugin_woocommerce' );
$activate_text = __( 'Install WooCommerce', 'woocommerce-payments' );
} else {
// WooCommerce is installed, so it just needs to be enabled.
$activate_url = wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=woocommerce/woocommerce.php' ), 'activate-plugin_woocommerce/woocommerce.php' );
$activate_text = __( 'Activate WooCommerce', 'woocommerce-payments' );
}
$error_message .= ' ' . $activate_text . '';
}
break;
case self::WOOCORE_INCOMPATIBLE:
$error_message = WC_Payments_Utils::esc_interpolated_html(
sprintf(
/* translators: %1: WooPayments, %2: current WooCommerce Payment version, %3: WooCommerce, %4: required WC version number, %5: currently installed WC version number */
__( '%1$s %2$s requires %3$s %4$s or greater to be installed (you are using %5$s). ', 'woocommerce-payments' ),
'WooPayments',
WCPAY_VERSION_NUMBER,
'WooCommerce',
$wc_version,
WC_VERSION
),
[ 'strong' => '' ]
);
if ( current_user_can( 'update_plugins' ) ) {
// Take the user to the "plugins" screen instead of trying to update WooCommerce inline. WooCommerce adds important information
// on its plugin row regarding the currently installed extensions and their compatibility with the latest WC version.
$error_message .= '
' . WC_Payments_Utils::esc_interpolated_html(
sprintf(
/* translators: %1$s: WooCommerce, %2$s: WooPayments, a1: link to the Plugins page, a2: link to the page having all previous versions */
__( 'Update %1$s (recommended) or manually re-install a previous version of %2$s.', 'woocommerce-payments' ),
'WooCommerce',
'WooPayments'
),
[
'a1' => '',
'strong' => '',
'a2' => '',
]
);
}
break;
case self::WOOADMIN_NOT_FOUND:
$error_message = WC_Payments_Utils::esc_interpolated_html(
sprintf(
/* translators: %1$s: WooPayments, %2$s: WooCommerce Admin */
__( '%1$s requires %2$s to be enabled. Please remove the woocommerce_admin_disabled
filter to use %1$s.', 'woocommerce-payments' ),
'WooPayments',
'WooCommerce Admin'
),
[ 'code' => '' ]
);
break;
case self::WOOADMIN_INCOMPATIBLE:
$error_message = WC_Payments_Utils::esc_interpolated_html(
sprintf(
/* translators: %1: WooPayments, %2: WooCommerce Admin, %3: required WC-Admin version number, %4: currently installed WC-Admin version number */
__( '%1$s requires %2$s %3$s or greater to be installed (you are using %4$s).', 'woocommerce-payments' ),
'WooPayments',
'WooCommerce Admin',
WCPAY_MIN_WC_ADMIN_VERSION,
WC_ADMIN_VERSION_NUMBER
),
[ 'strong' => '' ]
);
// Let's assume for now that any WC-Admin version bundled with WooCommerce will meet our minimum requirements.
$error_message .= ' ' . __( 'There is a newer version of WooCommerce Admin bundled with WooCommerce.', 'woocommerce-payments' );
if ( current_user_can( 'deactivate_plugins' ) ) {
$deactivate_url = wp_nonce_url( admin_url( 'plugins.php?action=deactivate&plugin=woocommerce-admin/woocommerce-admin.php' ), 'deactivate-plugin_woocommerce-admin/woocommerce-admin.php' );
$error_message .= ' ' . __( 'Use the bundled version of WooCommerce Admin', 'woocommerce-payments' ) . '';
}
break;
case self::WP_INCOMPATIBLE:
$error_message = WC_Payments_Utils::esc_interpolated_html(
sprintf(
/* translators: %1: WooPayments, %2: required WP version number, %3: currently installed WP version number */
__( '%1$s requires WordPress %2$s or greater (you are using %3$s).', 'woocommerce-payments' ),
'WooPayments',
$wp_version,
get_bloginfo( 'version' )
),
[ 'strong' => '' ]
);
if ( current_user_can( 'update_core' ) ) {
$error_message .= ' ' . __( 'Update WordPress', 'woocommerce-payments' ) . '';
}
break;
case self::DEV_ASSETS_NOT_BUILT:
$error_message = WC_Payments_Utils::esc_interpolated_html(
sprintf(
/* translators: %s: WooPayments */
__(
'You have installed a development version of %s which requires files to be built. From the plugin directory, run npm run build:client
to build and minify assets. Alternatively, you can download a pre-built version of the plugin from the WordPress.org repository or by visiting the releases page in the GitHub repository.',
'woocommerce-payments'
),
'WooPayments'
),
[
'code' => '',
'a1' => '',
'a2' => '',
]
);
break;
}
return $error_message;
}
/**
* Checks if current page is plugin installation process page.
*
* @return bool True when installing plugin.
*/
private static function is_at_plugin_install_page() {
$cur_screen = get_current_screen();
return $cur_screen && 'update' === $cur_screen->id && 'plugins' === $cur_screen->parent_base;
}
/**
* Check if the current WCPay Account has cache data.
*
* @return bool True if the cache data exists in wp_options.
*/
private static function has_cached_account_connection(): bool {
$account_data = get_option( Database_Cache::ACCOUNT_KEY );
return isset( $account_data['data'] ) && is_array( $account_data['data'] ) && ! empty( $account_data['data'] );
}
}