plugin install
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain;
|
||||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\AssetsController;
|
||||
use Automattic\WooCommerce\Blocks\BlockPatterns;
|
||||
use Automattic\WooCommerce\Blocks\BlockTemplatesRegistry;
|
||||
use Automattic\WooCommerce\Blocks\BlockTemplatesController;
|
||||
use Automattic\WooCommerce\Blocks\BlockTypesController;
|
||||
use Automattic\WooCommerce\Blocks\QueryFilters;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Notices;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\DraftOrders;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\GoogleAnalytics;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Hydration;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsAdmin;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFieldsFrontend;
|
||||
use Automattic\WooCommerce\Blocks\InboxNotifications;
|
||||
use Automattic\WooCommerce\Blocks\Installer;
|
||||
use Automattic\WooCommerce\Blocks\Migration;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Registry\Container;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ClassicTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\StoreApi\RoutesController;
|
||||
use Automattic\WooCommerce\StoreApi\SchemaController;
|
||||
use Automattic\WooCommerce\StoreApi\StoreApi;
|
||||
use Automattic\WooCommerce\Blocks\Shipping\ShippingController;
|
||||
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplateCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Templates\ArchiveProductTemplatesCompatibility;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\OnboardingTasks\TasksController;
|
||||
|
||||
/**
|
||||
* Takes care of bootstrapping the plugin.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Bootstrap {
|
||||
|
||||
/**
|
||||
* Holds the Dependency Injection Container
|
||||
*
|
||||
* @var Container
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* Holds the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
|
||||
/**
|
||||
* Holds the Migration instance
|
||||
*
|
||||
* @var Migration
|
||||
*/
|
||||
private $migration;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Container $container The Dependency Injection Container.
|
||||
*/
|
||||
public function __construct( Container $container ) {
|
||||
$this->container = $container;
|
||||
$this->package = $container->get( Package::class );
|
||||
$this->migration = $container->get( Migration::class );
|
||||
|
||||
$this->init();
|
||||
/**
|
||||
* Fires when the woocommerce blocks are loaded and ready to use.
|
||||
*
|
||||
* This hook is intended to be used as a safe event hook for when the plugin
|
||||
* has been loaded, and all dependency requirements have been met.
|
||||
*
|
||||
* To ensure blocks are initialized, you must use the `woocommerce_blocks_loaded`
|
||||
* hook instead of the `plugins_loaded` hook. This is because the functions
|
||||
* hooked into plugins_loaded on the same priority load in an inconsistent and unpredictable manner.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_loaded' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the package - load the blocks library and define constants.
|
||||
*/
|
||||
protected function init() {
|
||||
$this->register_dependencies();
|
||||
$this->register_payment_methods();
|
||||
$this->load_interactivity_api();
|
||||
|
||||
// This is just a temporary solution to make sure the migrations are run. We have to refactor this. More details: https://github.com/woocommerce/woocommerce-blocks/issues/10196.
|
||||
if ( $this->package->get_version() !== $this->package->get_version_stored_on_db() ) {
|
||||
$this->migration->run_migrations();
|
||||
$this->package->set_version_stored_on_db();
|
||||
}
|
||||
|
||||
add_action(
|
||||
'admin_init',
|
||||
function() {
|
||||
// Delete this notification because the blocks are included in WC Core now. This will handle any sites
|
||||
// with lingering notices.
|
||||
InboxNotifications::delete_surface_cart_checkout_blocks_notification();
|
||||
},
|
||||
10,
|
||||
0
|
||||
);
|
||||
|
||||
$is_rest = wc()->is_rest_api_request();
|
||||
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$is_store_api_request = $is_rest && ! empty( $_SERVER['REQUEST_URI'] ) && ( false !== strpos( $_SERVER['REQUEST_URI'], trailingslashit( rest_get_url_prefix() ) . 'wc/store/' ) );
|
||||
|
||||
// Load and init assets.
|
||||
$this->container->get( StoreApi::class )->init();
|
||||
$this->container->get( PaymentsApi::class )->init();
|
||||
$this->container->get( DraftOrders::class )->init();
|
||||
$this->container->get( CreateAccount::class )->init();
|
||||
$this->container->get( ShippingController::class )->init();
|
||||
$this->container->get( TasksController::class )->init();
|
||||
$this->container->get( CheckoutFields::class )->init();
|
||||
|
||||
// Load assets in admin and on the frontend.
|
||||
if ( ! $is_rest ) {
|
||||
$this->add_build_notice();
|
||||
$this->container->get( AssetDataRegistry::class );
|
||||
$this->container->get( AssetsController::class );
|
||||
$this->container->get( Installer::class )->init();
|
||||
$this->container->get( GoogleAnalytics::class )->init();
|
||||
$this->container->get( is_admin() ? CheckoutFieldsAdmin::class : CheckoutFieldsFrontend::class )->init();
|
||||
}
|
||||
|
||||
// Load assets unless this is a request specifically for the store API.
|
||||
if ( ! $is_store_api_request ) {
|
||||
// Template related functionality. These won't be loaded for store API requests, but may be loaded for
|
||||
// regular rest requests to maintain compatibility with the store editor.
|
||||
$this->container->get( BlockPatterns::class );
|
||||
$this->container->get( BlockTypesController::class );
|
||||
$this->container->get( BlockTemplatesRegistry::class )->init();
|
||||
$this->container->get( BlockTemplatesController::class )->init();
|
||||
$this->container->get( ClassicTemplatesCompatibility::class );
|
||||
$this->container->get( ArchiveProductTemplatesCompatibility::class )->init();
|
||||
$this->container->get( SingleProductTemplateCompatibility::class )->init();
|
||||
$this->container->get( Notices::class )->init();
|
||||
}
|
||||
|
||||
$this->container->get( QueryFilters::class )->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* See if files have been built or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_built() {
|
||||
return file_exists(
|
||||
$this->package->get_path( 'assets/client/blocks/featured-product.js' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a notice stating that the build has not been done yet.
|
||||
*/
|
||||
protected function add_build_notice() {
|
||||
if ( $this->is_built() ) {
|
||||
return;
|
||||
}
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function() {
|
||||
echo '<div class="error"><p>';
|
||||
printf(
|
||||
/* translators: %1$s is the node install command, %2$s is the install command, %3$s is the build command, %4$s is the watch command. */
|
||||
esc_html__( 'WooCommerce Blocks development mode requires files to be built. From the root directory, run %1$s to ensure your node version is aligned, run %2$s to install dependencies, %3$s to build the files or %4$s to build the files and watch for changes.', 'woocommerce' ),
|
||||
'<code>nvm use</code>',
|
||||
'<code>pnpm install</code>',
|
||||
'<code>pnpm --filter="@woocommerce/plugin-woocommerce" build</code>',
|
||||
'<code>pnpm --filter="@woocommerce/plugin-woocommerce" watch:build</code>'
|
||||
);
|
||||
echo '</p></div>';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and set up the Interactivity API if enabled.
|
||||
*/
|
||||
protected function load_interactivity_api() {
|
||||
require_once __DIR__ . '/../Interactivity/load.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Register core dependencies with the container.
|
||||
*/
|
||||
protected function register_dependencies() {
|
||||
$this->container->register(
|
||||
FeatureGating::class,
|
||||
function () {
|
||||
return new FeatureGating();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
AssetApi::class,
|
||||
function ( Container $container ) {
|
||||
return new AssetApi( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
AssetDataRegistry::class,
|
||||
function( Container $container ) {
|
||||
return new AssetDataRegistry( $container->get( AssetApi::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
AssetsController::class,
|
||||
function( Container $container ) {
|
||||
return new AssetsController( $container->get( AssetApi::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
PaymentMethodRegistry::class,
|
||||
function() {
|
||||
return new PaymentMethodRegistry();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
Installer::class,
|
||||
function () {
|
||||
return new Installer();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTypesController::class,
|
||||
function ( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
$asset_data_registry = $container->get( AssetDataRegistry::class );
|
||||
return new BlockTypesController( $asset_api, $asset_data_registry );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesRegistry::class,
|
||||
function ( Container $container ) {
|
||||
return new BlockTemplatesRegistry();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockTemplatesController::class,
|
||||
function ( Container $container ) {
|
||||
return new BlockTemplatesController();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
ClassicTemplatesCompatibility::class,
|
||||
function ( Container $container ) {
|
||||
$asset_data_registry = $container->get( AssetDataRegistry::class );
|
||||
return new ClassicTemplatesCompatibility( $asset_data_registry );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
ArchiveProductTemplatesCompatibility::class,
|
||||
function () {
|
||||
return new ArchiveProductTemplatesCompatibility();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
SingleProductTemplateCompatibility::class,
|
||||
function () {
|
||||
return new SingleProductTemplateCompatibility();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
DraftOrders::class,
|
||||
function( Container $container ) {
|
||||
return new DraftOrders( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CreateAccount::class,
|
||||
function( Container $container ) {
|
||||
return new CreateAccount( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
GoogleAnalytics::class,
|
||||
function( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new GoogleAnalytics( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
Notices::class,
|
||||
function( Container $container ) {
|
||||
return new Notices( $container->get( Package::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
Hydration::class,
|
||||
function( Container $container ) {
|
||||
return new Hydration( $container->get( AssetDataRegistry::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CheckoutFields::class,
|
||||
function( Container $container ) {
|
||||
return new CheckoutFields( $container->get( AssetDataRegistry::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CheckoutFieldsAdmin::class,
|
||||
function( Container $container ) {
|
||||
$checkout_fields_controller = $container->get( CheckoutFields::class );
|
||||
return new CheckoutFieldsAdmin( $checkout_fields_controller );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CheckoutFieldsFrontend::class,
|
||||
function( Container $container ) {
|
||||
$checkout_fields_controller = $container->get( CheckoutFields::class );
|
||||
return new CheckoutFieldsFrontend( $checkout_fields_controller );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
PaymentsApi::class,
|
||||
function ( Container $container ) {
|
||||
$payment_method_registry = $container->get( PaymentMethodRegistry::class );
|
||||
$asset_data_registry = $container->get( AssetDataRegistry::class );
|
||||
return new PaymentsApi( $payment_method_registry, $asset_data_registry );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
StoreApi::class,
|
||||
function () {
|
||||
return new StoreApi();
|
||||
}
|
||||
);
|
||||
// Maintains backwards compatibility with previous Store API namespace.
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\StoreApi\Formatters',
|
||||
function( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\Formatters', '6.4.0', 'Automattic\WooCommerce\StoreApi\Formatters', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( \Automattic\WooCommerce\StoreApi\Formatters::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi',
|
||||
function( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi', '6.4.0', 'Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( \Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\StoreApi\SchemaController',
|
||||
function( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\SchemaController', '6.4.0', 'Automattic\WooCommerce\StoreApi\SchemaController', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( SchemaController::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
'Automattic\WooCommerce\Blocks\StoreApi\RoutesController',
|
||||
function( Container $container ) {
|
||||
$this->deprecated_dependency( 'Automattic\WooCommerce\Blocks\StoreApi\RoutesController', '6.4.0', 'Automattic\WooCommerce\StoreApi\RoutesController', '6.5.0' );
|
||||
return $container->get( StoreApi::class )->container()->get( RoutesController::class );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BlockPatterns::class,
|
||||
function () {
|
||||
return new BlockPatterns( $this->package );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
ShippingController::class,
|
||||
function ( $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
$asset_data_registry = $container->get( AssetDataRegistry::class );
|
||||
return new ShippingController( $asset_api, $asset_data_registry );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
TasksController::class,
|
||||
function() {
|
||||
return new TasksController();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
QueryFilters::class,
|
||||
function() {
|
||||
return new QueryFilters();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a deprecation notice for a dependency without breaking requests.
|
||||
*
|
||||
* @param string $function Class or function being deprecated.
|
||||
* @param string $version Version in which it was deprecated.
|
||||
* @param string $replacement Replacement class or function, if applicable.
|
||||
* @param string $trigger_error_version Optional version to start surfacing this as a PHP error rather than a log. Defaults to $version.
|
||||
*/
|
||||
protected function deprecated_dependency( $function, $version, $replacement = '', $trigger_error_version = '' ) {
|
||||
if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$trigger_error_version = $trigger_error_version ? $trigger_error_version : $version;
|
||||
$error_message = $replacement ? sprintf(
|
||||
'%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.',
|
||||
$function,
|
||||
$version,
|
||||
$replacement
|
||||
) : sprintf(
|
||||
'%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.',
|
||||
$function,
|
||||
$version
|
||||
);
|
||||
/**
|
||||
* Fires when a deprecated function is called.
|
||||
*
|
||||
* @since 7.3.0
|
||||
*/
|
||||
do_action( 'deprecated_function_run', $function, $replacement, $version );
|
||||
|
||||
$log_error = false;
|
||||
|
||||
// If headers have not been sent yet, log to avoid breaking the request.
|
||||
if ( ! headers_sent() ) {
|
||||
$log_error = true;
|
||||
}
|
||||
|
||||
// If the $trigger_error_version was not yet reached, only log the error.
|
||||
if ( version_compare( Constants::get_constant( 'WC_VERSION' ), $trigger_error_version, '<' ) ) {
|
||||
$log_error = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters whether to trigger an error for deprecated functions. (Same as WP core)
|
||||
*
|
||||
* @since 7.3.0
|
||||
*
|
||||
* @param bool $trigger Whether to trigger the error for deprecated functions. Default true.
|
||||
*/
|
||||
if ( ! apply_filters( 'deprecated_function_trigger_error', true ) ) {
|
||||
$log_error = true;
|
||||
}
|
||||
|
||||
if ( $log_error ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( $error_message );
|
||||
} else {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
|
||||
trigger_error( $error_message, E_USER_DEPRECATED );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register payment method integrations with the container.
|
||||
*/
|
||||
protected function register_payment_methods() {
|
||||
$this->container->register(
|
||||
Cheque::class,
|
||||
function( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new Cheque( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
PayPal::class,
|
||||
function( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new PayPal( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
BankTransfer::class,
|
||||
function( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new BankTransfer( $asset_api );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
CashOnDelivery::class,
|
||||
function( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new CashOnDelivery( $asset_api );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
144
wp/wp-content/plugins/woocommerce/src/Blocks/Domain/Package.php
Normal file
144
wp/wp-content/plugins/woocommerce/src/Blocks/Domain/Package.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Options;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;
|
||||
|
||||
|
||||
/**
|
||||
* Main package class.
|
||||
*
|
||||
* Returns information about the package and handles init.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Package {
|
||||
|
||||
/**
|
||||
* Holds the current version of the blocks plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* Holds the main path to the blocks plugin directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Holds locally the plugin_dir_url to avoid recomputing it.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $plugin_dir_url;
|
||||
|
||||
/**
|
||||
* Holds the feature gating class instance.
|
||||
*
|
||||
* @var FeatureGating
|
||||
*/
|
||||
private $feature_gating;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $version Version of the plugin.
|
||||
* @param string $plugin_path Path to the main plugin file.
|
||||
* @param FeatureGating $feature_gating Feature gating class instance.
|
||||
*/
|
||||
public function __construct( $version, $plugin_path, FeatureGating $feature_gating ) {
|
||||
$this->version = $version;
|
||||
$this->path = $plugin_path;
|
||||
$this->feature_gating = $feature_gating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of WooCommerce Blocks.
|
||||
*
|
||||
* Note: since Blocks was merged into WooCommerce Core, the version of
|
||||
* WC Blocks doesn't update anymore. Use
|
||||
* `Constants::get_constant( 'WC_VERSION' )` when possible to get the
|
||||
* WooCommerce Core version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_version() {
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of WooCommerce Blocks stored in the database.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_version_stored_on_db() {
|
||||
return get_option( Options::WC_BLOCK_VERSION, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the version of WooCommerce Blocks in the database.
|
||||
* This is useful during the first installation or after the upgrade process.
|
||||
*/
|
||||
public function set_version_stored_on_db() {
|
||||
update_option( Options::WC_BLOCK_VERSION, $this->get_version() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the plugin directory.
|
||||
*
|
||||
* @param string $relative_path If provided, the relative path will be
|
||||
* appended to the plugin path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path( $relative_path = '' ) {
|
||||
return trailingslashit( $this->path ) . $relative_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the url to the blocks plugin directory.
|
||||
*
|
||||
* @param string $relative_url If provided, the relative url will be
|
||||
* appended to the plugin url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url( $relative_url = '' ) {
|
||||
if ( ! $this->plugin_dir_url ) {
|
||||
// Append index.php so WP does not return the parent directory.
|
||||
$this->plugin_dir_url = plugin_dir_url( $this->path . '/index.php' );
|
||||
}
|
||||
|
||||
return $this->plugin_dir_url . $relative_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the FeatureGating class.
|
||||
*
|
||||
* @return FeatureGating
|
||||
*/
|
||||
public function feature() {
|
||||
return $this->feature_gating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_experimental_build() {
|
||||
return $this->feature()->is_experimental_build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an feature plugin or experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_feature_plugin_build() {
|
||||
return $this->feature()->is_feature_plugin_build();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
|
||||
/**
|
||||
* Service class managing checkout fields and its related extensibility points in the admin area.
|
||||
*/
|
||||
class CheckoutFieldsAdmin {
|
||||
|
||||
/**
|
||||
* Checkout field controller.
|
||||
*
|
||||
* @var CheckoutFields
|
||||
*/
|
||||
private $checkout_fields_controller;
|
||||
|
||||
/**
|
||||
* Sets up core fields.
|
||||
*
|
||||
* @param CheckoutFields $checkout_fields_controller Instance of the checkout field controller.
|
||||
*/
|
||||
public function __construct( CheckoutFields $checkout_fields_controller ) {
|
||||
$this->checkout_fields_controller = $checkout_fields_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hooks. This is not run Store API requests.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_address_fields' ), 10, 3 );
|
||||
add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_contact_fields' ), 10, 3 );
|
||||
add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_address_fields' ), 10, 3 );
|
||||
add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_order_fields' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the shape of a checkout field to match whats needed in the WooCommerce meta boxes.
|
||||
*
|
||||
* @param array $field The field to format.
|
||||
* @param string $key The field key. This will be used for the ID of the field when passed to the meta box.
|
||||
* @return array Formatted field.
|
||||
*/
|
||||
protected function format_field_for_meta_box( $field, $key ) {
|
||||
$formatted_field = array(
|
||||
'id' => $key,
|
||||
'label' => $field['label'],
|
||||
'value' => $field['value'],
|
||||
'type' => $field['type'],
|
||||
'update_callback' => array( $this, 'update_callback' ),
|
||||
'show' => true,
|
||||
'wrapper_class' => 'form-field-wide',
|
||||
);
|
||||
|
||||
if ( 'select' === $field['type'] ) {
|
||||
$formatted_field['options'] = array_column( $field['options'], 'label', 'value' );
|
||||
}
|
||||
|
||||
if ( 'checkbox' === $field['type'] ) {
|
||||
$formatted_field['checked_value'] = '1';
|
||||
$formatted_field['unchecked_value'] = '0';
|
||||
}
|
||||
|
||||
return $formatted_field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a field value for an order.
|
||||
*
|
||||
* @param string $key The field key.
|
||||
* @param mixed $value The field value.
|
||||
* @param \WC_Order $order The order to update the field for.
|
||||
*/
|
||||
public function update_callback( $key, $value, $order ) {
|
||||
list( $group, $key ) = explode( '/', $key, 2 );
|
||||
$group = CheckoutFields::get_group_name( $group );
|
||||
$this->checkout_fields_controller->persist_field_for_order( $key, $value, $order, $group, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects address fields in WC admin orders screen.
|
||||
*
|
||||
* @param array $fields The fields to show.
|
||||
* @param \WC_Order|boolean $order The order to show the fields for.
|
||||
* @param string $context The context to show the fields for.
|
||||
* @return array
|
||||
*/
|
||||
public function admin_address_fields( $fields, $order = null, $context = 'edit' ) {
|
||||
if ( ! $order instanceof \WC_Order ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$group_name = doing_action( 'woocommerce_admin_billing_fields' ) ? 'billing' : 'shipping';
|
||||
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $group_name, $context );
|
||||
foreach ( $additional_fields as $key => $field ) {
|
||||
$prefixed_key = CheckoutFields::get_group_key( $group_name ) . $key;
|
||||
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
|
||||
}
|
||||
|
||||
array_splice(
|
||||
$fields,
|
||||
array_search(
|
||||
'state',
|
||||
array_keys( $fields ),
|
||||
true
|
||||
) + 1,
|
||||
0,
|
||||
$additional_fields
|
||||
);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects contact fields in WC admin orders screen.
|
||||
*
|
||||
* @param array $fields The fields to show.
|
||||
* @param \WC_Order|boolean $order The order to show the fields for.
|
||||
* @param string $context The context to show the fields for.
|
||||
* @return array
|
||||
*/
|
||||
public function admin_contact_fields( $fields, $order = null, $context = 'edit' ) {
|
||||
if ( ! $order instanceof \WC_Order ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', $context );
|
||||
|
||||
foreach ( $additional_fields as $key => $field ) {
|
||||
$prefixed_key = CheckoutFields::get_group_key( 'other' ) . $key;
|
||||
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
|
||||
}
|
||||
|
||||
return array_merge( $fields, $additional_fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects additional fields in WC admin orders screen.
|
||||
*
|
||||
* @param array $fields The fields to show.
|
||||
* @param \WC_Order|boolean $order The order to show the fields for.
|
||||
* @param string $context The context to show the fields for.
|
||||
* @return array
|
||||
*/
|
||||
public function admin_order_fields( $fields, $order = null, $context = 'edit' ) {
|
||||
if ( ! $order instanceof \WC_Order ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$additional_fields = $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', $context );
|
||||
|
||||
foreach ( $additional_fields as $key => $field ) {
|
||||
$prefixed_key = CheckoutFields::get_group_key( 'other' ) . $key;
|
||||
$additional_fields[ $key ] = $this->format_field_for_meta_box( $field, $prefixed_key );
|
||||
}
|
||||
|
||||
return array_merge( $fields, $additional_fields );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use WC_Customer;
|
||||
use WC_Order;
|
||||
|
||||
/**
|
||||
* Service class managing checkout fields and its related extensibility points on the frontend.
|
||||
*/
|
||||
class CheckoutFieldsFrontend {
|
||||
|
||||
/**
|
||||
* Checkout field controller.
|
||||
*
|
||||
* @var CheckoutFields
|
||||
*/
|
||||
private $checkout_fields_controller;
|
||||
|
||||
/**
|
||||
* Sets up core fields.
|
||||
*
|
||||
* @param CheckoutFields $checkout_fields_controller Instance of the checkout field controller.
|
||||
*/
|
||||
public function __construct( CheckoutFields $checkout_fields_controller ) {
|
||||
$this->checkout_fields_controller = $checkout_fields_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize hooks. This is not run Store API requests.
|
||||
*/
|
||||
public function init() {
|
||||
// Show custom checkout fields on the order details page.
|
||||
add_action( 'woocommerce_order_details_after_customer_address', array( $this, 'render_order_address_fields' ), 10, 2 );
|
||||
add_action( 'woocommerce_order_details_after_customer_details', array( $this, 'render_order_other_fields' ), 10 );
|
||||
|
||||
// Show custom checkout fields on the My Account page.
|
||||
add_action( 'woocommerce_my_account_after_my_address', array( $this, 'render_address_fields' ), 10, 1 );
|
||||
|
||||
// Edit account form under my account (for contact details).
|
||||
add_filter( 'woocommerce_save_account_details_required_fields', array( $this, 'edit_account_form_required_fields' ), 10, 1 );
|
||||
add_filter( 'woocommerce_edit_account_form_fields', array( $this, 'edit_account_form_fields' ), 10, 1 );
|
||||
add_action( 'woocommerce_save_account_details', array( $this, 'save_account_form_fields' ), 10, 1 );
|
||||
|
||||
// Edit address form under my account.
|
||||
add_filter( 'woocommerce_address_to_edit', array( $this, 'edit_address_fields' ), 10, 2 );
|
||||
add_action( 'woocommerce_after_save_address_validation', array( $this, 'save_address_fields' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom fields.
|
||||
*
|
||||
* @param array $fields List of additional fields with values.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_additional_fields( $fields ) {
|
||||
return ! empty( $fields ) ? '<dl class="wc-block-components-additional-fields-list">' . implode( '', array_map( array( $this, 'render_additional_field' ), $fields ) ) . '</dl>' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom field.
|
||||
*
|
||||
* @param array $field An additional field and value.
|
||||
* @return string
|
||||
*/
|
||||
protected function render_additional_field( $field ) {
|
||||
return sprintf(
|
||||
'<dt>%1$s</dt><dd>%2$s</dd>',
|
||||
esc_html( $field['label'] ),
|
||||
esc_html( $field['value'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders address fields on the order details page.
|
||||
*
|
||||
* @param string $address_type Type of address (billing or shipping).
|
||||
* @param WC_Order $order Order object.
|
||||
*/
|
||||
public function render_order_address_fields( $address_type, $order ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $this->render_additional_fields( $this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'address', $address_type, 'view' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders additional fields on the order details page.
|
||||
*
|
||||
* @param WC_Order $order Order object.
|
||||
*/
|
||||
public function render_order_other_fields( $order ) {
|
||||
$fields = array_merge(
|
||||
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'contact', 'other', 'view' ),
|
||||
$this->checkout_fields_controller->get_order_additional_fields_with_values( $order, 'order', 'other', 'view' ),
|
||||
);
|
||||
|
||||
if ( ! $fields ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<section class="wc-block-order-confirmation-additional-fields-wrapper">';
|
||||
echo '<h2>' . esc_html__( 'Additional information', 'woocommerce' ) . '</h2>';
|
||||
echo $this->render_additional_fields( $fields ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</section>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders address fields on the account page.
|
||||
*
|
||||
* @param string $address_type Type of address (billing or shipping).
|
||||
*/
|
||||
public function render_address_fields( $address_type ) {
|
||||
if ( ! in_array( $address_type, array( 'billing', 'shipping' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$customer = new WC_Customer( get_current_user_id() );
|
||||
$fields = $this->checkout_fields_controller->get_fields_for_location( 'address' );
|
||||
|
||||
if ( ! $fields || ! $customer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $fields as $key => $field ) {
|
||||
$value = $this->checkout_fields_controller->format_additional_field_value(
|
||||
$this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type ),
|
||||
$field
|
||||
);
|
||||
|
||||
if ( ! $value ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
printf( '<br><strong>%s</strong>: %s', wp_kses_post( $field['label'] ), wp_kses_post( $value ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register required additional contact fields.
|
||||
*
|
||||
* @param array $fields Required fields.
|
||||
* @return array
|
||||
*/
|
||||
public function edit_account_form_required_fields( $fields ) {
|
||||
$additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' );
|
||||
|
||||
foreach ( $additional_fields as $key => $field ) {
|
||||
if ( ! empty( $field['required'] ) ) {
|
||||
$fields[ $key ] = $field['label'];
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional contact fields to the My Account edit account form.
|
||||
*/
|
||||
public function edit_account_form_fields() {
|
||||
$customer = new WC_Customer( get_current_user_id() );
|
||||
$fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' );
|
||||
|
||||
foreach ( $fields as $key => $field ) {
|
||||
$field_key = CheckoutFields::get_group_key( 'other' ) . $key;
|
||||
$form_field = $field;
|
||||
$form_field['id'] = $field_key;
|
||||
$form_field['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, 'contact' );
|
||||
|
||||
if ( 'select' === $field['type'] ) {
|
||||
$form_field['options'] = array_column( $field['options'], 'label', 'value' );
|
||||
}
|
||||
|
||||
if ( 'checkbox' === $field['type'] ) {
|
||||
$form_field['checked_value'] = '1';
|
||||
$form_field['unchecked_value'] = '0';
|
||||
}
|
||||
|
||||
woocommerce_form_field( $key, $form_field, wc_get_post_data_by_key( $key, $form_field['value'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and saves additional address fields to the customer object on the My Account page.
|
||||
*
|
||||
* Customer is not provided by this hook so we handle save here.
|
||||
*
|
||||
* @param integer $user_id User ID.
|
||||
*/
|
||||
public function save_account_form_fields( $user_id ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
$customer = new WC_Customer( $user_id );
|
||||
$additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'contact' );
|
||||
$field_values = array();
|
||||
|
||||
foreach ( array_keys( $additional_fields ) as $key ) {
|
||||
$post_key = CheckoutFields::get_group_key( 'other' ) . $key;
|
||||
if ( ! isset( $_POST[ $post_key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $post_key ] ) ) );
|
||||
$validation = $this->checkout_fields_controller->validate_field( $key, $field_value );
|
||||
|
||||
if ( is_wp_error( $validation ) && $validation->has_errors() ) {
|
||||
wc_add_notice( $validation->get_error_message(), 'error' );
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_values[ $key ] = $field_value;
|
||||
}
|
||||
|
||||
// Persist individual additional fields to customer.
|
||||
foreach ( $field_values as $key => $value ) {
|
||||
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, 'other' );
|
||||
}
|
||||
|
||||
// Validate all fields for this location.
|
||||
$location_validation = $this->checkout_fields_controller->validate_fields_for_location( $field_values, 'contact', 'other' );
|
||||
|
||||
if ( is_wp_error( $location_validation ) && $location_validation->has_errors() ) {
|
||||
wc_add_notice( $location_validation->get_error_message(), 'error' );
|
||||
}
|
||||
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
$customer->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional address fields to the My Account edit address form.
|
||||
*
|
||||
* @param array $address Address fields.
|
||||
* @param string $address_type Type of address (billing or shipping).
|
||||
* @return array Updated address fields.
|
||||
*/
|
||||
public function edit_address_fields( $address, $address_type ) {
|
||||
$customer = new WC_Customer( get_current_user_id() );
|
||||
$fields = $this->checkout_fields_controller->get_fields_for_location( 'address' );
|
||||
|
||||
foreach ( $fields as $key => $field ) {
|
||||
$field_key = CheckoutFields::get_group_key( $address_type ) . $key;
|
||||
$address[ $field_key ] = $field;
|
||||
$address[ $field_key ]['value'] = $this->checkout_fields_controller->get_field_from_object( $key, $customer, $address_type );
|
||||
|
||||
if ( 'select' === $field['type'] ) {
|
||||
$address[ $field_key ]['options'] = array_column( $field['options'], 'label', 'value' );
|
||||
}
|
||||
|
||||
if ( 'checkbox' === $field['type'] ) {
|
||||
$address[ $field_key ]['checked_value'] = '1';
|
||||
$address[ $field_key ]['unchecked_value'] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the My Account page, save address fields. This uses the Store API endpoint for saving addresses so
|
||||
* extensibility hooks are consistent across the codebase.
|
||||
*
|
||||
* The caller saves the customer object if there are no errors. Nonces are checked before this method executes.
|
||||
*
|
||||
* @param integer $user_id User ID.
|
||||
* @param string $address_type Type of address (billing or shipping).
|
||||
* @param array $address Address fields.
|
||||
* @param WC_Customer $customer Customer object.
|
||||
*/
|
||||
public function save_address_fields( $user_id, $address_type, $address, $customer ) {
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
$additional_fields = $this->checkout_fields_controller->get_fields_for_location( 'address' );
|
||||
$field_values = array();
|
||||
|
||||
foreach ( array_keys( $additional_fields ) as $key ) {
|
||||
$post_key = CheckoutFields::get_group_key( $address_type ) . $key;
|
||||
|
||||
if ( ! isset( $_POST[ $post_key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_value = $this->checkout_fields_controller->sanitize_field( $key, wc_clean( wp_unslash( $_POST[ $post_key ] ) ) );
|
||||
$validation = $this->checkout_fields_controller->validate_field( $key, $field_value );
|
||||
|
||||
if ( is_wp_error( $validation ) && $validation->has_errors() ) {
|
||||
wc_add_notice( $validation->get_error_message(), 'error' );
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_values[ $key ] = $field_value;
|
||||
}
|
||||
|
||||
// Persist individual additional fields to customer.
|
||||
foreach ( $field_values as $key => $value ) {
|
||||
$this->checkout_fields_controller->persist_field_for_customer( $key, $value, $customer, $address_type );
|
||||
}
|
||||
|
||||
// Validate all fields for this location.
|
||||
$location_validation = $this->checkout_fields_controller->validate_fields_for_location( array_merge( $address, $field_values ), 'address', $address_type );
|
||||
|
||||
if ( is_wp_error( $location_validation ) && $location_validation->has_errors() ) {
|
||||
wc_add_notice( $location_validation->get_error_message(), 'error' );
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount;
|
||||
|
||||
/**
|
||||
* Service class implementing new create account emails used for order processing via the Block Based Checkout.
|
||||
*/
|
||||
class CreateAccount {
|
||||
/**
|
||||
* Reference to the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Package $package An instance of (Woo Blocks) Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init - register handlers for WooCommerce core email hooks.
|
||||
*/
|
||||
public function init() {
|
||||
// Override core email handlers to add our new improved "new account" email.
|
||||
add_action(
|
||||
'woocommerce_email',
|
||||
function ( $wc_emails_instance ) {
|
||||
// Remove core "new account" handler; we are going to replace it.
|
||||
remove_action( 'woocommerce_created_customer_notification', array( $wc_emails_instance, 'customer_new_account' ), 10, 3 );
|
||||
|
||||
// Add custom "new account" handler.
|
||||
add_action(
|
||||
'woocommerce_created_customer_notification',
|
||||
function( $customer_id, $new_customer_data = array(), $password_generated = false ) use ( $wc_emails_instance ) {
|
||||
// If this is a block-based signup, send a new email with password reset link (no password in email).
|
||||
if ( isset( $new_customer_data['source'] ) && 'store-api' === $new_customer_data['source'] ) {
|
||||
$this->customer_new_account( $customer_id, $new_customer_data );
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, trigger the existing legacy email (with new password inline).
|
||||
$wc_emails_instance->customer_new_account( $customer_id, $new_customer_data, $password_generated );
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger new account email.
|
||||
* This is intended as a replacement to WC_Emails::customer_new_account(),
|
||||
* with a set password link instead of emailing the new password in email
|
||||
* content.
|
||||
*
|
||||
* @param int $customer_id The ID of the new customer account.
|
||||
* @param array $new_customer_data Assoc array of data for the new account.
|
||||
*/
|
||||
public function customer_new_account( $customer_id = 0, array $new_customer_data = array() ) {
|
||||
$new_account_email = new CustomerNewAccount( $this->package );
|
||||
$new_account_email->trigger( $customer_id, $new_customer_data );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Exception;
|
||||
use WC_Order;
|
||||
|
||||
/**
|
||||
* Service class for adding DraftOrder functionality to WooCommerce core.
|
||||
*
|
||||
* Sets up all logic related to the Checkout Draft Orders service
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DraftOrders {
|
||||
|
||||
const DB_STATUS = 'wc-checkout-draft';
|
||||
const STATUS = 'checkout-draft';
|
||||
|
||||
/**
|
||||
* Holds the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Package $package An instance of the package class.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all hooks related to adding Checkout Draft order functionality to Woo Core.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'wc_order_statuses', [ $this, 'register_draft_order_status' ] );
|
||||
add_filter( 'woocommerce_register_shop_order_post_statuses', [ $this, 'register_draft_order_post_status' ] );
|
||||
add_filter( 'woocommerce_analytics_excluded_order_statuses', [ $this, 'append_draft_order_post_status' ] );
|
||||
add_filter( 'woocommerce_valid_order_statuses_for_payment', [ $this, 'append_draft_order_post_status' ] );
|
||||
add_filter( 'woocommerce_valid_order_statuses_for_payment_complete', [ $this, 'append_draft_order_post_status' ] );
|
||||
// Hook into the query to retrieve My Account orders so draft status is excluded.
|
||||
add_action( 'woocommerce_my_account_my_orders_query', [ $this, 'delete_draft_order_post_status_from_args' ] );
|
||||
add_action( 'woocommerce_cleanup_draft_orders', [ $this, 'delete_expired_draft_orders' ] );
|
||||
add_action( 'admin_init', [ $this, 'install' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Installation related logic for Draft order functionality.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function install() {
|
||||
$this->maybe_create_cronjobs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe create cron events.
|
||||
*/
|
||||
protected function maybe_create_cronjobs() {
|
||||
if ( function_exists( 'as_next_scheduled_action' ) && false === as_next_scheduled_action( 'woocommerce_cleanup_draft_orders' ) ) {
|
||||
as_schedule_recurring_action( strtotime( 'midnight tonight' ), DAY_IN_SECONDS, 'woocommerce_cleanup_draft_orders' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom order status for orders created via the API during checkout.
|
||||
*
|
||||
* Draft order status is used before payment is attempted, during checkout, when a cart is converted to an order.
|
||||
*
|
||||
* @param array $statuses Array of statuses.
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
public function register_draft_order_status( array $statuses ) {
|
||||
$statuses[ self::DB_STATUS ] = _x( 'Draft', 'Order status', 'woocommerce' );
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom order post status for orders created via the API during checkout.
|
||||
*
|
||||
* @param array $statuses Array of statuses.
|
||||
* @internal
|
||||
|
||||
* @return array
|
||||
*/
|
||||
public function register_draft_order_post_status( array $statuses ) {
|
||||
$statuses[ self::DB_STATUS ] = $this->get_post_status_properties();
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties of this post status for registration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_post_status_properties() {
|
||||
return [
|
||||
'label' => _x( 'Draft', 'Order status', 'woocommerce' ),
|
||||
'public' => false,
|
||||
'exclude_from_search' => false,
|
||||
'show_in_admin_all_list' => false,
|
||||
'show_in_admin_status_list' => true,
|
||||
/* translators: %s: number of orders */
|
||||
'label_count' => _n_noop( 'Drafts <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>', 'woocommerce' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove draft status from the 'status' argument of an $args array.
|
||||
*
|
||||
* @param array $args Array of arguments containing statuses in the status key.
|
||||
* @internal
|
||||
* @return array
|
||||
*/
|
||||
public function delete_draft_order_post_status_from_args( $args ) {
|
||||
if ( ! array_key_exists( 'status', $args ) ) {
|
||||
$statuses = [];
|
||||
foreach ( wc_get_order_statuses() as $key => $label ) {
|
||||
if ( self::DB_STATUS !== $key ) {
|
||||
$statuses[] = str_replace( 'wc-', '', $key );
|
||||
}
|
||||
}
|
||||
$args['status'] = $statuses;
|
||||
} elseif ( self::DB_STATUS === $args['status'] ) {
|
||||
$args['status'] = '';
|
||||
} elseif ( is_array( $args['status'] ) ) {
|
||||
$args['status'] = array_diff_key( $args['status'], array( self::STATUS => null ) );
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append draft status to a list of statuses.
|
||||
*
|
||||
* @param array $statuses Array of statuses.
|
||||
* @internal
|
||||
|
||||
* @return array
|
||||
*/
|
||||
public function append_draft_order_post_status( $statuses ) {
|
||||
$statuses[] = self::STATUS;
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete draft orders older than a day in batches of 20.
|
||||
*
|
||||
* Ran on a daily cron schedule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function delete_expired_draft_orders() {
|
||||
$count = 0;
|
||||
$batch_size = 20;
|
||||
$this->ensure_draft_status_registered();
|
||||
$orders = wc_get_orders(
|
||||
[
|
||||
'date_modified' => '<=' . strtotime( '-1 DAY' ),
|
||||
'limit' => $batch_size,
|
||||
'status' => self::DB_STATUS,
|
||||
'type' => 'shop_order',
|
||||
]
|
||||
);
|
||||
|
||||
// do we bail because the query results are unexpected?
|
||||
try {
|
||||
$this->assert_order_results( $orders, $batch_size );
|
||||
if ( $orders ) {
|
||||
foreach ( $orders as $order ) {
|
||||
$order->delete( true );
|
||||
$count ++;
|
||||
}
|
||||
}
|
||||
if ( $batch_size === $count && function_exists( 'as_enqueue_async_action' ) ) {
|
||||
as_enqueue_async_action( 'woocommerce_cleanup_draft_orders' );
|
||||
}
|
||||
} catch ( Exception $error ) {
|
||||
wc_caught_exception( $error, __METHOD__ );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since it's possible for third party code to clobber the `$wp_post_statuses` global,
|
||||
* we need to do a final check here to make sure the draft post status is
|
||||
* registered with the global so that it is not removed by WP_Query status
|
||||
* validation checks.
|
||||
*/
|
||||
private function ensure_draft_status_registered() {
|
||||
$is_registered = get_post_stati( [ 'name' => self::DB_STATUS ] );
|
||||
if ( empty( $is_registered ) ) {
|
||||
register_post_status(
|
||||
self::DB_STATUS,
|
||||
$this->get_post_status_properties()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts whether incoming order results are expected given the query
|
||||
* this service class executes.
|
||||
*
|
||||
* @param WC_Order[] $order_results The order results being asserted.
|
||||
* @param int $expected_batch_size The expected batch size for the results.
|
||||
* @throws Exception If any assertions fail, an exception is thrown.
|
||||
*/
|
||||
private function assert_order_results( $order_results, $expected_batch_size ) {
|
||||
// if not an array, then just return because it won't get handled
|
||||
// anyways.
|
||||
if ( ! is_array( $order_results ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = ' This is an indicator that something is filtering WooCommerce or WordPress queries and modifying the query parameters.';
|
||||
|
||||
// if count is greater than our expected batch size, then that's a problem.
|
||||
if ( count( $order_results ) > 20 ) {
|
||||
throw new Exception( 'There are an unexpected number of results returned from the query.' . $suffix );
|
||||
}
|
||||
|
||||
// if any of the returned orders are not draft (or not a WC_Order), then that's a problem.
|
||||
foreach ( $order_results as $order ) {
|
||||
if ( ! ( $order instanceof WC_Order ) ) {
|
||||
throw new Exception( 'The returned results contain a value that is not a WC_Order.' . $suffix );
|
||||
}
|
||||
if ( ! $order->has_status( self::STATUS ) ) {
|
||||
throw new Exception( 'The results contain an order that is not a `wc-checkout-draft` status in the results.' . $suffix );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services\Email;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
|
||||
/**
|
||||
* Customer New Account.
|
||||
*
|
||||
* An email sent to the customer when they create an account.
|
||||
* This is intended as a replacement to \WC_Email_Customer_New_Account(),
|
||||
* with a set password link instead of emailing the new password in email
|
||||
* content.
|
||||
*
|
||||
* @extends \WC_Email
|
||||
*/
|
||||
class CustomerNewAccount extends \WC_Email {
|
||||
|
||||
/**
|
||||
* User login name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $user_login;
|
||||
|
||||
/**
|
||||
* User email.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $user_email;
|
||||
|
||||
/**
|
||||
* Magic link to set initial password.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $set_password_url;
|
||||
|
||||
/**
|
||||
* Override (force) default template path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $default_template_path;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Package $package An instance of (Woo Blocks) Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
// Note - we're using the same ID as the real email.
|
||||
// This ensures that any merchant tweaks (Settings > Emails)
|
||||
// apply to this email (consistent with the core email).
|
||||
$this->id = 'customer_new_account';
|
||||
$this->customer_email = true;
|
||||
$this->title = __( 'New account', 'woocommerce' );
|
||||
$this->description = __( '“New Account” emails are sent when a customer signs up via the checkout flow.', 'woocommerce' );
|
||||
$this->template_html = 'emails/customer-new-account-blocks.php';
|
||||
$this->template_plain = 'emails/plain/customer-new-account-blocks.php';
|
||||
$this->default_template_path = $package->get_path( '/templates/' );
|
||||
|
||||
// Call parent constructor.
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email subject.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_subject() {
|
||||
return __( 'Your {site_title} account has been created!', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email heading.
|
||||
*
|
||||
* @since 3.1.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_heading() {
|
||||
return __( 'Welcome to {site_title}', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger.
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @param string $user_pass User password.
|
||||
* @param bool $password_generated Whether the password was generated automatically or not.
|
||||
*/
|
||||
public function trigger( $user_id, $user_pass = '', $password_generated = false ) {
|
||||
$this->setup_locale();
|
||||
|
||||
if ( $user_id ) {
|
||||
$this->object = new \WP_User( $user_id );
|
||||
|
||||
// Generate a magic link so user can set initial password.
|
||||
$key = get_password_reset_key( $this->object );
|
||||
if ( ! is_wp_error( $key ) ) {
|
||||
$action = 'newaccount';
|
||||
$this->set_password_url = wc_get_account_endpoint_url( 'lost-password' ) . "?action=$action&key=$key&login=" . rawurlencode( $this->object->user_login );
|
||||
}
|
||||
|
||||
$this->user_login = stripslashes( $this->object->user_login );
|
||||
$this->user_email = stripslashes( $this->object->user_email );
|
||||
$this->recipient = $this->user_email;
|
||||
}
|
||||
|
||||
if ( $this->is_enabled() && $this->get_recipient() ) {
|
||||
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments(), $this->set_password_url );
|
||||
}
|
||||
|
||||
$this->restore_locale();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content html.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content_html() {
|
||||
return wc_get_template_html(
|
||||
$this->template_html,
|
||||
array(
|
||||
'email_heading' => $this->get_heading(),
|
||||
'additional_content' => $this->get_additional_content(),
|
||||
'user_login' => $this->user_login,
|
||||
'blogname' => $this->get_blogname(),
|
||||
'set_password_url' => $this->set_password_url,
|
||||
'sent_to_admin' => false,
|
||||
'plain_text' => false,
|
||||
'email' => $this,
|
||||
),
|
||||
'',
|
||||
$this->default_template_path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content plain.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content_plain() {
|
||||
return wc_get_template_html(
|
||||
$this->template_plain,
|
||||
array(
|
||||
'email_heading' => $this->get_heading(),
|
||||
'additional_content' => $this->get_additional_content(),
|
||||
'user_login' => $this->user_login,
|
||||
'blogname' => $this->get_blogname(),
|
||||
'set_password_url' => $this->set_password_url,
|
||||
'sent_to_admin' => false,
|
||||
'plain_text' => true,
|
||||
'email' => $this,
|
||||
),
|
||||
'',
|
||||
$this->default_template_path
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default content to show below main email content.
|
||||
*
|
||||
* @since 3.7.0
|
||||
* @return string
|
||||
*/
|
||||
public function get_default_additional_content() {
|
||||
return __( 'We look forward to seeing you soon.', 'woocommerce' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
/**
|
||||
* Service class that handles the feature flags.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FeatureGating {
|
||||
|
||||
/**
|
||||
* Current flag value.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $flag;
|
||||
|
||||
const EXPERIMENTAL_FLAG = 3;
|
||||
const FEATURE_PLUGIN_FLAG = 2;
|
||||
const CORE_FLAG = 1;
|
||||
|
||||
/**
|
||||
* Current environment
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $environment;
|
||||
|
||||
const PRODUCTION_ENVIRONMENT = 'production';
|
||||
const DEVELOPMENT_ENVIRONMENT = 'development';
|
||||
const TEST_ENVIRONMENT = 'test';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param int $flag Hardcoded flag value. Useful for tests.
|
||||
* @param string $environment Hardcoded environment value. Useful for tests.
|
||||
*/
|
||||
public function __construct( $flag = 0, $environment = 'unset' ) {
|
||||
$this->flag = $flag;
|
||||
$this->environment = $environment;
|
||||
$this->load_flag();
|
||||
$this->load_environment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set correct flag.
|
||||
*/
|
||||
public function load_flag() {
|
||||
if ( 0 === $this->flag ) {
|
||||
$default_flag = defined( 'WC_BLOCKS_IS_FEATURE_PLUGIN' ) ? self::FEATURE_PLUGIN_FLAG : self::CORE_FLAG;
|
||||
if ( file_exists( __DIR__ . '/../../../../blocks.ini' ) ) {
|
||||
$allowed_flags = [ self::EXPERIMENTAL_FLAG, self::FEATURE_PLUGIN_FLAG, self::CORE_FLAG ];
|
||||
$woo_options = parse_ini_file( __DIR__ . '/../../../../blocks.ini' );
|
||||
$this->flag = is_array( $woo_options ) && in_array( intval( $woo_options['woocommerce_blocks_phase'] ), $allowed_flags, true ) ? $woo_options['woocommerce_blocks_phase'] : $default_flag;
|
||||
} else {
|
||||
$this->flag = $default_flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set correct environment.
|
||||
*/
|
||||
public function load_environment() {
|
||||
if ( 'unset' === $this->environment ) {
|
||||
if ( file_exists( __DIR__ . '/../../../../blocks.ini' ) ) {
|
||||
$allowed_environments = [ self::PRODUCTION_ENVIRONMENT, self::DEVELOPMENT_ENVIRONMENT, self::TEST_ENVIRONMENT ];
|
||||
$woo_options = parse_ini_file( __DIR__ . '/../../../../blocks.ini' );
|
||||
$this->environment = is_array( $woo_options ) && in_array( $woo_options['woocommerce_blocks_env'], $allowed_environments, true ) ? $woo_options['woocommerce_blocks_env'] : self::PRODUCTION_ENVIRONMENT;
|
||||
} else {
|
||||
$this->environment = self::PRODUCTION_ENVIRONMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current flag value.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_flag() {
|
||||
return $this->flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_experimental_build() {
|
||||
return $this->flag >= self::EXPERIMENTAL_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an feature plugin or experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_feature_plugin_build() {
|
||||
return $this->flag >= self::FEATURE_PLUGIN_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current environment value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_environment() {
|
||||
return $this->environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an development environment.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_development_environment() {
|
||||
return self::DEVELOPMENT_ENVIRONMENT === $this->environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in a production environment.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_production_environment() {
|
||||
return self::PRODUCTION_ENVIRONMENT === $this->environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in a test environment.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_test_environment() {
|
||||
return self::TEST_ENVIRONMENT === $this->environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns core flag value.
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public static function get_core_flag() {
|
||||
return self::CORE_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns feature plugin flag value.
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public static function get_feature_plugin_flag() {
|
||||
return self::FEATURE_PLUGIN_FLAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns experimental flag value.
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
public static function get_experimental_flag() {
|
||||
return self::EXPERIMENTAL_FLAG;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the block templates controller refactor should be used to display blocks.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_block_templates_controller_refactor_enabled() {
|
||||
if ( file_exists( __DIR__ . '/../../../../blocks.ini' ) ) {
|
||||
$conf = parse_ini_file( __DIR__ . '/../../../../blocks.ini' );
|
||||
return $this->is_development_environment() && isset( $conf['use_block_templates_controller_refactor'] ) && true === (bool) $conf['use_block_templates_controller_refactor'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
|
||||
/**
|
||||
* Service class to integrate Blocks with the Google Analytics extension,
|
||||
*/
|
||||
class GoogleAnalytics {
|
||||
/**
|
||||
* Instance of the asset API.
|
||||
*
|
||||
* @var AssetApi
|
||||
*/
|
||||
protected $asset_api;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Instance of the asset API.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api ) {
|
||||
$this->asset_api = $asset_api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WP.
|
||||
*/
|
||||
public function init() {
|
||||
// Require Google Analytics Integration to be activated.
|
||||
if ( ! class_exists( 'WC_Google_Analytics_Integration', false ) ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'init', array( $this, 'register_assets' ) );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
||||
add_filter( 'script_loader_tag', array( $this, 'async_script_loader_tags' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register scripts.
|
||||
*/
|
||||
public function register_assets() {
|
||||
$this->asset_api->register_script( 'wc-blocks-google-analytics', 'assets/client/blocks/wc-blocks-google-analytics.js', [ 'google-tag-manager' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the Google Tag Manager script if prerequisites are met.
|
||||
*/
|
||||
public function enqueue_scripts() {
|
||||
$settings = $this->get_google_analytics_settings();
|
||||
$prefix = strstr( strtoupper( $settings['ga_id'] ), '-', true );
|
||||
|
||||
// Require tracking to be enabled with a valid GA ID.
|
||||
if ( ! in_array( $prefix, [ 'G', 'GT' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to disable Google Analytics tracking.
|
||||
*
|
||||
* @internal Matches filter name in GA extension.
|
||||
* @since 4.9.0
|
||||
*
|
||||
* @param boolean $disable_tracking If true, tracking will be disabled.
|
||||
*/
|
||||
if ( apply_filters( 'woocommerce_ga_disable_tracking', ! wc_string_to_bool( $settings['ga_event_tracking_enabled'] ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_script_is( 'google-tag-manager', 'registered' ) ) {
|
||||
// Using an array with strategies as the final argument to wp_register_script was introduced in WP 6.3.
|
||||
// WC requires at least 6.3 at the point of adding this, so it's safe to leave in without version checks.
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
wp_register_script(
|
||||
'google-tag-manager',
|
||||
'https://www.googletagmanager.com/gtag/js?id=' . $settings['ga_id'],
|
||||
[],
|
||||
null,
|
||||
[
|
||||
'in_footer' => false,
|
||||
'strategy' => 'async',
|
||||
]
|
||||
);
|
||||
wp_add_inline_script(
|
||||
'google-tag-manager',
|
||||
"
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '" . esc_js( $settings['ga_id'] ) . "', { 'send_page_view': false });"
|
||||
);
|
||||
}
|
||||
wp_enqueue_script( 'wc-blocks-google-analytics' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get settings from the GA integration extension.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_google_analytics_settings() {
|
||||
return wp_parse_args(
|
||||
get_option( 'woocommerce_google_analytics_settings' ),
|
||||
[
|
||||
'ga_id' => '',
|
||||
'ga_event_tracking_enabled' => 'no',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add async to script tags with defined handles.
|
||||
*
|
||||
* @param string $tag HTML for the script tag.
|
||||
* @param string $handle Handle of script.
|
||||
* @param string $src Src of script.
|
||||
* @return string
|
||||
*/
|
||||
public function async_script_loader_tags( $tag, $handle, $src ) {
|
||||
if ( ! in_array( $handle, array( 'google-tag-manager' ), true ) ) {
|
||||
return $tag;
|
||||
}
|
||||
// If script was output manually in wp_head, abort.
|
||||
if ( did_action( 'woocommerce_gtag_snippet' ) ) {
|
||||
return '';
|
||||
}
|
||||
return str_replace( '<script src', '<script async src', $tag );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\StoreApi\RoutesController;
|
||||
use Automattic\WooCommerce\StoreApi\SchemaController;
|
||||
use Automattic\WooCommerce\StoreApi\StoreApi;
|
||||
|
||||
/**
|
||||
* Service class that handles hydration of API data for blocks.
|
||||
*/
|
||||
class Hydration {
|
||||
/**
|
||||
* Instance of the asset data registry.
|
||||
*
|
||||
* @var AssetDataRegistry
|
||||
*/
|
||||
protected $asset_data_registry;
|
||||
|
||||
/**
|
||||
* Cached notices to restore after hydrating the API.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $cached_store_notices = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
|
||||
*/
|
||||
public function __construct( AssetDataRegistry $asset_data_registry ) {
|
||||
$this->asset_data_registry = $asset_data_registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrates the asset data registry with data from the API. Disables notices and nonces so requests contain valid
|
||||
* data that is not polluted by the current session.
|
||||
*
|
||||
* @param array $path API paths to hydrate e.g. '/wc/store/v1/cart'.
|
||||
* @return array Response data.
|
||||
*/
|
||||
public function get_rest_api_response_data( $path = '' ) {
|
||||
if ( ! str_starts_with( $path, '/wc/store' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Allow-list only store API routes. No other request can be hydrated for safety.
|
||||
$available_routes = StoreApi::container()->get( RoutesController::class )->get_all_routes( 'v1', true );
|
||||
$controller_class = $this->match_route_to_handler( $path, $available_routes );
|
||||
|
||||
/**
|
||||
* We disable nonce check to support endpoints such as checkout. The caveat here is that we need to be careful to only support GET requests. No other request type should be processed without nonce check. Additionally, no GET request can modify data as part of hydration request, for example adding items to cart.
|
||||
*
|
||||
* Long term, we should consider validating nonce here, instead of disabling it temporarily.
|
||||
*/
|
||||
$this->disable_nonce_check();
|
||||
|
||||
$this->cache_store_notices();
|
||||
|
||||
$preloaded_data = array();
|
||||
|
||||
if ( null !== $controller_class ) {
|
||||
try {
|
||||
$response = $this->get_response_from_controller( $controller_class, $path );
|
||||
if ( $response ) {
|
||||
$preloaded_data = array(
|
||||
'body' => $response->get_data(),
|
||||
'headers' => $response->get_headers(),
|
||||
);
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
// This is executing in frontend of the site, a failure in hydration should not stop the site from working.
|
||||
wc_get_logger()->warning(
|
||||
'Error in hydrating REST API request: ' . $e->getMessage(),
|
||||
array(
|
||||
'source' => 'blocks-hydration',
|
||||
'data' => array(
|
||||
'path' => $path,
|
||||
'controller' => $controller_class,
|
||||
),
|
||||
'backtrace' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Preload the request and add it to the array. It will be $preloaded_requests['path'] and contain 'body' and 'headers'.
|
||||
$preloaded_requests = rest_preload_api_request( array(), $path );
|
||||
$preloaded_data = $preloaded_requests[ $path ] ?? array();
|
||||
}
|
||||
|
||||
$this->restore_cached_store_notices();
|
||||
$this->restore_nonce_check();
|
||||
|
||||
// Returns just the single preloaded request, or an empty array if it doesn't exist.
|
||||
return $preloaded_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate GET response from a controller. Also fires the `rest_request_after_callbacks` for backward compatibility.
|
||||
*
|
||||
* @param string $controller_class Controller class FQN that will respond to the request.
|
||||
* @param string $path Request path regex.
|
||||
*
|
||||
* @return false|mixed|null Response
|
||||
*/
|
||||
private function get_response_from_controller( $controller_class, $path ) {
|
||||
if ( null === $controller_class ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request = new \WP_REST_Request( 'GET', $path );
|
||||
$schema_controller = StoreApi::container()->get( SchemaController::class );
|
||||
$controller = new $controller_class(
|
||||
$schema_controller,
|
||||
$schema_controller->get( $controller_class::SCHEMA_TYPE, $controller_class::SCHEMA_VERSION )
|
||||
);
|
||||
|
||||
$controller_args = is_callable( array( $controller, 'get_args' ) ) ? $controller->get_args() : array();
|
||||
|
||||
if ( empty( $controller_args ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the handler that responds to read request.
|
||||
$handler = current(
|
||||
array_filter(
|
||||
$controller_args,
|
||||
function ( $method_handler ) {
|
||||
return is_array( $method_handler ) && isset( $method_handler['methods'] ) && \WP_REST_Server::READABLE === $method_handler['methods'];
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $handler ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to WP core's `rest_dispatch_request` filter, this allows plugin to override hydrating the request.
|
||||
* Allows backward compatibility with the `rest_dispatch_request` filter by providing the same arguments.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param mixed $hydration_result Result of the hydration. If not null, this will be used as the response.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
* @param string $path Request path matched for the request..
|
||||
* @param array $handler Route handler used for the request.
|
||||
*/
|
||||
$hydration_result = apply_filters( 'woocommerce_hydration_dispatch_request', null, $request, $path, $handler );
|
||||
|
||||
if ( null !== $hydration_result ) {
|
||||
$response = $hydration_result;
|
||||
} else {
|
||||
$response = call_user_func_array( $handler['callback'], array( $request ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to WP core's `rest_request_after_callbacks` filter, this allows to modify the response after it has been generated.
|
||||
* Allows backward compatibility with the `rest_request_after_callbacks` filter by providing the same arguments.
|
||||
*
|
||||
* @since 8.9.0
|
||||
*
|
||||
* @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
|
||||
* Usually a WP_REST_Response or WP_Error.
|
||||
* @param array $handler Route handler used for the request.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
$response = apply_filters( 'woocommerce_hydration_request_after_callbacks', $response, $handler, $request );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspired from WP core's `match_request_to_handler`, this matches a given path from available route regexes.
|
||||
* However, unlike WP core, this does not check against query params, request method etc.
|
||||
*
|
||||
* @param string $path The path to match.
|
||||
* @param array $available_routes Available routes in { $regex1 => $contoller_class1, ... } format.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function match_route_to_handler( $path, $available_routes ) {
|
||||
$matched_route = null;
|
||||
foreach ( $available_routes as $route_path => $controller ) {
|
||||
$match = preg_match( '@^' . $route_path . '$@i', $path );
|
||||
if ( $match ) {
|
||||
$matched_route = $controller;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $matched_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the nonce check temporarily.
|
||||
*/
|
||||
protected function disable_nonce_check() {
|
||||
add_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to disable the nonce check. While we could use `__return_true`, we use a custom named callback so that
|
||||
* we can remove it later without affecting other filters.
|
||||
*/
|
||||
public function disable_nonce_check_callback() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the nonce check.
|
||||
*/
|
||||
protected function restore_nonce_check() {
|
||||
remove_filter( 'woocommerce_store_api_disable_nonce_check', array( $this, 'disable_nonce_check_callback' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache notices before hydrating the API if the customer has a session.
|
||||
*/
|
||||
protected function cache_store_notices() {
|
||||
if ( ! did_action( 'woocommerce_init' ) || null === WC()->session ) {
|
||||
return;
|
||||
}
|
||||
$this->cached_store_notices = WC()->session->get( 'wc_notices', array() );
|
||||
WC()->session->set( 'wc_notices', null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore notices into current session from cache.
|
||||
*/
|
||||
protected function restore_cached_store_notices() {
|
||||
if ( ! did_action( 'woocommerce_init' ) || null === WC()->session ) {
|
||||
return;
|
||||
}
|
||||
WC()->session->set( 'wc_notices', $this->cached_store_notices );
|
||||
$this->cached_store_notices = array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
|
||||
|
||||
/**
|
||||
* Service class for adding new-style Notices to WooCommerce core.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Notices {
|
||||
/**
|
||||
* Holds the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Templates used for notices.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $notice_templates = array(
|
||||
'notices/error.php',
|
||||
'notices/notice.php',
|
||||
'notices/success.php',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Package $package An instance of the package class.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize notice hooks.
|
||||
*/
|
||||
public function init() {
|
||||
add_action(
|
||||
'after_setup_theme',
|
||||
function() {
|
||||
/**
|
||||
* Allow classic theme developers to opt-in to using block notices.
|
||||
*
|
||||
* @since 8.8.0
|
||||
* @param bool $use_block_notices_in_classic_theme Whether to use block notices in classic theme.
|
||||
* @return bool
|
||||
*/
|
||||
if ( wp_is_block_theme() || apply_filters( 'woocommerce_use_block_notices_in_classic_theme', false ) ) {
|
||||
add_filter( 'wc_get_template', [ $this, 'get_notices_template' ], 10, 5 );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_filter( 'woocommerce_kses_notice_allowed_tags', [ $this, 'add_kses_notice_allowed_tags' ] );
|
||||
add_action( 'wp_head', [ $this, 'enqueue_notice_styles' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow SVG icon in notices.
|
||||
*
|
||||
* @param array $allowed_tags Allowed tags.
|
||||
* @return array
|
||||
*/
|
||||
public function add_kses_notice_allowed_tags( $allowed_tags ) {
|
||||
$svg_args = array(
|
||||
'svg' => array(
|
||||
'aria-hidden' => true,
|
||||
'xmlns' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'viewbox' => true,
|
||||
'focusable' => true,
|
||||
),
|
||||
'path' => array(
|
||||
'd' => true,
|
||||
),
|
||||
);
|
||||
return array_merge( $allowed_tags, $svg_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces core notice templates with those from blocks.
|
||||
*
|
||||
* The new notice templates match block components with matching icons and styling. The differences are:
|
||||
* 1. Core has notices for info, success, and error notices, blocks has notices for info, success, error,
|
||||
* warning, and a default notice type.
|
||||
* 2. The block notices use different CSS classes to the core notices. Core uses `woocommerce-message`, `is-info`
|
||||
* and `is-error` classes, blocks uses `wc-block-components-notice-banner is-error`,
|
||||
* `wc-block-components-notice-banner is-info`, and `wc-block-components-notice-banner is-success`.
|
||||
* 3. The markup of the notices is different, with the block notices using SVG icons and a slightly different
|
||||
* structure to accommodate this.
|
||||
*
|
||||
* @param string $template Located template path.
|
||||
* @param string $template_name Template name.
|
||||
* @param array $args Template arguments.
|
||||
* @param string $template_path Template path.
|
||||
* @param string $default_path Default path.
|
||||
* @return string
|
||||
*/
|
||||
public function get_notices_template( $template, $template_name, $args, $template_path, $default_path ) {
|
||||
$directory = get_stylesheet_directory();
|
||||
$file = $directory . '/woocommerce/' . $template_name;
|
||||
if ( file_exists( $file ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
if ( in_array( $template_name, $this->notice_templates, true ) ) {
|
||||
$template = $this->package->get_path( 'templates/block-' . $template_name );
|
||||
wp_enqueue_style( 'wc-blocks-style' );
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all notices with the new block based notices.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_notice_styles() {
|
||||
wp_enqueue_style( 'wc-blocks-style' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||
|
||||
/**
|
||||
* Review the cart/checkout Task
|
||||
*/
|
||||
class ReviewCheckoutTask extends Task {
|
||||
/**
|
||||
* ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_id() {
|
||||
return 'review-checkout-experience';
|
||||
}
|
||||
|
||||
/**
|
||||
* Title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
return __( 'Review your checkout experience', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Time.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_time() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional Info.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_additional_info() {
|
||||
return __( 'Make sure cart and checkout flows are configured correctly for your shoppers.', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Task completion.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_complete() {
|
||||
return $this->is_visited();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store uses blocks on the cart or checkout page.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_cart_block() {
|
||||
$cart_page_id = wc_get_page_id( 'cart' );
|
||||
$has_block_cart = $cart_page_id && ( has_block( 'woocommerce/cart', $cart_page_id ) || has_block( 'woocommerce/classic-shortcode', $cart_page_id ) );
|
||||
|
||||
return $has_block_cart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store uses blocks on the cart or checkout page.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_checkout_block() {
|
||||
$cart_page_id = wc_get_page_id( 'cart' );
|
||||
$has_block_cart = $cart_page_id && ( has_block( 'woocommerce/cart', $cart_page_id ) || has_block( 'woocommerce/classic-shortcode', $cart_page_id ) );
|
||||
|
||||
return $has_block_cart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the store uses blocks on the cart or checkout page.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function has_cart_or_checkout_block() {
|
||||
return $this->has_cart_block() || $this->has_checkout_block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Task visibility.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view() {
|
||||
return $this->has_cart_or_checkout_block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_action_url() {
|
||||
$base_url = wc_current_theme_is_fse_theme() ? 'site-editor.php?postType=page&postId=' : 'post.php?action=edit&post=';
|
||||
$page_id = $this->has_cart_block() ? wc_get_page_id( 'cart' ) : wc_get_page_id( 'checkout' );
|
||||
$focus = $this->has_cart_block() ? 'cart' : 'checkout';
|
||||
|
||||
return admin_url( $base_url . absint( $page_id ) . '&focus=' . $focus . '&canvas=edit' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services\OnboardingTasks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\OnboardingTasks\ReviewCheckoutTask;
|
||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists;
|
||||
|
||||
/**
|
||||
* Onboarding Tasks Controller
|
||||
*/
|
||||
class TasksController {
|
||||
|
||||
/**
|
||||
* Init tasks.
|
||||
*/
|
||||
public function init() {
|
||||
add_action( 'init', [ $this, 'register_tasks' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register tasks.
|
||||
*/
|
||||
public function register_tasks() {
|
||||
TaskLists::add_task(
|
||||
'extended',
|
||||
new ReviewCheckoutTask()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
|
||||
|
||||
if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) {
|
||||
/**
|
||||
* Register a checkout field.
|
||||
*
|
||||
* @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details.
|
||||
* @throws \Exception If field registration fails.
|
||||
*/
|
||||
function woocommerce_register_additional_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
|
||||
|
||||
// Check if `woocommerce_blocks_loaded` ran. If not then the CheckoutFields class will not be available yet.
|
||||
// In that case, re-hook `woocommerce_blocks_loaded` and try running this again.
|
||||
$woocommerce_blocks_loaded_ran = did_action( 'woocommerce_blocks_loaded' );
|
||||
if ( ! $woocommerce_blocks_loaded_ran ) {
|
||||
add_action(
|
||||
'woocommerce_blocks_loaded',
|
||||
function () use ( $options ) {
|
||||
woocommerce_register_additional_checkout_field( $options );
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
$checkout_fields = Package::container()->get( CheckoutFields::class );
|
||||
$result = $checkout_fields->register_checkout_field( $options );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
throw new \Exception( esc_attr( $result->get_error_message() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( '__experimental_woocommerce_blocks_register_checkout_field' ) ) {
|
||||
|
||||
/**
|
||||
* Register a checkout field.
|
||||
*
|
||||
* @param array $options Field arguments. See CheckoutFields::register_checkout_field() for details.
|
||||
* @throws \Exception If field registration fails.
|
||||
* @deprecated 5.6.0 Use woocommerce_register_additional_checkout_field() instead.
|
||||
*/
|
||||
function __experimental_woocommerce_blocks_register_checkout_field( $options ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
|
||||
wc_deprecated_function( __FUNCTION__, '8.9.0', 'woocommerce_register_additional_checkout_field' );
|
||||
woocommerce_register_additional_checkout_field( $options );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( '__internal_woocommerce_blocks_deregister_checkout_field' ) ) {
|
||||
/**
|
||||
* Deregister a checkout field.
|
||||
*
|
||||
* @param string $field_id Field ID.
|
||||
* @throws \Exception If field deregistration fails.
|
||||
* @internal
|
||||
*/
|
||||
function __internal_woocommerce_blocks_deregister_checkout_field( $field_id ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore
|
||||
$checkout_fields = Package::container()->get( CheckoutFields::class );
|
||||
$result = $checkout_fields->deregister_checkout_field( $field_id );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
throw new \Exception( esc_attr( $result->get_error_message() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user