initial commit
This commit is contained in:
95
packages/woocommerce-blocks/src/Assets.php
Normal file
95
packages/woocommerce-blocks/src/Assets.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
|
||||
/**
|
||||
* Assets class.
|
||||
*
|
||||
* @deprecated 5.0.0 This class will be removed in a future release. This has been replaced by AssetsController.
|
||||
* @internal
|
||||
*/
|
||||
class Assets {
|
||||
|
||||
/**
|
||||
* Initialize class features on init.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function init() {
|
||||
_deprecated_function( 'Assets::init', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block scripts & styles.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function register_assets() {
|
||||
_deprecated_function( 'Assets::register_assets', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the vendors style file. We need to do it after the other files
|
||||
* because we need to check if `wp-edit-post` has been enqueued.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function enqueue_scripts() {
|
||||
_deprecated_function( 'Assets::enqueue_scripts', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add body classes.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
* @param array $classes Array of CSS classnames.
|
||||
* @return array Modified array of CSS classnames.
|
||||
*/
|
||||
public static function add_theme_body_class( $classes = [] ) {
|
||||
_deprecated_function( 'Assets::add_theme_body_class', '5.0.0' );
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add theme class to admin body.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
* @param array $classes String with the CSS classnames.
|
||||
* @return array Modified string of CSS classnames.
|
||||
*/
|
||||
public static function add_theme_admin_body_class( $classes = '' ) {
|
||||
_deprecated_function( 'Assets::add_theme_admin_body_class', '5.0.0' );
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a redirect field to the login form so blocks can redirect users after login.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function redirect_to_field() {
|
||||
_deprecated_function( 'Assets::redirect_to_field', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a block script in the frontend.
|
||||
*
|
||||
* @since 2.3.0
|
||||
* @since 2.6.0 Changed $name to $script_name and added $handle argument.
|
||||
* @since 2.9.0 Made it so scripts are not loaded in admin pages.
|
||||
* @deprecated 4.5.0 Block types register the scripts themselves.
|
||||
*
|
||||
* @param string $script_name Name of the script used to identify the file inside build folder.
|
||||
* @param string $handle Optional. Provided if the handle should be different than the script name. `wc-` prefix automatically added.
|
||||
* @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
*/
|
||||
public static function register_block_script( $script_name, $handle = '', $dependencies = [] ) {
|
||||
_deprecated_function( 'register_block_script', '4.5.0' );
|
||||
$asset_api = Package::container()->get( AssetApi::class );
|
||||
$asset_api->register_block_script( $script_name, $handle, $dependencies );
|
||||
}
|
||||
}
|
196
packages/woocommerce-blocks/src/Assets/Api.php
Normal file
196
packages/woocommerce-blocks/src/Assets/Api.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Assets;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Exception;
|
||||
/**
|
||||
* The Api class provides an interface to various asset registration helpers.
|
||||
*
|
||||
* Contains asset api methods
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Api {
|
||||
/**
|
||||
* Stores inline scripts already enqueued.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $inline_scripts = [];
|
||||
|
||||
/**
|
||||
* Reference to the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Constructor for class
|
||||
*
|
||||
* @param Package $package An instance of Package.
|
||||
*/
|
||||
public function __construct( Package $package ) {
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file modified time as a cache buster if we're in dev mode.
|
||||
*
|
||||
* @param string $file Local path to the file (relative to the plugin
|
||||
* directory).
|
||||
* @return string The cache buster value to use for the given file.
|
||||
*/
|
||||
protected function get_file_version( $file ) {
|
||||
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $this->package->get_path() . $file ) ) {
|
||||
return filemtime( $this->package->get_path( trim( $file, '/' ) ) );
|
||||
}
|
||||
return $this->package->get_version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the url to an asset for this plugin.
|
||||
*
|
||||
* @param string $relative_path An optional relative path appended to the
|
||||
* returned url.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_asset_url( $relative_path = '' ) {
|
||||
return $this->package->get_url( $relative_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get src, version and dependencies given a script relative src.
|
||||
*
|
||||
* @param string $relative_src Relative src to the script.
|
||||
* @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
*
|
||||
* @return array src, version and dependencies of the script.
|
||||
*/
|
||||
public function get_script_data( $relative_src, $dependencies = [] ) {
|
||||
$src = '';
|
||||
$version = '1';
|
||||
|
||||
if ( $relative_src ) {
|
||||
$src = $this->get_asset_url( $relative_src );
|
||||
$asset_path = $this->package->get_path(
|
||||
str_replace( '.js', '.asset.php', $relative_src )
|
||||
);
|
||||
|
||||
if ( file_exists( $asset_path ) ) {
|
||||
$asset = require $asset_path;
|
||||
$dependencies = isset( $asset['dependencies'] ) ? array_merge( $asset['dependencies'], $dependencies ) : $dependencies;
|
||||
$version = ! empty( $asset['version'] ) ? $asset['version'] : $this->get_file_version( $relative_src );
|
||||
} else {
|
||||
$version = $this->get_file_version( $relative_src );
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'src' => $src,
|
||||
'version' => $version,
|
||||
'dependencies' => $dependencies,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a script according to `wp_register_script`, adding the correct prefix, and additionally loading translations.
|
||||
*
|
||||
* When creating script assets, the following rules should be followed:
|
||||
* 1. All asset handles should have a `wc-` prefix.
|
||||
* 2. If the asset handle is for a Block (in editor context) use the `-block` suffix.
|
||||
* 3. If the asset handle is for a Block (in frontend context) use the `-block-frontend` suffix.
|
||||
* 4. If the asset is for any other script being consumed or enqueued by the blocks plugin, use the `wc-blocks-` prefix.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @throws Exception If the registered script has a dependency on itself.
|
||||
*
|
||||
* @param string $handle Unique name of the script.
|
||||
* @param string $relative_src Relative url for the script to the path from plugin root.
|
||||
* @param array $dependencies Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
* @param bool $has_i18n Optional. Whether to add a script translation call to this file. Default: true.
|
||||
*/
|
||||
public function register_script( $handle, $relative_src, $dependencies = [], $has_i18n = true ) {
|
||||
$script_data = $this->get_script_data( $relative_src, $dependencies );
|
||||
|
||||
if ( in_array( $handle, $script_data['dependencies'], true ) ) {
|
||||
if ( $this->package->feature()->is_development_environment() ) {
|
||||
$dependencies = array_diff( $script_data['dependencies'], [ $handle ] );
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function() use ( $handle ) {
|
||||
echo '<div class="error"><p>';
|
||||
/* translators: %s file handle name. */
|
||||
printf( esc_html__( 'Script with handle %s had a dependency on itself which has been removed. This is an indicator that your JS code has a circular dependency that can cause bugs.', 'woocommerce' ), esc_html( $handle ) );
|
||||
echo '</p></div>';
|
||||
}
|
||||
);
|
||||
} else {
|
||||
throw new Exception( sprintf( 'Script with handle %s had a dependency on itself. This is an indicator that your JS code has a circular dependency that can cause bugs.', $handle ) );
|
||||
}
|
||||
}
|
||||
|
||||
wp_register_script( $handle, $script_data['src'], apply_filters( 'woocommerce_blocks_register_script_dependencies', $script_data['dependencies'], $handle ), $script_data['version'], true );
|
||||
|
||||
if ( $has_i18n && function_exists( 'wp_set_script_translations' ) ) {
|
||||
wp_set_script_translations( $handle, 'woocommerce', $this->package->get_path( 'languages' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a style according to `wp_register_style`.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @since 2.6.0 Change src to be relative source.
|
||||
*
|
||||
* @param string $handle Name of the stylesheet. Should be unique.
|
||||
* @param string $relative_src Relative source of the stylesheet to the plugin path.
|
||||
* @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
|
||||
* @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like
|
||||
* 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'.
|
||||
*/
|
||||
public function register_style( $handle, $relative_src, $deps = [], $media = 'all' ) {
|
||||
$filename = str_replace( plugins_url( '/', __DIR__ ), '', $relative_src );
|
||||
$src = $this->get_asset_url( $relative_src );
|
||||
$ver = $this->get_file_version( $filename );
|
||||
wp_register_style( $handle, $src, $deps, $ver, $media );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate asset path for loading either legacy builds or
|
||||
* current builds.
|
||||
*
|
||||
* @param string $filename Filename for asset path (without extension).
|
||||
* @param string $type File type (.css or .js).
|
||||
*
|
||||
* @return string The generated path.
|
||||
*/
|
||||
public function get_block_asset_build_path( $filename, $type = 'js' ) {
|
||||
global $wp_version;
|
||||
$suffix = version_compare( $wp_version, '5.3', '>=' )
|
||||
? ''
|
||||
: '-legacy';
|
||||
return "build/$filename$suffix.$type";
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an inline script, once.
|
||||
*
|
||||
* @param string $handle Script handle.
|
||||
* @param string $script Script contents.
|
||||
*/
|
||||
public function add_inline_script( $handle, $script ) {
|
||||
if ( ! empty( $this->inline_scripts[ $handle ] ) && in_array( $script, $this->inline_scripts[ $handle ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_add_inline_script( $handle, $script );
|
||||
|
||||
if ( isset( $this->inline_scripts[ $handle ] ) ) {
|
||||
$this->inline_scripts[ $handle ][] = $script;
|
||||
} else {
|
||||
$this->inline_scripts[ $handle ] = array( $script );
|
||||
}
|
||||
}
|
||||
}
|
386
packages/woocommerce-blocks/src/Assets/AssetDataRegistry.php
Normal file
386
packages/woocommerce-blocks/src/Assets/AssetDataRegistry.php
Normal file
@ -0,0 +1,386 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Assets;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class instance for registering data used on the current view session by
|
||||
* assets.
|
||||
*
|
||||
* Holds data registered for output on the current view session when
|
||||
* `wc-settings` is enqueued( directly or via dependency )
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class AssetDataRegistry {
|
||||
/**
|
||||
* Contains registered data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* Lazy data is an array of closures that will be invoked just before
|
||||
* asset data is generated for the enqueued script.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $lazy_data = [];
|
||||
|
||||
/**
|
||||
* Asset handle for registered data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $handle = 'wc-settings';
|
||||
|
||||
/**
|
||||
* Asset API interface for various asset registration.
|
||||
*
|
||||
* @var API
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Api $asset_api Asset API interface for various asset registration.
|
||||
*/
|
||||
public function __construct( Api $asset_api ) {
|
||||
$this->api = $asset_api;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WP asset registration for enqueueing asset data.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'init', array( $this, 'register_data_script' ) );
|
||||
add_action( 'wp_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 );
|
||||
add_action( 'admin_print_footer_scripts', array( $this, 'enqueue_asset_data' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes core data via the wcSettings global. This data is shared throughout the client.
|
||||
*
|
||||
* Settings that are used by various components or multiple blocks should be added here. Note, that settings here are
|
||||
* global so be sure not to add anything heavy if possible.
|
||||
*
|
||||
* @return array An array containing core data.
|
||||
*/
|
||||
protected function get_core_data() {
|
||||
return [
|
||||
'adminUrl' => admin_url(),
|
||||
'countries' => WC()->countries->get_countries(),
|
||||
'currency' => $this->get_currency_data(),
|
||||
'currentUserIsAdmin' => current_user_can( 'manage_woocommerce' ),
|
||||
'homeUrl' => esc_url( home_url( '/' ) ),
|
||||
'locale' => $this->get_locale_data(),
|
||||
'orderStatuses' => $this->get_order_statuses(),
|
||||
'placeholderImgSrc' => wc_placeholder_img_src(),
|
||||
'siteTitle' => get_bloginfo( 'name' ),
|
||||
'storePages' => $this->get_store_pages(),
|
||||
'wcAssetUrl' => plugins_url( 'assets/', WC_PLUGIN_FILE ),
|
||||
'wcVersion' => defined( 'WC_VERSION' ) ? WC_VERSION : '',
|
||||
'wpLoginUrl' => wp_login_url(),
|
||||
'wpVersion' => get_bloginfo( 'version' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get currency data to include in settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_currency_data() {
|
||||
$currency = get_woocommerce_currency();
|
||||
|
||||
return [
|
||||
'code' => $currency,
|
||||
'precision' => wc_get_price_decimals(),
|
||||
'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $currency ) ),
|
||||
'symbolPosition' => get_option( 'woocommerce_currency_pos' ),
|
||||
'decimalSeparator' => wc_get_price_decimal_separator(),
|
||||
'thousandSeparator' => wc_get_price_thousand_separator(),
|
||||
'priceFormat' => html_entity_decode( get_woocommerce_price_format() ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale data to include in settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_locale_data() {
|
||||
global $wp_locale;
|
||||
|
||||
return [
|
||||
'siteLocale' => get_locale(),
|
||||
'userLocale' => get_user_locale(),
|
||||
'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get store pages to include in settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_store_pages() {
|
||||
return array_map(
|
||||
[ $this, 'format_page_resource' ],
|
||||
[
|
||||
'myaccount' => wc_get_page_id( 'myaccount' ),
|
||||
'shop' => wc_get_page_id( 'shop' ),
|
||||
'cart' => wc_get_page_id( 'cart' ),
|
||||
'checkout' => wc_get_page_id( 'checkout' ),
|
||||
'privacy' => wc_privacy_policy_page_id(),
|
||||
'terms' => wc_terms_and_conditions_page_id(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a page object into a standard array of data.
|
||||
*
|
||||
* @param WP_Post|int $page Page object or ID.
|
||||
* @return array
|
||||
*/
|
||||
protected function format_page_resource( $page ) {
|
||||
if ( is_numeric( $page ) && $page > 0 ) {
|
||||
$page = get_post( $page );
|
||||
}
|
||||
if ( ! is_a( $page, '\WP_Post' ) || 'publish' !== $page->post_status ) {
|
||||
return [
|
||||
'id' => 0,
|
||||
'title' => '',
|
||||
'permalink' => false,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'id' => $page->ID,
|
||||
'title' => $page->post_title,
|
||||
'permalink' => get_permalink( $page->ID ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns block-related data for enqueued wc-settings script.
|
||||
* Format order statuses by removing a leading 'wc-' if present.
|
||||
*
|
||||
* @return array formatted statuses.
|
||||
*/
|
||||
protected function get_order_statuses() {
|
||||
$formatted_statuses = array();
|
||||
foreach ( wc_get_order_statuses() as $key => $value ) {
|
||||
$formatted_key = preg_replace( '/^wc-/', '', $key );
|
||||
$formatted_statuses[ $formatted_key ] = $value;
|
||||
}
|
||||
return $formatted_statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for on demand initialization of asset data and registering it with
|
||||
* the internal data registry.
|
||||
*
|
||||
* Note: core data will overwrite any externally registered data via the api.
|
||||
*/
|
||||
protected function initialize_core_data() {
|
||||
/**
|
||||
* Low level hook for registration of new data late in the cycle. This is deprecated.
|
||||
* Instead, use the data api:
|
||||
* Automattic\WooCommerce\Blocks\Package::container()
|
||||
* ->get( Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::class )
|
||||
* ->add( $key, $value )
|
||||
*/
|
||||
$settings = apply_filters( 'woocommerce_shared_settings', $this->data );
|
||||
|
||||
// Surface a deprecation warning in the error console.
|
||||
if ( has_filter( 'woocommerce_shared_settings' ) ) {
|
||||
$error_handle = 'deprecated-shared-settings-error';
|
||||
$error_message = '`woocommerce_shared_settings` filter in Blocks is deprecated. See https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/contributors/block-assets.md';
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
wp_register_script( $error_handle, '' );
|
||||
wp_enqueue_script( $error_handle );
|
||||
wp_add_inline_script(
|
||||
$error_handle,
|
||||
sprintf( 'console.warn( "%s" );', $error_message )
|
||||
);
|
||||
}
|
||||
|
||||
// note this WILL wipe any data already registered to these keys because they are protected.
|
||||
$this->data = array_replace_recursive( $settings, $this->get_core_data() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through each registered lazy data callback and adds the returned
|
||||
* value to the data array.
|
||||
*
|
||||
* This method is executed right before preparing the data for printing to
|
||||
* the rendered screen.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function execute_lazy_data() {
|
||||
foreach ( $this->lazy_data as $key => $callback ) {
|
||||
$this->data[ $key ] = $callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes private registered data to child classes.
|
||||
*
|
||||
* @return array The registered data on the private data property
|
||||
*/
|
||||
protected function get() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows checking whether a key exists.
|
||||
*
|
||||
* @param string $key The key to check if exists.
|
||||
* @return bool Whether the key exists in the current data registry.
|
||||
*/
|
||||
public function exists( $key ) {
|
||||
return array_key_exists( $key, $this->data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for adding data to the registry.
|
||||
*
|
||||
* You can only register data that is not already in the registry identified by the given key. If there is a
|
||||
* duplicate found, unless $ignore_duplicates is true, an exception will be thrown.
|
||||
*
|
||||
* @param string $key The key used to reference the data being registered.
|
||||
* @param mixed $data If not a function, registered to the registry as is. If a function, then the
|
||||
* callback is invoked right before output to the screen.
|
||||
* @param boolean $check_key_exists If set to true, duplicate data will be ignored if the key exists.
|
||||
* If false, duplicate data will cause an exception.
|
||||
*
|
||||
* @throws InvalidArgumentException Only throws when site is in debug mode. Always logs the error.
|
||||
*/
|
||||
public function add( $key, $data, $check_key_exists = false ) {
|
||||
if ( $check_key_exists && $this->exists( $key ) ) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->add_data( $key, $data );
|
||||
} catch ( Exception $e ) {
|
||||
if ( $this->debug() ) {
|
||||
// bubble up.
|
||||
throw $e;
|
||||
}
|
||||
wc_caught_exception( $e, __METHOD__, [ $key, $data ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate from API.
|
||||
*
|
||||
* @param string $path REST API path to preload.
|
||||
*/
|
||||
public function hydrate_api_request( $path ) {
|
||||
if ( ! isset( $this->data['preloadedApiRequests'] ) ) {
|
||||
$this->data['preloadedApiRequests'] = [];
|
||||
}
|
||||
if ( ! isset( $this->data['preloadedApiRequests'][ $path ] ) ) {
|
||||
$this->data['preloadedApiRequests'] = rest_preload_api_request( $this->data['preloadedApiRequests'], $path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a page permalink to the data registry.
|
||||
*
|
||||
* @param integer $page_id Page ID to add to the registry.
|
||||
*/
|
||||
public function register_page_id( $page_id ) {
|
||||
$permalink = $page_id ? get_permalink( $page_id ) : false;
|
||||
|
||||
if ( $permalink ) {
|
||||
$this->data[ 'page-' . $page_id ] = $permalink;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for registering the data script via WordPress API.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_data_script() {
|
||||
$this->api->register_script(
|
||||
$this->handle,
|
||||
'build/wc-settings.js',
|
||||
[],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for enqueuing asset data via the WP api.
|
||||
*
|
||||
* Note: while this is hooked into print/admin_print_scripts, it still only
|
||||
* happens if the script attached to `wc-settings` handle is enqueued. This
|
||||
* is done to allow for any potentially expensive data generation to only
|
||||
* happen for routes that need it.
|
||||
*/
|
||||
public function enqueue_asset_data() {
|
||||
if ( wp_script_is( $this->handle, 'enqueued' ) ) {
|
||||
$this->initialize_core_data();
|
||||
$this->execute_lazy_data();
|
||||
$data = rawurlencode( wp_json_encode( $this->data ) );
|
||||
wp_add_inline_script(
|
||||
$this->handle,
|
||||
"var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '"
|
||||
. esc_js( $data )
|
||||
. "' ) );",
|
||||
'before'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See self::add() for docs.
|
||||
*
|
||||
* @param string $key Key for the data.
|
||||
* @param mixed $data Value for the data.
|
||||
*
|
||||
* @throws InvalidArgumentException If key is not a string or already
|
||||
* exists in internal data cache.
|
||||
*/
|
||||
protected function add_data( $key, $data ) {
|
||||
if ( ! is_string( $key ) ) {
|
||||
if ( $this->debug() ) {
|
||||
throw new InvalidArgumentException(
|
||||
'Key for the data being registered must be a string'
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( isset( $this->data[ $key ] ) ) {
|
||||
if ( $this->debug() ) {
|
||||
throw new InvalidArgumentException(
|
||||
'Overriding existing data with an already registered key is not allowed'
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( \is_callable( $data ) ) {
|
||||
$this->lazy_data[ $key ] = $data;
|
||||
return;
|
||||
}
|
||||
$this->data[ $key ] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes whether the current site is in debug mode or not.
|
||||
*
|
||||
* @return boolean True means the site is in debug mode.
|
||||
*/
|
||||
protected function debug() {
|
||||
return defined( 'WP_DEBUG' ) && WP_DEBUG;
|
||||
}
|
||||
}
|
152
packages/woocommerce-blocks/src/AssetsController.php
Normal file
152
packages/woocommerce-blocks/src/AssetsController.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry as AssetDataRegistry;
|
||||
|
||||
/**
|
||||
* AssetsController class.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @internal
|
||||
*/
|
||||
final class AssetsController {
|
||||
|
||||
/**
|
||||
* Asset API interface for various asset registration.
|
||||
*
|
||||
* @var AssetApi
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Asset API interface for various asset registration.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api ) {
|
||||
$this->api = $asset_api;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class features.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'init', array( $this, 'register_assets' ) );
|
||||
add_action( 'body_class', array( $this, 'add_theme_body_class' ), 1 );
|
||||
add_action( 'admin_body_class', array( $this, 'add_theme_body_class' ), 1 );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'update_block_style_dependencies' ), 20 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register block scripts & styles.
|
||||
*/
|
||||
public function register_assets() {
|
||||
$this->register_style( 'wc-blocks-vendors-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-vendors-style', 'css' ), __DIR__ ) );
|
||||
$this->register_style( 'wc-blocks-editor-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-editor-style', 'css' ), __DIR__ ), [ 'wp-edit-blocks' ], 'all', true );
|
||||
$this->register_style( 'wc-blocks-style', plugins_url( $this->api->get_block_asset_build_path( 'wc-blocks-style', 'css' ), __DIR__ ), [ 'wc-blocks-vendors-style' ], 'all', true );
|
||||
|
||||
$this->api->register_script( 'wc-blocks-middleware', 'build/wc-blocks-middleware.js', [], false );
|
||||
$this->api->register_script( 'wc-blocks-data-store', 'build/wc-blocks-data.js', [ 'wc-blocks-middleware' ] );
|
||||
$this->api->register_script( 'wc-blocks-vendors', $this->api->get_block_asset_build_path( 'wc-blocks-vendors' ), [], false );
|
||||
$this->api->register_script( 'wc-blocks-registry', 'build/wc-blocks-registry.js', [], false );
|
||||
$this->api->register_script( 'wc-blocks', $this->api->get_block_asset_build_path( 'wc-blocks' ), [ 'wc-blocks-vendors' ], false );
|
||||
$this->api->register_script( 'wc-blocks-shared-context', 'build/wc-blocks-shared-context.js', [] );
|
||||
$this->api->register_script( 'wc-blocks-shared-hocs', 'build/wc-blocks-shared-hocs.js', [], false );
|
||||
|
||||
// The price package is shared externally so has no blocks prefix.
|
||||
$this->api->register_script( 'wc-price-format', 'build/price-format.js', [], false );
|
||||
|
||||
if ( Package::feature()->is_feature_plugin_build() ) {
|
||||
$this->api->register_script( 'wc-blocks-checkout', 'build/blocks-checkout.js', [] );
|
||||
}
|
||||
|
||||
wp_add_inline_script(
|
||||
'wc-blocks-middleware',
|
||||
"
|
||||
var wcBlocksMiddlewareConfig = {
|
||||
storeApiNonce: '" . esc_js( wp_create_nonce( 'wc_store_api' ) ) . "',
|
||||
wcStoreApiNonceTimestamp: '" . esc_js( time() ) . "'
|
||||
};
|
||||
",
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add body classes to the frontend and within admin.
|
||||
*
|
||||
* @param string|array $classes Array or string of CSS classnames.
|
||||
* @return string|array Modified classnames.
|
||||
*/
|
||||
public function add_theme_body_class( $classes ) {
|
||||
$class = 'theme-' . get_template();
|
||||
|
||||
if ( is_array( $classes ) ) {
|
||||
$classes[] = $class;
|
||||
} else {
|
||||
$classes .= ' ' . $class . ' ';
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file modified time as a cache buster if we're in dev mode.
|
||||
*
|
||||
* @param string $file Local path to the file.
|
||||
* @return string The cache buster value to use for the given file.
|
||||
*/
|
||||
protected function get_file_version( $file ) {
|
||||
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( \Automattic\WooCommerce\Blocks\Package::get_path() . $file ) ) {
|
||||
return filemtime( \Automattic\WooCommerce\Blocks\Package::get_path() . $file );
|
||||
}
|
||||
return \Automattic\WooCommerce\Blocks\Package::get_version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a style according to `wp_register_style`.
|
||||
*
|
||||
* @param string $handle Name of the stylesheet. Should be unique.
|
||||
* @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
|
||||
* @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
|
||||
* @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'. Accepts media types like
|
||||
* 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'.
|
||||
* @param boolean $rtl Optional. Whether or not to register RTL styles.
|
||||
*/
|
||||
protected function register_style( $handle, $src, $deps = [], $media = 'all', $rtl = false ) {
|
||||
$filename = str_replace( plugins_url( '/', __DIR__ ), '', $src );
|
||||
$ver = self::get_file_version( $filename );
|
||||
|
||||
wp_register_style( $handle, $src, $deps, $ver, $media );
|
||||
|
||||
if ( $rtl ) {
|
||||
wp_style_add_data( $handle, 'rtl', 'replace' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update block style dependencies after they have been registered.
|
||||
*/
|
||||
public function update_block_style_dependencies() {
|
||||
$wp_styles = wp_styles();
|
||||
$style = $wp_styles->query( 'wc-blocks-style', 'registered' );
|
||||
|
||||
if ( ! $style ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In WC < 5.5, `woocommerce-general` is not registered in block editor
|
||||
// screens, so we don't add it as a dependency if it's not registered.
|
||||
// In WC >= 5.5, `woocommerce-general` is registered on `admin_enqueue_scripts`,
|
||||
// so we need to check if it's registered here instead of on `init`.
|
||||
if (
|
||||
wp_style_is( 'woocommerce-general', 'registered' ) &&
|
||||
! in_array( 'woocommerce-general', $style->deps, true )
|
||||
) {
|
||||
$style->deps[] = 'woocommerce-general';
|
||||
}
|
||||
}
|
||||
}
|
437
packages/woocommerce-blocks/src/BlockTypes/AbstractBlock.php
Normal file
437
packages/woocommerce-blocks/src/BlockTypes/AbstractBlock.php
Normal file
@ -0,0 +1,437 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use WP_Block;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
use Automattic\WooCommerce\Blocks\RestApi;
|
||||
|
||||
/**
|
||||
* AbstractBlock class.
|
||||
*/
|
||||
abstract class AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'woocommerce';
|
||||
|
||||
/**
|
||||
* Block name within this namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = '';
|
||||
|
||||
/**
|
||||
* Tracks if assets have been enqueued.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $enqueued_assets = false;
|
||||
|
||||
/**
|
||||
* Instance of the asset API.
|
||||
*
|
||||
* @var AssetApi
|
||||
*/
|
||||
protected $asset_api;
|
||||
|
||||
/**
|
||||
* Instance of the asset data registry.
|
||||
*
|
||||
* @var AssetDataRegistry
|
||||
*/
|
||||
protected $asset_data_registry;
|
||||
|
||||
/**
|
||||
* Instance of the integration registry.
|
||||
*
|
||||
* @var IntegrationRegistry
|
||||
*/
|
||||
protected $integration_registry;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Instance of the asset API.
|
||||
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
|
||||
* @param IntegrationRegistry $integration_registry Instance of the integration registry.
|
||||
* @param string $block_name Optionally set block name during construct.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry, IntegrationRegistry $integration_registry, $block_name = '' ) {
|
||||
$this->asset_api = $asset_api;
|
||||
$this->asset_data_registry = $asset_data_registry;
|
||||
$this->integration_registry = $integration_registry;
|
||||
$this->block_name = $block_name ? $block_name : $this->block_name;
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default render_callback for all blocks. This will ensure assets are enqueued just in time, then render
|
||||
* the block (if applicable).
|
||||
*
|
||||
* @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
public function render_callback( $attributes = [], $content = '' ) {
|
||||
$render_callback_attributes = $this->parse_render_callback_attributes( $attributes );
|
||||
if ( ! is_admin() ) {
|
||||
$this->enqueue_assets( $render_callback_attributes );
|
||||
}
|
||||
return $this->render( $render_callback_attributes, $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue assets used for rendering the block in editor context.
|
||||
*
|
||||
* This is needed if a block is not yet within the post content--`render` and `enqueue_assets` may not have ran.
|
||||
*/
|
||||
public function enqueue_editor_assets() {
|
||||
if ( $this->enqueued_assets ) {
|
||||
return;
|
||||
}
|
||||
$this->enqueue_data();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this block type.
|
||||
*
|
||||
* - Hook into WP lifecycle.
|
||||
* - Register the block with WordPress.
|
||||
*/
|
||||
protected function initialize() {
|
||||
if ( empty( $this->block_name ) ) {
|
||||
_doing_it_wrong( __METHOD__, esc_html( __( 'Block name is required.', 'woocommerce' ) ), '4.5.0' );
|
||||
return false;
|
||||
}
|
||||
$this->integration_registry->initialize( $this->block_name . '_block' );
|
||||
$this->register_block_type_assets();
|
||||
$this->register_block_type();
|
||||
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register script and style assets for the block type before it is registered.
|
||||
*
|
||||
* This registers the scripts; it does not enqueue them.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
if ( null !== $this->get_block_type_editor_script() ) {
|
||||
$data = $this->asset_api->get_script_data( $this->get_block_type_editor_script( 'path' ) );
|
||||
$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
|
||||
|
||||
$this->asset_api->register_script(
|
||||
$this->get_block_type_editor_script( 'handle' ),
|
||||
$this->get_block_type_editor_script( 'path' ),
|
||||
array_merge(
|
||||
$this->get_block_type_editor_script( 'dependencies' ),
|
||||
$this->integration_registry->get_all_registered_editor_script_handles()
|
||||
),
|
||||
$has_i18n
|
||||
);
|
||||
}
|
||||
if ( null !== $this->get_block_type_script() ) {
|
||||
$data = $this->asset_api->get_script_data( $this->get_block_type_script( 'path' ) );
|
||||
$has_i18n = in_array( 'wp-i18n', $data['dependencies'], true );
|
||||
|
||||
$this->asset_api->register_script(
|
||||
$this->get_block_type_script( 'handle' ),
|
||||
$this->get_block_type_script( 'path' ),
|
||||
array_merge(
|
||||
$this->get_block_type_script( 'dependencies' ),
|
||||
$this->integration_registry->get_all_registered_script_handles()
|
||||
),
|
||||
$has_i18n
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects Chunk Translations into the page so translations work for lazy loaded components.
|
||||
*
|
||||
* The chunk names are defined when creating lazy loaded components using webpackChunkName.
|
||||
*
|
||||
* @param string[] $chunks Array of chunk names.
|
||||
*/
|
||||
protected function register_chunk_translations( $chunks ) {
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$handle = 'wc-blocks-' . $chunk . '-chunk';
|
||||
$this->asset_api->register_script( $handle, $this->asset_api->get_block_asset_build_path( $chunk ), [], true );
|
||||
wp_add_inline_script(
|
||||
$this->get_block_type_script( 'handle' ),
|
||||
wp_scripts()->print_translations( $handle, false ),
|
||||
'before'
|
||||
);
|
||||
wp_deregister_script( $handle );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the block type with WordPress.
|
||||
*/
|
||||
protected function register_block_type() {
|
||||
register_block_type(
|
||||
$this->get_block_type(),
|
||||
array(
|
||||
'render_callback' => $this->get_block_type_render_callback(),
|
||||
'editor_script' => $this->get_block_type_editor_script( 'handle' ),
|
||||
'editor_style' => $this->get_block_type_editor_style(),
|
||||
'style' => $this->get_block_type_style(),
|
||||
'attributes' => $this->get_block_type_attributes(),
|
||||
'supports' => $this->get_block_type_supports(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_block_type() {
|
||||
return $this->namespace . '/' . $this->block_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the render callback for this block type.
|
||||
*
|
||||
* Dynamic blocks should return a callback, for example, `return [ $this, 'render' ];`
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return callable|null;
|
||||
*/
|
||||
protected function get_block_type_render_callback() {
|
||||
return [ $this, 'render_callback' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor script data for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor style handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get_block_type_editor_style() {
|
||||
return 'wc-blocks-editor-style';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return string|null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return 'wc-blocks-style';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supports array for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @return string;
|
||||
*/
|
||||
protected function get_block_type_supports() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array;
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses block attributes from the render_callback.
|
||||
*
|
||||
* @param array|WP_Block $attributes Block attributes, or an instance of a WP_Block. Defaults to an empty array.
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_render_callback_attributes( $attributes ) {
|
||||
return is_a( $attributes, 'WP_Block' ) ? $attributes->attributes : $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block. Extended by children.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @internal This prevents the block script being enqueued on all pages. It is only enqueued as needed. Note that
|
||||
* we intentionally do not pass 'script' to register_block_type.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
if ( $this->enqueued_assets ) {
|
||||
return;
|
||||
}
|
||||
$this->enqueue_data( $attributes );
|
||||
$this->enqueue_scripts( $attributes );
|
||||
$this->enqueued_assets = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects block attributes into the block.
|
||||
*
|
||||
* @param string $content HTML content to inject into.
|
||||
* @param array $attributes Key value pairs of attributes.
|
||||
* @return string Rendered block with data attributes.
|
||||
*/
|
||||
protected function inject_html_data_attributes( $content, array $attributes ) {
|
||||
return preg_replace( '/<div /', '<div ' . $this->get_html_data_attributes( $attributes ) . ' ', $content, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts block attributes to HTML data attributes.
|
||||
*
|
||||
* @param array $attributes Key value pairs of attributes.
|
||||
* @return string Rendered HTML attributes.
|
||||
*/
|
||||
protected function get_html_data_attributes( array $attributes ) {
|
||||
$data = [];
|
||||
|
||||
foreach ( $attributes as $key => $value ) {
|
||||
if ( is_bool( $value ) ) {
|
||||
$value = $value ? 'true' : 'false';
|
||||
}
|
||||
if ( ! is_scalar( $value ) ) {
|
||||
$value = wp_json_encode( $value );
|
||||
}
|
||||
$data[] = 'data-' . esc_attr( strtolower( preg_replace( '/(?<!\ )[A-Z]/', '-$0', $key ) ) ) . '="' . esc_attr( $value ) . '"';
|
||||
}
|
||||
|
||||
return implode( ' ', $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
$registered_script_data = $this->integration_registry->get_all_registered_script_data();
|
||||
|
||||
foreach ( $registered_script_data as $asset_data_key => $asset_data_value ) {
|
||||
if ( ! $this->asset_data_registry->exists( $asset_data_key ) ) {
|
||||
$this->asset_data_registry->add( $asset_data_key, $asset_data_value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $this->asset_data_registry->exists( 'wcBlocksConfig' ) ) {
|
||||
$this->asset_data_registry->add(
|
||||
'wcBlocksConfig',
|
||||
[
|
||||
'buildPhase' => Package::feature()->get_flag(),
|
||||
'pluginUrl' => plugins_url( '/', dirname( __DIR__ ) ),
|
||||
'productCount' => array_sum( (array) wp_count_posts( 'product' ) ),
|
||||
'restApiRoutes' => [
|
||||
'/wc/store' => array_keys( Package::container()->get( RestApi::class )->get_routes_from_namespace( 'wc/store' ) ),
|
||||
],
|
||||
'defaultAvatar' => get_avatar_url( 0, [ 'force_default' => true ] ),
|
||||
|
||||
/*
|
||||
* translators: If your word count is based on single characters (e.g. East Asian characters),
|
||||
* enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
|
||||
* Do not translate into your own language.
|
||||
*/
|
||||
'wordCountType' => _x( 'words', 'Word count type. Do not translate!', 'woocommerce' ),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register/enqueue scripts used for this block on the frontend, during render.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
*/
|
||||
protected function enqueue_scripts( array $attributes = [] ) {
|
||||
if ( null !== $this->get_block_type_script() ) {
|
||||
wp_enqueue_script( $this->get_block_type_script( 'handle' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Script to append the correct sizing class to a block skeleton.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_skeleton_inline_script() {
|
||||
return "<script>
|
||||
var containers = document.querySelectorAll( 'div.wc-block-skeleton' );
|
||||
|
||||
if ( containers.length ) {
|
||||
Array.prototype.forEach.call( containers, function( el, i ) {
|
||||
var w = el.offsetWidth;
|
||||
var classname = '';
|
||||
|
||||
if ( w > 700 )
|
||||
classname = 'is-large';
|
||||
else if ( w > 520 )
|
||||
classname = 'is-medium';
|
||||
else if ( w > 400 )
|
||||
classname = 'is-small';
|
||||
else
|
||||
classname = 'is-mobile';
|
||||
|
||||
if ( ! el.classList.contains( classname ) ) {
|
||||
el.classList.add( classname );
|
||||
}
|
||||
|
||||
el.classList.remove( 'hidden' );
|
||||
} );
|
||||
}
|
||||
</script>";
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AbstractDynamicBlock class.
|
||||
*/
|
||||
abstract class AbstractDynamicBlock extends AbstractBlock {
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the alignment property.
|
||||
*
|
||||
* @return array Property definition for align.
|
||||
*/
|
||||
protected function get_schema_align() {
|
||||
return array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'left', 'center', 'right', 'wide', 'full' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for a list of IDs.
|
||||
*
|
||||
* @return array Property definition for a list of numeric ids.
|
||||
*/
|
||||
protected function get_schema_list_ids() {
|
||||
return array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'number',
|
||||
),
|
||||
'default' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for a boolean value.
|
||||
*
|
||||
* @param string $default The default value.
|
||||
* @return array Property definition.
|
||||
*/
|
||||
protected function get_schema_boolean( $default = true ) {
|
||||
return array(
|
||||
'type' => 'boolean',
|
||||
'default' => $default,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for a numeric value.
|
||||
*
|
||||
* @param string $default The default value.
|
||||
* @return array Property definition.
|
||||
*/
|
||||
protected function get_schema_number( $default ) {
|
||||
return array(
|
||||
'type' => 'number',
|
||||
'default' => $default,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for a string value.
|
||||
*
|
||||
* @param string $default The default value.
|
||||
* @return array Property definition.
|
||||
*/
|
||||
protected function get_schema_string( $default = '' ) {
|
||||
return array(
|
||||
'type' => 'string',
|
||||
'default' => $default,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,520 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlocksWpQuery;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\SchemaController;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
|
||||
/**
|
||||
* AbstractProductGrid class.
|
||||
*/
|
||||
abstract class AbstractProductGrid extends AbstractDynamicBlock {
|
||||
|
||||
/**
|
||||
* Attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes = array();
|
||||
|
||||
/**
|
||||
* InnerBlocks content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content = '';
|
||||
|
||||
/**
|
||||
* Query args.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $query_args = array();
|
||||
|
||||
/**
|
||||
* Get a set of attributes shared across most of the grid blocks.
|
||||
*
|
||||
* @return array List of block attributes with type and defaults.
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array(
|
||||
'className' => $this->get_schema_string(),
|
||||
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
|
||||
'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
|
||||
'categories' => $this->get_schema_list_ids(),
|
||||
'catOperator' => array(
|
||||
'type' => 'string',
|
||||
'default' => 'any',
|
||||
),
|
||||
'contentVisibility' => $this->get_schema_content_visibility(),
|
||||
'align' => $this->get_schema_align(),
|
||||
'alignButtons' => $this->get_schema_boolean( false ),
|
||||
'isPreview' => $this->get_schema_boolean( false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Include and render the dynamic block.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param string $content Block content. Default empty string.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes = array(), $content = '' ) {
|
||||
$this->attributes = $this->parse_attributes( $attributes );
|
||||
$this->content = $content;
|
||||
$this->query_args = $this->parse_query_args();
|
||||
$products = array_filter( array_map( 'wc_get_product', $this->get_products() ) );
|
||||
|
||||
if ( ! $products ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Product List Render event.
|
||||
*
|
||||
* Fires a WP Hook named `experimental__woocommerce_blocks-product-list-render` on render so that the client
|
||||
* can add event handling when certain products are displayed. This can be used by tracking extensions such
|
||||
* as Google Analytics to track impressions.
|
||||
*
|
||||
* Provides the list of product data (shaped like the Store API responses) and the block name.
|
||||
*/
|
||||
$this->asset_api->add_inline_script(
|
||||
'wp-hooks',
|
||||
'
|
||||
window.addEventListener( "DOMContentLoaded", () => {
|
||||
wp.hooks.doAction(
|
||||
"experimental__woocommerce_blocks-product-list-render",
|
||||
{
|
||||
products: JSON.parse( decodeURIComponent( "' . esc_js(
|
||||
rawurlencode(
|
||||
wp_json_encode(
|
||||
array_map(
|
||||
[ Package::container()->get( SchemaController::class )->get( 'product' ), 'get_item_response' ],
|
||||
$products
|
||||
)
|
||||
)
|
||||
)
|
||||
) . '" ) ),
|
||||
listName: "' . esc_js( $this->block_name ) . '"
|
||||
}
|
||||
);
|
||||
} );
|
||||
',
|
||||
'after'
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div class="%s"><ul class="wc-block-grid__products">%s</ul></div>',
|
||||
esc_attr( $this->get_container_classes() ),
|
||||
implode( '', array_map( array( $this, 'render_product' ), $products ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the contentVisibility attribute
|
||||
*
|
||||
* @return array List of block attributes with type and defaults.
|
||||
*/
|
||||
protected function get_schema_content_visibility() {
|
||||
return array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'title' => $this->get_schema_boolean( true ),
|
||||
'price' => $this->get_schema_boolean( true ),
|
||||
'rating' => $this->get_schema_boolean( true ),
|
||||
'button' => $this->get_schema_boolean( true ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema for the orderby attribute.
|
||||
*
|
||||
* @return array Property definition of `orderby` attribute.
|
||||
*/
|
||||
protected function get_schema_orderby() {
|
||||
return array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'date', 'popularity', 'price_asc', 'price_desc', 'rating', 'title', 'menu_order' ),
|
||||
'default' => 'date',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block's attributes.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return array Block attributes merged with defaults.
|
||||
*/
|
||||
protected function parse_attributes( $attributes ) {
|
||||
// These should match what's set in JS `registerBlockType`.
|
||||
$defaults = array(
|
||||
'columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ),
|
||||
'rows' => wc_get_theme_support( 'product_blocks::default_rows', 3 ),
|
||||
'alignButtons' => false,
|
||||
'categories' => array(),
|
||||
'catOperator' => 'any',
|
||||
'contentVisibility' => array(
|
||||
'title' => true,
|
||||
'price' => true,
|
||||
'rating' => true,
|
||||
'button' => true,
|
||||
),
|
||||
);
|
||||
|
||||
return wp_parse_args( $attributes, $defaults );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query args.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse_query_args() {
|
||||
$query_args = array(
|
||||
'post_type' => 'product',
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => false,
|
||||
'orderby' => '',
|
||||
'order' => '',
|
||||
'meta_query' => WC()->query->get_meta_query(), // phpcs:ignore WordPress.DB.SlowDBQuery
|
||||
'tax_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery
|
||||
'posts_per_page' => $this->get_products_limit(),
|
||||
);
|
||||
|
||||
$this->set_block_query_args( $query_args );
|
||||
$this->set_ordering_query_args( $query_args );
|
||||
$this->set_categories_query_args( $query_args );
|
||||
$this->set_visibility_query_args( $query_args );
|
||||
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query args.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_ordering_query_args( &$query_args ) {
|
||||
if ( isset( $this->attributes['orderby'] ) ) {
|
||||
if ( 'price_desc' === $this->attributes['orderby'] ) {
|
||||
$query_args['orderby'] = 'price';
|
||||
$query_args['order'] = 'DESC';
|
||||
} elseif ( 'price_asc' === $this->attributes['orderby'] ) {
|
||||
$query_args['orderby'] = 'price';
|
||||
$query_args['order'] = 'ASC';
|
||||
} elseif ( 'date' === $this->attributes['orderby'] ) {
|
||||
$query_args['orderby'] = 'date';
|
||||
$query_args['order'] = 'DESC';
|
||||
} else {
|
||||
$query_args['orderby'] = $this->attributes['orderby'];
|
||||
}
|
||||
}
|
||||
|
||||
$query_args = array_merge(
|
||||
$query_args,
|
||||
WC()->query->get_catalog_ordering_args( $query_args['orderby'], $query_args['order'] )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
abstract protected function set_block_query_args( &$query_args );
|
||||
|
||||
/**
|
||||
* Set categories query args.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_categories_query_args( &$query_args ) {
|
||||
if ( ! empty( $this->attributes['categories'] ) ) {
|
||||
$categories = array_map( 'absint', $this->attributes['categories'] );
|
||||
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => 'product_cat',
|
||||
'terms' => $categories,
|
||||
'field' => 'term_id',
|
||||
'operator' => 'all' === $this->attributes['catOperator'] ? 'AND' : 'IN',
|
||||
|
||||
/*
|
||||
* When cat_operator is AND, the children categories should be excluded,
|
||||
* as only products belonging to all the children categories would be selected.
|
||||
*/
|
||||
'include_children' => 'all' === $this->attributes['catOperator'] ? false : true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set visibility query args.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_visibility_query_args( &$query_args ) {
|
||||
$product_visibility_terms = wc_get_product_visibility_term_ids();
|
||||
$product_visibility_not_in = array( $product_visibility_terms['exclude-from-catalog'] );
|
||||
|
||||
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
|
||||
$product_visibility_not_in[] = $product_visibility_terms['outofstock'];
|
||||
}
|
||||
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => 'product_visibility',
|
||||
'field' => 'term_taxonomy_id',
|
||||
'terms' => $product_visibility_not_in,
|
||||
'operator' => 'NOT IN',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Works out the item limit based on rows and columns, or returns default.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_products_limit() {
|
||||
if ( isset( $this->attributes['rows'], $this->attributes['columns'] ) && ! empty( $this->attributes['rows'] ) ) {
|
||||
$this->attributes['limit'] = intval( $this->attributes['columns'] ) * intval( $this->attributes['rows'] );
|
||||
}
|
||||
return intval( $this->attributes['limit'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the query and return an array of product IDs
|
||||
*
|
||||
* @return array List of product IDs
|
||||
*/
|
||||
protected function get_products() {
|
||||
$is_cacheable = (bool) apply_filters( 'woocommerce_blocks_product_grid_is_cacheable', true, $this->query_args );
|
||||
$transient_version = \WC_Cache_Helper::get_transient_version( 'product_query' );
|
||||
|
||||
$query = new BlocksWpQuery( $this->query_args );
|
||||
$results = wp_parse_id_list( $is_cacheable ? $query->get_cached_posts( $transient_version ) : $query->get_posts() );
|
||||
|
||||
// Remove ordering query arguments which may have been added by get_catalog_ordering_args.
|
||||
WC()->query->remove_ordering_args();
|
||||
|
||||
// Prime caches to reduce future queries.
|
||||
if ( is_callable( '_prime_post_caches' ) ) {
|
||||
_prime_post_caches( $results );
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of classes to apply to this block.
|
||||
*
|
||||
* @return string space-separated list of classes.
|
||||
*/
|
||||
protected function get_container_classes() {
|
||||
$classes = array(
|
||||
'wc-block-grid',
|
||||
"wp-block-{$this->block_name}",
|
||||
"wc-block-{$this->block_name}",
|
||||
"has-{$this->attributes['columns']}-columns",
|
||||
);
|
||||
|
||||
if ( $this->attributes['rows'] > 1 ) {
|
||||
$classes[] = 'has-multiple-rows';
|
||||
}
|
||||
|
||||
if ( isset( $this->attributes['align'] ) ) {
|
||||
$classes[] = "align{$this->attributes['align']}";
|
||||
}
|
||||
|
||||
if ( ! empty( $this->attributes['alignButtons'] ) ) {
|
||||
$classes[] = 'has-aligned-buttons';
|
||||
}
|
||||
|
||||
if ( ! empty( $this->attributes['className'] ) ) {
|
||||
$classes[] = $this->attributes['className'];
|
||||
}
|
||||
|
||||
return implode( ' ', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a single products.
|
||||
*
|
||||
* @param \WC_Product $product Product object.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function render_product( $product ) {
|
||||
$data = (object) array(
|
||||
'permalink' => esc_url( $product->get_permalink() ),
|
||||
'image' => $this->get_image_html( $product ),
|
||||
'title' => $this->get_title_html( $product ),
|
||||
'rating' => $this->get_rating_html( $product ),
|
||||
'price' => $this->get_price_html( $product ),
|
||||
'badge' => $this->get_sale_badge_html( $product ),
|
||||
'button' => $this->get_button_html( $product ),
|
||||
);
|
||||
|
||||
return apply_filters(
|
||||
'woocommerce_blocks_product_grid_item_html',
|
||||
"<li class=\"wc-block-grid__product\">
|
||||
<a href=\"{$data->permalink}\" class=\"wc-block-grid__product-link\">
|
||||
{$data->image}
|
||||
{$data->title}
|
||||
</a>
|
||||
{$data->badge}
|
||||
{$data->price}
|
||||
{$data->rating}
|
||||
{$data->button}
|
||||
</li>",
|
||||
$data,
|
||||
$product
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product image.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_image_html( $product ) {
|
||||
return '<div class="wc-block-grid__product-image">' . $product->get_image( 'woocommerce_thumbnail' ) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product title.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string
|
||||
*/
|
||||
protected function get_title_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['title'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return '<div class="wc-block-grid__product-title">' . wp_kses_post( $product->get_title() ) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the rating icons.
|
||||
*
|
||||
* @param WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_rating_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['rating'] ) ) {
|
||||
return '';
|
||||
}
|
||||
$rating_count = $product->get_rating_count();
|
||||
$review_count = $product->get_review_count();
|
||||
$average = $product->get_average_rating();
|
||||
|
||||
if ( $rating_count > 0 ) {
|
||||
return sprintf(
|
||||
'<div class="wc-block-grid__product-rating">%s</div>',
|
||||
wc_get_rating_html( $average, $rating_count ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_price_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return sprintf(
|
||||
'<div class="wc-block-grid__product-price price">%s</div>',
|
||||
wp_kses_post( $product->get_price_html() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sale badge.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_sale_badge_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['price'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( ! $product->is_on_sale() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return '<div class="wc-block-grid__product-onsale">
|
||||
<span aria-hidden="true">' . esc_html__( 'Sale', 'woocommerce' ) . '</span>
|
||||
<span class="screen-reader-text">' . esc_html__( 'Product on sale', 'woocommerce' ) . '</span>
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the button.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_button_html( $product ) {
|
||||
if ( empty( $this->attributes['contentVisibility']['button'] ) ) {
|
||||
return '';
|
||||
}
|
||||
return '<div class="wp-block-button wc-block-grid__product-add-to-cart">' . $this->get_add_to_cart( $product ) . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "add to cart" button.
|
||||
*
|
||||
* @param \WC_Product $product Product.
|
||||
* @return string Rendered product output.
|
||||
*/
|
||||
protected function get_add_to_cart( $product ) {
|
||||
$attributes = array(
|
||||
'aria-label' => $product->add_to_cart_description(),
|
||||
'data-quantity' => '1',
|
||||
'data-product_id' => $product->get_id(),
|
||||
'data-product_sku' => $product->get_sku(),
|
||||
'rel' => 'nofollow',
|
||||
'class' => 'wp-block-button__link add_to_cart_button',
|
||||
);
|
||||
|
||||
if (
|
||||
$product->supports( 'ajax_add_to_cart' ) &&
|
||||
$product->is_purchasable() &&
|
||||
( $product->is_in_stock() || $product->backorders_allowed() )
|
||||
) {
|
||||
$attributes['class'] .= ' ajax_add_to_cart';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<a href="%s" %s>%s</a>',
|
||||
esc_url( $product->add_to_cart_url() ),
|
||||
wc_implode_html_attributes( $attributes ),
|
||||
esc_html( $product->add_to_cart_text() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'min_columns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_columns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_columns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
|
||||
$this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
|
||||
}
|
||||
}
|
14
packages/woocommerce-blocks/src/BlockTypes/ActiveFilters.php
Normal file
14
packages/woocommerce-blocks/src/BlockTypes/ActiveFilters.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ActiveFilters class.
|
||||
*/
|
||||
class ActiveFilters extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'active-filters';
|
||||
}
|
56
packages/woocommerce-blocks/src/BlockTypes/AllProducts.php
Normal file
56
packages/woocommerce-blocks/src/BlockTypes/AllProducts.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AllProducts class.
|
||||
*/
|
||||
class AllProducts extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'all-products';
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'min_columns', wc_get_theme_support( 'product_blocks::min_columns', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_columns', wc_get_theme_support( 'product_blocks::max_columns', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_columns', wc_get_theme_support( 'product_blocks::default_columns', 3 ), true );
|
||||
$this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true );
|
||||
$this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true );
|
||||
$this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register script and style assets for the block type before it is registered.
|
||||
*
|
||||
* This registers the scripts; it does not enqueue them.
|
||||
*/
|
||||
protected function register_block_type_assets() {
|
||||
parent::register_block_type_assets();
|
||||
$this->register_chunk_translations(
|
||||
[
|
||||
'atomic-block-components/price',
|
||||
'atomic-block-components/image',
|
||||
'atomic-block-components/title',
|
||||
'atomic-block-components/rating',
|
||||
'atomic-block-components/button',
|
||||
'atomic-block-components/summary',
|
||||
'atomic-block-components/sale-badge',
|
||||
'atomic-block-components/sku',
|
||||
'atomic-block-components/category-list',
|
||||
'atomic-block-components/tag-list',
|
||||
'atomic-block-components/stock-indicator',
|
||||
'atomic-block-components/add-to-cart',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
43
packages/woocommerce-blocks/src/BlockTypes/AllReviews.php
Normal file
43
packages/woocommerce-blocks/src/BlockTypes/AllReviews.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AllReviews class.
|
||||
*/
|
||||
class AllReviews extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'all-reviews';
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-reviews-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true );
|
||||
$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true );
|
||||
}
|
||||
}
|
69
packages/woocommerce-blocks/src/BlockTypes/AtomicBlock.php
Normal file
69
packages/woocommerce-blocks/src/BlockTypes/AtomicBlock.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AtomicBlock class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AtomicBlock extends AbstractBlock {
|
||||
/**
|
||||
* Inject attributes and block name.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
return $this->inject_html_data_attributes( $content, $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor script data for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the editor style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_editor_style() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend style handle for this block type.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_style() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts block attributes to HTML data attributes.
|
||||
*
|
||||
* @param array $attributes Key value pairs of attributes.
|
||||
* @return string Rendered HTML attributes.
|
||||
*/
|
||||
protected function get_html_data_attributes( array $attributes ) {
|
||||
$data = parent::get_html_data_attributes( $attributes );
|
||||
return trim( $data . ' data-block-name="' . esc_attr( $this->namespace . '/' . $this->block_name ) . '"' );
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AttributeFilter class.
|
||||
*/
|
||||
class AttributeFilter extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'attribute-filter';
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'attributes', array_values( wc_get_attribute_taxonomies() ), true );
|
||||
}
|
||||
}
|
246
packages/woocommerce-blocks/src/BlockTypes/Cart.php
Normal file
246
packages/woocommerce-blocks/src/BlockTypes/Cart.php
Normal file
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
|
||||
/**
|
||||
* Cart class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Cart extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart';
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' );
|
||||
parent::enqueue_assets( $attributes );
|
||||
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the Cart block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
// Deregister core cart scripts and styles.
|
||||
wp_dequeue_script( 'wc-cart' );
|
||||
wp_dequeue_script( 'wc-password-strength-meter' );
|
||||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
|
||||
return $this->inject_html_data_attributes( $content . $this->get_skeleton(), $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'shippingCountries',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'shippingStates',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'countryLocale',
|
||||
function() {
|
||||
// Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944.
|
||||
$country_locale = wc()->countries->get_country_locale();
|
||||
$states = wc()->countries->get_states();
|
||||
|
||||
foreach ( $states as $country => $states ) {
|
||||
if ( empty( $states ) ) {
|
||||
$country_locale[ $country ]['state']['required'] = false;
|
||||
$country_locale[ $country ]['state']['hidden'] = true;
|
||||
}
|
||||
}
|
||||
return $country_locale;
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true );
|
||||
$this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ), true );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true );
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true );
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true );
|
||||
$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->hydrate_from_api();
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_blocks_cart_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
|
||||
*
|
||||
* @param array $array Array of values to sort.
|
||||
* @return array Sorted array.
|
||||
*/
|
||||
protected function deep_sort_with_accents( $array ) {
|
||||
if ( ! is_array( $array ) || empty( $array ) ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if ( is_array( reset( $array ) ) ) {
|
||||
return array_map( [ $this, 'deep_sort_with_accents' ], $array );
|
||||
}
|
||||
|
||||
$array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) );
|
||||
asort( $array_without_accents );
|
||||
return array_replace( $array_without_accents, $array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the cart block with data from the API.
|
||||
*/
|
||||
protected function hydrate_from_api() {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/cart' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render skeleton markup for the cart block.
|
||||
*/
|
||||
protected function get_skeleton() {
|
||||
return '
|
||||
<div class="wc-block-skeleton wc-block-components-sidebar-layout wc-block-cart wc-block-cart--is-loading wc-block-cart--skeleton hidden" aria-hidden="true">
|
||||
<div class="wc-block-components-main wc-block-cart__main">
|
||||
<h2 class="wc-block-components-title"><span></span></h2>
|
||||
<table class="wc-block-cart-items">
|
||||
<thead>
|
||||
<tr class="wc-block-cart-items__header">
|
||||
<th class="wc-block-cart-items__header-image"><span /></th>
|
||||
<th class="wc-block-cart-items__header-product"><span /></th>
|
||||
<th class="wc-block-cart-items__header-total"><span /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="wc-block-components-sidebar wc-block-cart__sidebar">
|
||||
<div class="components-card"></div>
|
||||
</div>
|
||||
</div>
|
||||
' . $this->get_skeleton_inline_script();
|
||||
}
|
||||
}
|
165
packages/woocommerce-blocks/src/BlockTypes/CartI2.php
Normal file
165
packages/woocommerce-blocks/src/BlockTypes/CartI2.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
|
||||
/**
|
||||
* Cart class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class CartI2 extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'cart-i2';
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_before' );
|
||||
parent::enqueue_assets( $attributes );
|
||||
do_action( 'woocommerce_blocks_enqueue_cart_block_scripts_after' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the Cart block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
// Deregister core cart scripts and styles.
|
||||
wp_dequeue_script( 'wc-cart' );
|
||||
wp_dequeue_script( 'wc-password-strength-meter' );
|
||||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
|
||||
return $this->inject_html_data_attributes( $content, $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'shippingCountries',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'shippingStates',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'countryLocale',
|
||||
function() {
|
||||
// Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944.
|
||||
$country_locale = wc()->countries->get_country_locale();
|
||||
$states = wc()->countries->get_states();
|
||||
|
||||
foreach ( $states as $country => $states ) {
|
||||
if ( empty( $states ) ) {
|
||||
$country_locale[ $country ]['state']['required'] = false;
|
||||
$country_locale[ $country ]['state']['hidden'] = true;
|
||||
}
|
||||
}
|
||||
return $country_locale;
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true );
|
||||
$this->asset_data_registry->add( 'isShippingCalculatorEnabled', filter_var( get_option( 'woocommerce_enable_shipping_calc' ), FILTER_VALIDATE_BOOLEAN ), true );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true );
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true );
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true );
|
||||
$this->asset_data_registry->register_page_id( isset( $attributes['checkoutPageId'] ) ? $attributes['checkoutPageId'] : 0 );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->hydrate_from_api();
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_blocks_cart_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
|
||||
*
|
||||
* @param array $array Array of values to sort.
|
||||
* @return array Sorted array.
|
||||
*/
|
||||
protected function deep_sort_with_accents( $array ) {
|
||||
if ( ! is_array( $array ) || empty( $array ) ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if ( is_array( reset( $array ) ) ) {
|
||||
return array_map( [ $this, 'deep_sort_with_accents' ], $array );
|
||||
}
|
||||
|
||||
$array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) );
|
||||
asort( $array_without_accents );
|
||||
return array_replace( $array_without_accents, $array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the cart block with data from the API.
|
||||
*/
|
||||
protected function hydrate_from_api() {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/cart' );
|
||||
}
|
||||
}
|
340
packages/woocommerce-blocks/src/BlockTypes/Checkout.php
Normal file
340
packages/woocommerce-blocks/src/BlockTypes/Checkout.php
Normal file
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* Checkout class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Checkout extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'checkout';
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue frontend assets for this block, just in time for rendering.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
*/
|
||||
protected function enqueue_assets( array $attributes ) {
|
||||
do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_before' );
|
||||
parent::enqueue_assets( $attributes );
|
||||
do_action( 'woocommerce_blocks_enqueue_checkout_block_scripts_after' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
if ( $this->is_checkout_endpoint() ) {
|
||||
// Note: Currently the block only takes care of the main checkout form -- if an endpoint is set, refer to the
|
||||
// legacy shortcode instead and do not render block.
|
||||
return '[woocommerce_checkout]';
|
||||
}
|
||||
|
||||
// Deregister core checkout scripts and styles.
|
||||
wp_dequeue_script( 'wc-checkout' );
|
||||
wp_dequeue_script( 'wc-password-strength-meter' );
|
||||
wp_dequeue_script( 'selectWoo' );
|
||||
wp_dequeue_style( 'select2' );
|
||||
|
||||
// If the content is empty, we may have transformed from an older checkout block. Insert the default list of blocks.
|
||||
$regex_for_empty_block = '/<div class="[a-zA-Z0-9_\- ]*wp-block-woocommerce-checkout[a-zA-Z0-9_\- ]*"><\/div>/mi';
|
||||
|
||||
$is_empty = preg_match( $regex_for_empty_block, $content );
|
||||
|
||||
if ( $is_empty ) {
|
||||
$inner_blocks_html = '<div data-block-name="woocommerce/checkout-fields-block" class="wp-block-woocommerce-checkout-fields-block"></div><div data-block-name="woocommerce/checkout-totals-block" class="wp-block-woocommerce-checkout-totals-block"></div>';
|
||||
|
||||
$content = str_replace( '</div>', $inner_blocks_html . '</div>', $content );
|
||||
}
|
||||
|
||||
return $this->inject_html_data_attributes( $content, $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're viewing a checkout page endpoint, rather than the main checkout page itself.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_checkout_endpoint() {
|
||||
return is_wc_endpoint_url( 'order-pay' ) || is_wc_endpoint_url( 'order-received' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'allowedCountries',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_allowed_countries() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'allowedStates',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_allowed_country_states() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'shippingCountries',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_countries() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'shippingStates',
|
||||
function() {
|
||||
return $this->deep_sort_with_accents( WC()->countries->get_shipping_country_states() );
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'countryLocale',
|
||||
function() {
|
||||
// Merge country and state data to work around https://github.com/woocommerce/woocommerce/issues/28944.
|
||||
$country_locale = wc()->countries->get_country_locale();
|
||||
$states = wc()->countries->get_states();
|
||||
|
||||
foreach ( $states as $country => $states ) {
|
||||
if ( empty( $states ) ) {
|
||||
$country_locale[ $country ]['state']['required'] = false;
|
||||
$country_locale[ $country ]['state']['hidden'] = true;
|
||||
}
|
||||
}
|
||||
return $country_locale;
|
||||
},
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add( 'baseLocation', wc_get_base_location(), true );
|
||||
$this->asset_data_registry->add(
|
||||
'checkoutAllowsGuest',
|
||||
false === filter_var(
|
||||
WC()->checkout()->is_registration_required(),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
),
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add(
|
||||
'checkoutAllowsSignup',
|
||||
filter_var(
|
||||
WC()->checkout()->is_registration_enabled(),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
),
|
||||
true
|
||||
);
|
||||
$this->asset_data_registry->add( 'checkoutShowLoginReminder', filter_var( get_option( 'woocommerce_enable_checkout_login_reminder' ), FILTER_VALIDATE_BOOLEAN ), true );
|
||||
$this->asset_data_registry->add( 'displayCartPricesIncludingTax', 'incl' === get_option( 'woocommerce_tax_display_cart' ), true );
|
||||
$this->asset_data_registry->add( 'displayItemizedTaxes', 'itemized' === get_option( 'woocommerce_tax_total_display' ), true );
|
||||
$this->asset_data_registry->add( 'taxesEnabled', wc_tax_enabled(), true );
|
||||
$this->asset_data_registry->add( 'couponsEnabled', wc_coupons_enabled(), true );
|
||||
$this->asset_data_registry->add( 'shippingEnabled', wc_shipping_enabled(), true );
|
||||
$this->asset_data_registry->add( 'hasDarkEditorStyleSupport', current_theme_supports( 'dark-editor-style' ), true );
|
||||
$this->asset_data_registry->register_page_id( isset( $attributes['cartPageId'] ) ? $attributes['cartPageId'] : 0 );
|
||||
|
||||
$is_block_editor = $this->is_block_editor();
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'shippingMethodsExist' ) ) {
|
||||
$methods_exist = wc_get_shipping_method_count( false, true ) > 0;
|
||||
$this->asset_data_registry->add( 'shippingMethodsExist', $methods_exist );
|
||||
}
|
||||
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalShippingMethods' ) ) {
|
||||
$shipping_methods = WC()->shipping()->get_shipping_methods();
|
||||
$formatted_shipping_methods = array_reduce(
|
||||
$shipping_methods,
|
||||
function( $acc, $method ) {
|
||||
if ( $method->supports( 'settings' ) ) {
|
||||
$acc[] = [
|
||||
'id' => $method->id,
|
||||
'title' => $method->method_title,
|
||||
'description' => $method->method_description,
|
||||
];
|
||||
}
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
$this->asset_data_registry->add( 'globalShippingMethods', $formatted_shipping_methods );
|
||||
}
|
||||
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'activeShippingZones' ) && class_exists( '\WC_Shipping_Zones' ) ) {
|
||||
$shipping_zones = \WC_Shipping_Zones::get_zones();
|
||||
$formatted_shipping_zones = array_reduce(
|
||||
$shipping_zones,
|
||||
function( $acc, $zone ) {
|
||||
$acc[] = [
|
||||
'id' => $zone['id'],
|
||||
'title' => $zone['zone_name'],
|
||||
'description' => $zone['formatted_zone_location'],
|
||||
];
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
$formatted_shipping_zones[] = [
|
||||
'id' => 0,
|
||||
'title' => __( 'International', 'woocommerce' ),
|
||||
'description' => __( 'Locations outside all other zones', 'woocommerce' ),
|
||||
];
|
||||
$this->asset_data_registry->add( 'activeShippingZones', $formatted_shipping_zones );
|
||||
}
|
||||
|
||||
if ( $is_block_editor && ! $this->asset_data_registry->exists( 'globalPaymentMethods' ) ) {
|
||||
$payment_methods = WC()->payment_gateways->payment_gateways();
|
||||
$formatted_payment_methods = array_reduce(
|
||||
$payment_methods,
|
||||
function( $acc, $method ) {
|
||||
if ( 'yes' === $method->enabled ) {
|
||||
$acc[] = [
|
||||
'id' => $method->id,
|
||||
'title' => $method->method_title,
|
||||
'description' => $method->method_description,
|
||||
];
|
||||
}
|
||||
return $acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
$this->asset_data_registry->add( 'globalPaymentMethods', $formatted_payment_methods );
|
||||
}
|
||||
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->hydrate_from_api();
|
||||
$this->hydrate_customer_payment_methods();
|
||||
}
|
||||
|
||||
do_action( 'woocommerce_blocks_checkout_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we currently on the admin block editor screen?
|
||||
*/
|
||||
protected function is_block_editor() {
|
||||
if ( ! is_admin() || ! function_exists( 'get_current_screen' ) ) {
|
||||
return false;
|
||||
}
|
||||
$screen = get_current_screen();
|
||||
|
||||
return $screen && $screen->is_block_editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes accents from an array of values, sorts by the values, then returns the original array values sorted.
|
||||
*
|
||||
* @param array $array Array of values to sort.
|
||||
* @return array Sorted array.
|
||||
*/
|
||||
protected function deep_sort_with_accents( $array ) {
|
||||
if ( ! is_array( $array ) || empty( $array ) ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if ( is_array( reset( $array ) ) ) {
|
||||
return array_map( [ $this, 'deep_sort_with_accents' ], $array );
|
||||
}
|
||||
|
||||
$array_without_accents = array_map( 'remove_accents', array_map( 'wc_strtolower', array_map( 'html_entity_decode', $array ) ) );
|
||||
asort( $array_without_accents );
|
||||
return array_replace( $array_without_accents, $array );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get customer payment methods for use in checkout.
|
||||
*/
|
||||
protected function hydrate_customer_payment_methods() {
|
||||
if ( ! is_user_logged_in() || $this->asset_data_registry->exists( 'customerPaymentMethods' ) ) {
|
||||
return;
|
||||
}
|
||||
add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 );
|
||||
$this->asset_data_registry->add(
|
||||
'customerPaymentMethods',
|
||||
wc_get_customer_saved_methods_list( get_current_user_id() )
|
||||
);
|
||||
remove_filter( 'woocommerce_payment_methods_list_item', [ $this, 'include_token_id_with_payment_methods' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the checkout block with data from the API.
|
||||
*/
|
||||
protected function hydrate_from_api() {
|
||||
// Print existing notices now, otherwise they are caught by the Cart
|
||||
// Controller and converted to exceptions.
|
||||
wc_print_notices();
|
||||
|
||||
add_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' );
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/cart' );
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/checkout' );
|
||||
remove_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for woocommerce_payment_methods_list_item filter to add token id
|
||||
* to the generated list.
|
||||
*
|
||||
* @param array $list_item The current list item for the saved payment method.
|
||||
* @param \WC_Token $token The token for the current list item.
|
||||
*
|
||||
* @return array The list item with the token id added.
|
||||
*/
|
||||
public static function include_token_id_with_payment_methods( $list_item, $token ) {
|
||||
$list_item['tokenId'] = $token->get_id();
|
||||
$brand = ! empty( $list_item['method']['brand'] ) ?
|
||||
strtolower( $list_item['method']['brand'] ) :
|
||||
'';
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- need to match on translated value from core.
|
||||
if ( ! empty( $brand ) && esc_html__( 'Credit card', 'woocommerce' ) !== $brand ) {
|
||||
$list_item['method']['brand'] = wc_get_credit_card_type_label( $brand );
|
||||
}
|
||||
return $list_item;
|
||||
}
|
||||
}
|
181
packages/woocommerce-blocks/src/BlockTypes/FeaturedCategory.php
Normal file
181
packages/woocommerce-blocks/src/BlockTypes/FeaturedCategory.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FeaturedCategory class.
|
||||
*/
|
||||
class FeaturedCategory extends AbstractDynamicBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'featured-category';
|
||||
|
||||
/**
|
||||
* Default attribute values, should match what's set in JS `registerBlockType`.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = array(
|
||||
'align' => 'none',
|
||||
'contentAlign' => 'center',
|
||||
'dimRatio' => 50,
|
||||
'focalPoint' => false,
|
||||
'height' => false,
|
||||
'mediaId' => 0,
|
||||
'mediaSrc' => '',
|
||||
'showDesc' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Render the Featured Category block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
$id = absint( isset( $attributes['categoryId'] ) ? $attributes['categoryId'] : 0 );
|
||||
$category = get_term( $id, 'product_cat' );
|
||||
|
||||
if ( ! $category || is_wp_error( $category ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$attributes = wp_parse_args( $attributes, $this->defaults );
|
||||
|
||||
$attributes['height'] = $attributes['height'] ? $attributes['height'] : wc_get_theme_support( 'featured_block::default_height', 500 );
|
||||
|
||||
$title = sprintf(
|
||||
'<h2 class="wc-block-featured-category__title">%s</h2>',
|
||||
wp_kses_post( $category->name )
|
||||
);
|
||||
|
||||
$desc_str = sprintf(
|
||||
'<div class="wc-block-featured-category__description">%s</div>',
|
||||
wc_format_content( wp_kses_post( $category->description ) )
|
||||
);
|
||||
|
||||
$output = sprintf( '<div class="%1$s" style="%2$s">', esc_attr( $this->get_classes( $attributes ) ), esc_attr( $this->get_styles( $attributes, $category ) ) );
|
||||
$output .= '<div class="wc-block-featured-category__wrapper">';
|
||||
$output .= $title;
|
||||
if ( $attributes['showDesc'] ) {
|
||||
$output .= $desc_str;
|
||||
}
|
||||
$output .= '<div class="wc-block-featured-category__link">' . $content . '</div>';
|
||||
$output .= '</div>';
|
||||
$output .= '</div>';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the styles for the wrapper element (background image, color).
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param \WP_Term $category Term object.
|
||||
* @return string
|
||||
*/
|
||||
public function get_styles( $attributes, $category ) {
|
||||
$style = '';
|
||||
$image_size = 'large';
|
||||
if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) {
|
||||
$image_size = 'full';
|
||||
}
|
||||
|
||||
if ( $attributes['mediaId'] ) {
|
||||
$image = wp_get_attachment_image_url( $attributes['mediaId'], $image_size );
|
||||
} else {
|
||||
$image = $this->get_image( $category, $image_size );
|
||||
}
|
||||
|
||||
if ( ! empty( $image ) ) {
|
||||
$style .= sprintf( 'background-image:url(%s);', esc_url( $image ) );
|
||||
}
|
||||
|
||||
if ( isset( $attributes['customOverlayColor'] ) ) {
|
||||
$style .= sprintf( 'background-color:%s;', esc_attr( $attributes['customOverlayColor'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $attributes['height'] ) ) {
|
||||
$style .= sprintf( 'min-height:%dpx;', intval( $attributes['height'] ) );
|
||||
}
|
||||
|
||||
if ( is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] ) ) {
|
||||
$style .= sprintf(
|
||||
'background-position: %s%% %s%%',
|
||||
$attributes['focalPoint']['x'] * 100,
|
||||
$attributes['focalPoint']['y'] * 100
|
||||
);
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class names for the block container.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string
|
||||
*/
|
||||
public function get_classes( $attributes ) {
|
||||
$classes = array( 'wc-block-' . $this->block_name );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classes[] = "align{$attributes['align']}";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) {
|
||||
$classes[] = 'has-background-dim';
|
||||
|
||||
if ( 50 !== $attributes['dimRatio'] ) {
|
||||
$classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) {
|
||||
$classes[] = "has-{$attributes['contentAlign']}-content";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['overlayColor'] ) ) {
|
||||
$classes[] = "has-{$attributes['overlayColor']}-background-color";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['className'] ) ) {
|
||||
$classes[] = $attributes['className'];
|
||||
}
|
||||
|
||||
return implode( ' ', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main product image URL.
|
||||
*
|
||||
* @param \WP_Term $category Term object.
|
||||
* @param string $size Image size, defaults to 'full'.
|
||||
* @return string
|
||||
*/
|
||||
public function get_image( $category, $size = 'full' ) {
|
||||
$image = '';
|
||||
$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
|
||||
|
||||
if ( $image_id ) {
|
||||
$image = wp_get_attachment_image_url( $image_id, $size );
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'min_height', wc_get_theme_support( 'featured_block::min_height', 500 ), true );
|
||||
$this->asset_data_registry->add( 'default_height', wc_get_theme_support( 'featured_block::default_height', 500 ), true );
|
||||
}
|
||||
}
|
199
packages/woocommerce-blocks/src/BlockTypes/FeaturedProduct.php
Normal file
199
packages/woocommerce-blocks/src/BlockTypes/FeaturedProduct.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* FeaturedProduct class.
|
||||
*/
|
||||
class FeaturedProduct extends AbstractDynamicBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'featured-product';
|
||||
|
||||
/**
|
||||
* Default attribute values, should match what's set in JS `registerBlockType`.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = array(
|
||||
'align' => 'none',
|
||||
'contentAlign' => 'center',
|
||||
'dimRatio' => 50,
|
||||
'focalPoint' => false,
|
||||
'height' => false,
|
||||
'mediaId' => 0,
|
||||
'mediaSrc' => '',
|
||||
'showDesc' => true,
|
||||
'showPrice' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Render the Featured Product block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
$id = absint( isset( $attributes['productId'] ) ? $attributes['productId'] : 0 );
|
||||
$product = wc_get_product( $id );
|
||||
if ( ! $product ) {
|
||||
return '';
|
||||
}
|
||||
$attributes = wp_parse_args( $attributes, $this->defaults );
|
||||
|
||||
$attributes['height'] = $attributes['height'] ? $attributes['height'] : wc_get_theme_support( 'featured_block::default_height', 500 );
|
||||
|
||||
$title = sprintf(
|
||||
'<h2 class="wc-block-featured-product__title">%s</h2>',
|
||||
wp_kses_post( $product->get_title() )
|
||||
);
|
||||
|
||||
if ( $product->is_type( 'variation' ) ) {
|
||||
$title .= sprintf(
|
||||
'<h3 class="wc-block-featured-product__variation">%s</h3>',
|
||||
wp_kses_post( wc_get_formatted_variation( $product, true, true, false ) )
|
||||
);
|
||||
}
|
||||
|
||||
$desc_str = sprintf(
|
||||
'<div class="wc-block-featured-product__description">%s</div>',
|
||||
wc_format_content( wp_kses_post( $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) ) )
|
||||
);
|
||||
|
||||
$price_str = sprintf(
|
||||
'<div class="wc-block-featured-product__price">%s</div>',
|
||||
wp_kses_post( $product->get_price_html() )
|
||||
);
|
||||
|
||||
$output = sprintf( '<div class="%1$s" style="%2$s">', esc_attr( $this->get_classes( $attributes ) ), esc_attr( $this->get_styles( $attributes, $product ) ) );
|
||||
$output .= '<div class="wc-block-featured-product__wrapper">';
|
||||
$output .= $title;
|
||||
if ( $attributes['showDesc'] ) {
|
||||
$output .= $desc_str;
|
||||
}
|
||||
if ( $attributes['showPrice'] ) {
|
||||
$output .= $price_str;
|
||||
}
|
||||
$output .= '<div class="wc-block-featured-product__link">' . $content . '</div>';
|
||||
$output .= '</div>';
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the styles for the wrapper element (background image, color).
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param \WC_Product $product Product object.
|
||||
* @return string
|
||||
*/
|
||||
public function get_styles( $attributes, $product ) {
|
||||
$style = '';
|
||||
$image_size = 'large';
|
||||
if ( 'none' !== $attributes['align'] || $attributes['height'] > 800 ) {
|
||||
$image_size = 'full';
|
||||
}
|
||||
|
||||
if ( $attributes['mediaId'] ) {
|
||||
$image = wp_get_attachment_image_url( $attributes['mediaId'], $image_size );
|
||||
} else {
|
||||
$image = $this->get_image( $product, $image_size );
|
||||
}
|
||||
|
||||
if ( ! empty( $image ) ) {
|
||||
$style .= sprintf( 'background-image:url(%s);', esc_url( $image ) );
|
||||
}
|
||||
|
||||
if ( isset( $attributes['customOverlayColor'] ) ) {
|
||||
$style .= sprintf( 'background-color:%s;', esc_attr( $attributes['customOverlayColor'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $attributes['height'] ) ) {
|
||||
$style .= sprintf( 'min-height:%dpx;', intval( $attributes['height'] ) );
|
||||
}
|
||||
|
||||
if ( is_array( $attributes['focalPoint'] ) && 2 === count( $attributes['focalPoint'] ) ) {
|
||||
$style .= sprintf(
|
||||
'background-position: %s%% %s%%',
|
||||
$attributes['focalPoint']['x'] * 100,
|
||||
$attributes['focalPoint']['y'] * 100
|
||||
);
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class names for the block container.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string
|
||||
*/
|
||||
public function get_classes( $attributes ) {
|
||||
$classes = array( 'wc-block-' . $this->block_name );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classes[] = "align{$attributes['align']}";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['dimRatio'] ) && ( 0 !== $attributes['dimRatio'] ) ) {
|
||||
$classes[] = 'has-background-dim';
|
||||
|
||||
if ( 50 !== $attributes['dimRatio'] ) {
|
||||
$classes[] = 'has-background-dim-' . 10 * round( $attributes['dimRatio'] / 10 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $attributes['contentAlign'] ) && 'center' !== $attributes['contentAlign'] ) {
|
||||
$classes[] = "has-{$attributes['contentAlign']}-content";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['overlayColor'] ) ) {
|
||||
$classes[] = "has-{$attributes['overlayColor']}-background-color";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['className'] ) ) {
|
||||
$classes[] = $attributes['className'];
|
||||
}
|
||||
|
||||
return implode( ' ', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main product image URL.
|
||||
*
|
||||
* @param \WC_Product $product Product object.
|
||||
* @param string $size Image size, defaults to 'full'.
|
||||
* @return string
|
||||
*/
|
||||
public function get_image( $product, $size = 'full' ) {
|
||||
$image = '';
|
||||
if ( $product->get_image_id() ) {
|
||||
$image = wp_get_attachment_image_url( $product->get_image_id(), $size );
|
||||
} elseif ( $product->get_parent_id() ) {
|
||||
$parent_product = wc_get_product( $product->get_parent_id() );
|
||||
if ( $parent_product ) {
|
||||
$image = wp_get_attachment_image_url( $parent_product->get_image_id(), $size );
|
||||
}
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'min_height', wc_get_theme_support( 'featured_block::min_height', 500 ), true );
|
||||
$this->asset_data_registry->add( 'default_height', wc_get_theme_support( 'featured_block::default_height', 500 ), true );
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* HandpickedProducts class.
|
||||
*/
|
||||
class HandpickedProducts extends AbstractProductGrid {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'handpicked-products';
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {
|
||||
$ids = array_map( 'absint', $this->attributes['products'] );
|
||||
|
||||
$query_args['post__in'] = $ids;
|
||||
$query_args['posts_per_page'] = count( $ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set visibility query args. Handpicked products will show hidden products if chosen.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_visibility_query_args( &$query_args ) {
|
||||
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
|
||||
$product_visibility_terms = wc_get_product_visibility_term_ids();
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => 'product_visibility',
|
||||
'field' => 'term_taxonomy_id',
|
||||
'terms' => array( $product_visibility_terms['outofstock'] ),
|
||||
'operator' => 'NOT IN',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array(
|
||||
'align' => $this->get_schema_align(),
|
||||
'alignButtons' => $this->get_schema_boolean( false ),
|
||||
'className' => $this->get_schema_string(),
|
||||
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
|
||||
'editMode' => $this->get_schema_boolean( true ),
|
||||
'orderby' => $this->get_schema_orderby(),
|
||||
'products' => $this->get_schema_list_ids(),
|
||||
'contentVisibility' => $this->get_schema_content_visibility(),
|
||||
'isPreview' => $this->get_schema_boolean( false ),
|
||||
);
|
||||
}
|
||||
}
|
313
packages/woocommerce-blocks/src/BlockTypes/MiniCart.php
Normal file
313
packages/woocommerce-blocks/src/BlockTypes/MiniCart.php
Normal file
@ -0,0 +1,313 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
|
||||
|
||||
/**
|
||||
* Mini Cart class.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MiniCart extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'mini-cart';
|
||||
|
||||
/**
|
||||
* Array of scripts that will be lazy loaded when interacting with the block.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $scripts_to_lazy_load = array();
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
if ( is_cart() || is_checkout() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name . '-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
if ( is_cart() || is_checkout() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
// Hydrate the following data depending on admin or frontend context.
|
||||
if ( ! is_admin() && ! WC()->is_rest_api_request() ) {
|
||||
$this->hydrate_from_api();
|
||||
}
|
||||
|
||||
$script_data = $this->asset_api->get_script_data( 'build/mini-cart-component-frontend.js' );
|
||||
|
||||
$num_dependencies = count( $script_data['dependencies'] );
|
||||
$wp_scripts = wp_scripts();
|
||||
|
||||
for ( $i = 0; $i < $num_dependencies; $i++ ) {
|
||||
$dependency = $script_data['dependencies'][ $i ];
|
||||
|
||||
foreach ( $wp_scripts->registered as $script ) {
|
||||
if ( $script->handle === $dependency ) {
|
||||
$this->append_script_and_deps_src( $script );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->scripts_to_lazy_load['wc-block-mini-cart-component-frontend'] = array(
|
||||
'src' => $script_data['src'],
|
||||
'version' => $script_data['version'],
|
||||
);
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'mini_cart_block_frontend_dependencies',
|
||||
$this->scripts_to_lazy_load,
|
||||
true
|
||||
);
|
||||
|
||||
$this->asset_data_registry->add(
|
||||
'displayCartPricesIncludingTax',
|
||||
'incl' === get_option( 'woocommerce_tax_display_cart' ),
|
||||
true
|
||||
);
|
||||
|
||||
do_action( 'woocommerce_blocks_cart_enqueue_data' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the cart block with data from the API.
|
||||
*/
|
||||
protected function hydrate_from_api() {
|
||||
$this->asset_data_registry->hydrate_api_request( '/wc/store/cart' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script data given its handle.
|
||||
*
|
||||
* @param string $handle Handle of the script.
|
||||
*
|
||||
* @return array Array containing the script data.
|
||||
*/
|
||||
protected function get_script_from_handle( $handle ) {
|
||||
$wp_scripts = wp_scripts();
|
||||
foreach ( $wp_scripts->registered as $script ) {
|
||||
if ( $script->handle === $handle ) {
|
||||
return $script;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively appends a scripts and its dependencies into the
|
||||
* scripts_to_lazy_load array.
|
||||
*
|
||||
* @param string $script Array containing script data.
|
||||
*/
|
||||
protected function append_script_and_deps_src( $script ) {
|
||||
$wp_scripts = wp_scripts();
|
||||
// This script and its dependencies have already been appended.
|
||||
if ( array_key_exists( $script->handle, $this->scripts_to_lazy_load ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( count( $script->deps ) > 0 ) {
|
||||
foreach ( $script->deps as $dep ) {
|
||||
if ( ! array_key_exists( $dep, $this->scripts_to_lazy_load ) ) {
|
||||
$dep_script = $this->get_script_from_handle( $dep );
|
||||
$this->append_script_and_deps_src( $dep_script );
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->scripts_to_lazy_load[ $script->handle ] = array(
|
||||
'src' => $script->src,
|
||||
'version' => $script->ver,
|
||||
'before' => $wp_scripts->print_inline_script( $script->handle, 'before', false ),
|
||||
'after' => $wp_scripts->print_inline_script( $script->handle, 'after', false ),
|
||||
'translations' => $wp_scripts->print_translations( $script->handle, false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append frontend scripts when rendering the Mini Cart block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
*
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
return $this->inject_html_data_attributes( $content . $this->get_markup(), $attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the markup for the Mini Cart block.
|
||||
*
|
||||
* @return string The HTML markup.
|
||||
*/
|
||||
protected function get_markup() {
|
||||
if ( is_admin() || WC()->is_rest_api_request() ) {
|
||||
// In the editor we will display the placeholder, so no need to load
|
||||
// real cart data and to print the markup.
|
||||
return '';
|
||||
}
|
||||
$cart_controller = new CartController();
|
||||
$cart = $cart_controller->get_cart_instance();
|
||||
$cart_contents_count = $cart->get_cart_contents_count();
|
||||
$cart_contents = $cart->get_cart();
|
||||
$cart_contents_total = $cart->get_subtotal();
|
||||
|
||||
if ( $cart->display_prices_including_tax() ) {
|
||||
$cart_contents_total += $cart->get_subtotal_tax();
|
||||
}
|
||||
|
||||
$button_text = sprintf(
|
||||
/* translators: %d is the number of products in the cart. */
|
||||
_n(
|
||||
'%d item',
|
||||
'%d items',
|
||||
$cart_contents_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$cart_contents_count
|
||||
);
|
||||
$aria_label = sprintf(
|
||||
/* translators: %1$d is the number of products in the cart. %2$s is the cart total */
|
||||
_n(
|
||||
'%1$d item in cart, total price of %2$s',
|
||||
'%1$d items in cart, total price of %2$s',
|
||||
$cart_contents_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$cart_contents_count,
|
||||
wp_strip_all_tags( wc_price( $cart_contents_total ) )
|
||||
);
|
||||
$title = sprintf(
|
||||
/* translators: %d is the count of items in the cart. */
|
||||
_n(
|
||||
'Your cart (%d item)',
|
||||
'Your cart (%d items)',
|
||||
$cart_contents_count,
|
||||
'woocommerce'
|
||||
),
|
||||
$cart_contents_count
|
||||
);
|
||||
|
||||
if ( is_cart() || is_checkout() ) {
|
||||
return '<div class="wc-block-mini-cart">
|
||||
<button class="wc-block-mini-cart__button" aria-label="' . $aria_label . '" disabled>' . $button_text . '</button>
|
||||
</div>';
|
||||
}
|
||||
|
||||
return '<div class="wc-block-mini-cart">
|
||||
<button class="wc-block-mini-cart__button" aria-label="' . $aria_label . '">' . $button_text . '</button>
|
||||
<div class="wc-block-mini-cart__drawer is-loading is-mobile wc-block-components-drawer__screen-overlay wc-block-components-drawer__screen-overlay--is-hidden" aria-hidden="true">
|
||||
<div class="components-modal__frame wc-block-components-drawer">
|
||||
<div class="components-modal__content">
|
||||
<div class="components-modal__header">
|
||||
<div class="components-modal__header-heading-container">
|
||||
<h1 id="components-modal-header-1" class="components-modal__header-heading">' . $title . '</h1>
|
||||
</div>
|
||||
</div>'
|
||||
. $this->get_cart_contents_markup( $cart_contents ) .
|
||||
'</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the markup of the Cart contents.
|
||||
*
|
||||
* @param array $cart_contents Array of contents in the cart.
|
||||
*
|
||||
* @return string The HTML markup.
|
||||
*/
|
||||
protected function get_cart_contents_markup( $cart_contents ) {
|
||||
// Force mobile styles.
|
||||
return '<table class="wc-block-cart-items">
|
||||
<thead>
|
||||
<tr class="wc-block-cart-items__header">
|
||||
<th class="wc-block-cart-items__header-image"><span /></th>
|
||||
<th class="wc-block-cart-items__header-product"><span /></th>
|
||||
<th class="wc-block-cart-items__header-total"><span /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>' . implode( array_map( array( $this, 'get_cart_item_markup' ), $cart_contents ) ) . '</tbody>
|
||||
</table>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the skeleton of a Cart item.
|
||||
*
|
||||
* @return string The skeleton HTML markup.
|
||||
*/
|
||||
protected function get_cart_item_markup() {
|
||||
return '<tr class="wc-block-cart-items__row">
|
||||
<td class="wc-block-cart-item__image">
|
||||
<a href=""><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" width="1" height="1" /></a>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__product">
|
||||
<div class="wc-block-components-product-name"></div>
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
<div class="wc-block-components-product-metadata"></div>
|
||||
<div class="wc-block-cart-item__quantity">
|
||||
<div class="wc-block-components-quantity-selector">
|
||||
<input class="wc-block-components-quantity-selector__input" type="number" step="1" min="0" value="1" />
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus">-</button>
|
||||
<button class="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus">+</button>
|
||||
</div>
|
||||
<button class="wc-block-cart-item__remove-link"></button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="wc-block-cart-item__total">
|
||||
<div class="wc-block-cart-item__total-price-and-sale-badge-wrapper">
|
||||
<div class="wc-block-components-product-price"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
}
|
15
packages/woocommerce-blocks/src/BlockTypes/PriceFilter.php
Normal file
15
packages/woocommerce-blocks/src/BlockTypes/PriceFilter.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* PriceFilter class.
|
||||
*/
|
||||
class PriceFilter extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'price-filter';
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductBestSellers class.
|
||||
*/
|
||||
class ProductBestSellers extends AbstractProductGrid {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-best-sellers';
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {
|
||||
$query_args['orderby'] = 'popularity';
|
||||
}
|
||||
}
|
366
packages/woocommerce-blocks/src/BlockTypes/ProductCategories.php
Normal file
366
packages/woocommerce-blocks/src/BlockTypes/ProductCategories.php
Normal file
@ -0,0 +1,366 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductCategories class.
|
||||
*/
|
||||
class ProductCategories extends AbstractDynamicBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-categories';
|
||||
|
||||
/**
|
||||
* Default attribute values, should match what's set in JS `registerBlockType`.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = array(
|
||||
'hasCount' => true,
|
||||
'hasImage' => false,
|
||||
'hasEmpty' => false,
|
||||
'isDropdown' => false,
|
||||
'isHierarchical' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array_merge(
|
||||
parent::get_block_type_attributes(),
|
||||
array(
|
||||
'align' => $this->get_schema_align(),
|
||||
'className' => $this->get_schema_string(),
|
||||
'hasCount' => $this->get_schema_boolean( true ),
|
||||
'hasImage' => $this->get_schema_boolean( false ),
|
||||
'hasEmpty' => $this->get_schema_boolean( false ),
|
||||
'isDropdown' => $this->get_schema_boolean( false ),
|
||||
'isHierarchical' => $this->get_schema_boolean( true ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the Product Categories List block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
$uid = uniqid( 'product-categories-' );
|
||||
$categories = $this->get_categories( $attributes );
|
||||
|
||||
if ( empty( $categories ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( ! empty( $content ) ) {
|
||||
// Deal with legacy attributes (before this was an SSR block) that differ from defaults.
|
||||
if ( strstr( $content, 'data-has-count="false"' ) ) {
|
||||
$attributes['hasCount'] = false;
|
||||
}
|
||||
if ( strstr( $content, 'data-is-dropdown="true"' ) ) {
|
||||
$attributes['isDropdown'] = true;
|
||||
}
|
||||
if ( strstr( $content, 'data-is-hierarchical="false"' ) ) {
|
||||
$attributes['isHierarchical'] = false;
|
||||
}
|
||||
if ( strstr( $content, 'data-has-empty="true"' ) ) {
|
||||
$attributes['hasEmpty'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$classes = $this->get_container_classes( $attributes );
|
||||
|
||||
$output = '<div class="' . esc_attr( $classes ) . '">';
|
||||
$output .= ! empty( $attributes['isDropdown'] ) ? $this->renderDropdown( $categories, $attributes, $uid ) : $this->renderList( $categories, $attributes, $uid );
|
||||
$output .= '</div>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of classes to apply to this block.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string space-separated list of classes.
|
||||
*/
|
||||
protected function get_container_classes( $attributes = array() ) {
|
||||
$classes = array( 'wc-block-product-categories' );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classes[] = "align{$attributes['align']}";
|
||||
}
|
||||
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$classes[] = $attributes['className'];
|
||||
}
|
||||
|
||||
if ( $attributes['isDropdown'] ) {
|
||||
$classes[] = 'is-dropdown';
|
||||
} else {
|
||||
$classes[] = 'is-list';
|
||||
}
|
||||
|
||||
return implode( ' ', $classes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories (terms) from the db.
|
||||
*
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return array
|
||||
*/
|
||||
protected function get_categories( $attributes ) {
|
||||
$hierarchical = wc_string_to_bool( $attributes['isHierarchical'] );
|
||||
$categories = get_terms(
|
||||
'product_cat',
|
||||
[
|
||||
'hide_empty' => ! $attributes['hasEmpty'],
|
||||
'pad_counts' => true,
|
||||
'hierarchical' => true,
|
||||
]
|
||||
);
|
||||
|
||||
if ( ! is_array( $categories ) || empty( $categories ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// This ensures that no categories with a product count of 0 is rendered.
|
||||
if ( ! $attributes['hasEmpty'] ) {
|
||||
$categories = array_filter(
|
||||
$categories,
|
||||
function( $category ) {
|
||||
return 0 !== $category->count;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $hierarchical ? $this->build_category_tree( $categories ) : $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hierarchical tree of categories.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @return array
|
||||
*/
|
||||
protected function build_category_tree( $categories ) {
|
||||
$categories_by_parent = [];
|
||||
|
||||
foreach ( $categories as $category ) {
|
||||
if ( ! isset( $categories_by_parent[ 'cat-' . $category->parent ] ) ) {
|
||||
$categories_by_parent[ 'cat-' . $category->parent ] = [];
|
||||
}
|
||||
$categories_by_parent[ 'cat-' . $category->parent ][] = $category;
|
||||
}
|
||||
|
||||
$tree = $categories_by_parent['cat-0'];
|
||||
unset( $categories_by_parent['cat-0'] );
|
||||
|
||||
foreach ( $tree as $category ) {
|
||||
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
|
||||
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
|
||||
}
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build hierarchical tree of categories by appending children in the tree.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $categories_by_parent List of terms grouped by parent.
|
||||
* @return array
|
||||
*/
|
||||
protected function fill_category_children( $categories, $categories_by_parent ) {
|
||||
foreach ( $categories as $category ) {
|
||||
if ( ! empty( $categories_by_parent[ 'cat-' . $category->term_id ] ) ) {
|
||||
$category->children = $this->fill_category_children( $categories_by_parent[ 'cat-' . $category->term_id ], $categories_by_parent );
|
||||
}
|
||||
}
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the category list as a dropdown.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderDropdown( $categories, $attributes, $uid ) {
|
||||
$aria_label = empty( $attributes['hasCount'] ) ?
|
||||
__( 'List of categories', 'woocommerce' ) :
|
||||
__( 'List of categories with their product counts', 'woocommerce' );
|
||||
|
||||
$output = '
|
||||
<div class="wc-block-product-categories__dropdown">
|
||||
<label
|
||||
class="screen-reader-text"
|
||||
for="' . esc_attr( $uid ) . '-select"
|
||||
>
|
||||
' . esc_html__( 'Select a category', 'woocommerce' ) . '
|
||||
</label>
|
||||
<select aria-label="' . esc_attr( $aria_label ) . '" id="' . esc_attr( $uid ) . '-select">
|
||||
<option value="false" hidden>
|
||||
' . esc_html__( 'Select a category', 'woocommerce' ) . '
|
||||
</option>
|
||||
' . $this->renderDropdownOptions( $categories, $attributes, $uid ) . '
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="wc-block-product-categories__button"
|
||||
aria-label="' . esc_html__( 'Go to category', 'woocommerce' ) . '"
|
||||
onclick="const url = document.getElementById( \'' . esc_attr( $uid ) . '-select\' ).value; if ( \'false\' !== url ) document.location.href = url;"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
focusable="false"
|
||||
class="dashicon dashicons-arrow-right-alt2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
|
||||
</svg>
|
||||
</button>
|
||||
';
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render dropdown options list.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @param int $depth Current depth.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderDropdownOptions( $categories, $attributes, $uid, $depth = 0 ) {
|
||||
$output = '';
|
||||
|
||||
foreach ( $categories as $category ) {
|
||||
$output .= '
|
||||
<option value="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">
|
||||
' . str_repeat( '−', $depth ) . '
|
||||
' . esc_html( $category->name ) . '
|
||||
' . $this->getCount( $category, $attributes ) . '
|
||||
</option>
|
||||
' . ( ! empty( $category->children ) ? $this->renderDropdownOptions( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
|
||||
';
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the category list as a list.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @param int $depth Current depth.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderList( $categories, $attributes, $uid, $depth = 0 ) {
|
||||
$classes = [
|
||||
'wc-block-product-categories-list',
|
||||
'wc-block-product-categories-list--depth-' . absint( $depth ),
|
||||
];
|
||||
if ( ! empty( $attributes['hasImage'] ) ) {
|
||||
$classes[] = 'wc-block-product-categories-list--has-images';
|
||||
}
|
||||
$output = '<ul class="' . esc_attr( implode( ' ', $classes ) ) . '">' . $this->renderListItems( $categories, $attributes, $uid, $depth ) . '</ul>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a list of terms.
|
||||
*
|
||||
* @param array $categories List of terms.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param int $uid Unique ID for the rendered block, used for HTML IDs.
|
||||
* @param int $depth Current depth.
|
||||
* @return string Rendered output.
|
||||
*/
|
||||
protected function renderListItems( $categories, $attributes, $uid, $depth = 0 ) {
|
||||
$output = '';
|
||||
|
||||
foreach ( $categories as $category ) {
|
||||
$output .= '
|
||||
<li class="wc-block-product-categories-list-item">
|
||||
<a href="' . esc_attr( get_term_link( $category->term_id, 'product_cat' ) ) . '">' . $this->get_image_html( $category, $attributes ) . esc_html( $category->name ) . '</a>
|
||||
' . $this->getCount( $category, $attributes ) . '
|
||||
' . ( ! empty( $category->children ) ? $this->renderList( $category->children, $attributes, $uid, $depth + 1 ) : '' ) . '
|
||||
</li>
|
||||
';
|
||||
}
|
||||
|
||||
return preg_replace( '/\r|\n/', '', $output );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category image html
|
||||
*
|
||||
* @param \WP_Term $category Term object.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @param string $size Image size, defaults to 'woocommerce_thumbnail'.
|
||||
* @return string
|
||||
*/
|
||||
public function get_image_html( $category, $attributes, $size = 'woocommerce_thumbnail' ) {
|
||||
if ( empty( $attributes['hasImage'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$image_id = get_term_meta( $category->term_id, 'thumbnail_id', true );
|
||||
|
||||
if ( ! $image_id ) {
|
||||
return '<span class="wc-block-product-categories-list-item__image wc-block-product-categories-list-item__image--placeholder">' . wc_placeholder_img( 'woocommerce_thumbnail' ) . '</span>';
|
||||
}
|
||||
|
||||
return '<span class="wc-block-product-categories-list-item__image">' . wp_get_attachment_image( $image_id, 'woocommerce_thumbnail' ) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the count, if displaying.
|
||||
*
|
||||
* @param object $category Term object.
|
||||
* @param array $attributes Block attributes. Default empty array.
|
||||
* @return string
|
||||
*/
|
||||
protected function getCount( $category, $attributes ) {
|
||||
if ( empty( $attributes['hasCount'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( $attributes['isDropdown'] ) {
|
||||
return '(' . absint( $category->count ) . ')';
|
||||
}
|
||||
|
||||
$screen_reader_text = sprintf(
|
||||
/* translators: %s number of products in cart. */
|
||||
_n( '%d product', '%d products', absint( $category->count ), 'woocommerce' ),
|
||||
absint( $category->count )
|
||||
);
|
||||
|
||||
return '<span class="wc-block-product-categories-list-item-count">'
|
||||
. '<span aria-hidden="true">' . absint( $category->count ) . '</span>'
|
||||
. '<span class="screen-reader-text">' . esc_html( $screen_reader_text ) . '</span>'
|
||||
. '</span>';
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductCategory class.
|
||||
*/
|
||||
class ProductCategory extends AbstractProductGrid {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-category';
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array_merge(
|
||||
parent::get_block_type_attributes(),
|
||||
array(
|
||||
'className' => $this->get_schema_string(),
|
||||
'orderby' => $this->get_schema_orderby(),
|
||||
'editMode' => $this->get_schema_boolean( true ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
25
packages/woocommerce-blocks/src/BlockTypes/ProductNew.php
Normal file
25
packages/woocommerce-blocks/src/BlockTypes/ProductNew.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductNew class.
|
||||
*/
|
||||
class ProductNew extends AbstractProductGrid {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-new';
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {
|
||||
$query_args['orderby'] = 'date';
|
||||
$query_args['order'] = 'DESC';
|
||||
}
|
||||
}
|
38
packages/woocommerce-blocks/src/BlockTypes/ProductOnSale.php
Normal file
38
packages/woocommerce-blocks/src/BlockTypes/ProductOnSale.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductOnSale class.
|
||||
*/
|
||||
class ProductOnSale extends AbstractProductGrid {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-on-sale';
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {
|
||||
$query_args['post__in'] = array_merge( array( 0 ), wc_get_product_ids_on_sale() );
|
||||
}
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array_merge(
|
||||
parent::get_block_type_attributes(),
|
||||
array(
|
||||
'className' => $this->get_schema_string(),
|
||||
'orderby' => $this->get_schema_orderby(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
127
packages/woocommerce-blocks/src/BlockTypes/ProductSearch.php
Normal file
127
packages/woocommerce-blocks/src/BlockTypes/ProductSearch.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductSearch class.
|
||||
*/
|
||||
class ProductSearch extends AbstractBlock {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-search';
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return null
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
static $instance_id = 0;
|
||||
|
||||
$attributes = wp_parse_args(
|
||||
$attributes,
|
||||
array(
|
||||
'hasLabel' => true,
|
||||
'align' => '',
|
||||
'className' => '',
|
||||
'label' => __( 'Search', 'woocommerce' ),
|
||||
'placeholder' => __( 'Search products…', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Product Search event.
|
||||
*
|
||||
* Listens for product search form submission, and on submission fires a WP Hook named
|
||||
* `experimental__woocommerce_blocks-product-search`. This can be used by tracking extensions such as Google
|
||||
* Analytics to track searches.
|
||||
*/
|
||||
$this->asset_api->add_inline_script(
|
||||
'wp-hooks',
|
||||
"
|
||||
window.addEventListener( 'DOMContentLoaded', () => {
|
||||
const forms = document.querySelectorAll( '.wc-block-product-search form' );
|
||||
|
||||
for ( const form of forms ) {
|
||||
form.addEventListener( 'submit', ( event ) => {
|
||||
const field = form.querySelector( '.wc-block-product-search__field' );
|
||||
|
||||
if ( field && field.value ) {
|
||||
wp.hooks.doAction( 'experimental__woocommerce_blocks-product-search', { event: event, searchTerm: field.value } );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} );
|
||||
",
|
||||
'after'
|
||||
);
|
||||
|
||||
$input_id = 'wc-block-search__input-' . ( ++$instance_id );
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => implode(
|
||||
' ',
|
||||
array_filter(
|
||||
[
|
||||
'wc-block-product-search',
|
||||
$attributes['align'] ? 'align' . $attributes['align'] : '',
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$label_markup = $attributes['hasLabel'] ? sprintf(
|
||||
'<label for="%s" class="wc-block-product-search__label">%s</label>',
|
||||
esc_attr( $input_id ),
|
||||
esc_html( $attributes['label'] )
|
||||
) : sprintf(
|
||||
'<label for="%s" class="wc-block-product-search__label screen-reader-text">%s</label>',
|
||||
esc_attr( $input_id ),
|
||||
esc_html( $attributes['label'] )
|
||||
);
|
||||
|
||||
$input_markup = sprintf(
|
||||
'<input type="search" id="%s" class="wc-block-product-search__field" placeholder="%s" name="s" />',
|
||||
esc_attr( $input_id ),
|
||||
esc_attr( $attributes['placeholder'] )
|
||||
);
|
||||
$button_markup = sprintf(
|
||||
'<button type="submit" class="wc-block-product-search__button" aria-label="%s">
|
||||
<svg aria-hidden="true" role="img" focusable="false" class="dashicon dashicons-arrow-right-alt2" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<path d="M6 15l5-5-5-5 1-2 7 7-7 7z" />
|
||||
</svg>
|
||||
</button>',
|
||||
esc_attr__( 'Search', 'woocommerce' )
|
||||
);
|
||||
|
||||
$field_markup = '
|
||||
<div class="wc-block-product-search__fields">
|
||||
' . $input_markup . $button_markup . '
|
||||
<input type="hidden" name="post_type" value="product" />
|
||||
</div>
|
||||
';
|
||||
|
||||
return sprintf(
|
||||
'<div %s><form role="search" method="get" action="%s">%s</form></div>',
|
||||
$wrapper_attributes,
|
||||
esc_url( home_url( '/' ) ),
|
||||
$label_markup . $field_markup
|
||||
);
|
||||
}
|
||||
}
|
69
packages/woocommerce-blocks/src/BlockTypes/ProductTag.php
Normal file
69
packages/woocommerce-blocks/src/BlockTypes/ProductTag.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductTag class.
|
||||
*/
|
||||
class ProductTag extends AbstractProductGrid {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-tag';
|
||||
|
||||
/**
|
||||
* Set args specific to this block.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {
|
||||
if ( ! empty( $this->attributes['tags'] ) ) {
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => 'product_tag',
|
||||
'terms' => array_map( 'absint', $this->attributes['tags'] ),
|
||||
'field' => 'id',
|
||||
'operator' => isset( $this->attributes['tagOperator'] ) && 'any' === $this->attributes['tagOperator'] ? 'IN' : 'AND',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array(
|
||||
'className' => $this->get_schema_string(),
|
||||
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
|
||||
'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
|
||||
'contentVisibility' => $this->get_schema_content_visibility(),
|
||||
'align' => $this->get_schema_align(),
|
||||
'alignButtons' => $this->get_schema_boolean( false ),
|
||||
'orderby' => $this->get_schema_orderby(),
|
||||
'tags' => $this->get_schema_list_ids(),
|
||||
'tagOperator' => array(
|
||||
'type' => 'string',
|
||||
'default' => 'any',
|
||||
),
|
||||
'isPreview' => $this->get_schema_boolean( false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
|
||||
$tag_count = wp_count_terms( 'product_tag' );
|
||||
|
||||
$this->asset_data_registry->add( 'hasTags', $tag_count > 0, true );
|
||||
$this->asset_data_registry->add( 'limitTags', $tag_count > 100, true );
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductTopRated class.
|
||||
*/
|
||||
class ProductTopRated extends AbstractProductGrid {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'product-top-rated';
|
||||
|
||||
/**
|
||||
* Force orderby to rating.
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {
|
||||
$query_args['orderby'] = 'rating';
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ProductsByAttribute class.
|
||||
*/
|
||||
class ProductsByAttribute extends AbstractProductGrid {
|
||||
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'products-by-attribute';
|
||||
|
||||
/**
|
||||
* Set args specific to this block
|
||||
*
|
||||
* @param array $query_args Query args.
|
||||
*/
|
||||
protected function set_block_query_args( &$query_args ) {
|
||||
if ( ! empty( $this->attributes['attributes'] ) ) {
|
||||
$taxonomy = sanitize_title( $this->attributes['attributes'][0]['attr_slug'] );
|
||||
$terms = wp_list_pluck( $this->attributes['attributes'], 'id' );
|
||||
|
||||
$query_args['tax_query'][] = array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'terms' => array_map( 'absint', $terms ),
|
||||
'field' => 'term_id',
|
||||
'operator' => 'all' === $this->attributes['attrOperator'] ? 'AND' : 'IN',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_type_attributes() {
|
||||
return array(
|
||||
'align' => $this->get_schema_align(),
|
||||
'alignButtons' => $this->get_schema_boolean( false ),
|
||||
'attributes' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'type' => 'number',
|
||||
),
|
||||
'attr_slug' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'default' => array(),
|
||||
),
|
||||
'attrOperator' => array(
|
||||
'type' => 'string',
|
||||
'default' => 'any',
|
||||
),
|
||||
'className' => $this->get_schema_string(),
|
||||
'columns' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_columns', 3 ) ),
|
||||
'contentVisibility' => $this->get_schema_content_visibility(),
|
||||
'editMode' => $this->get_schema_boolean( true ),
|
||||
'orderby' => $this->get_schema_orderby(),
|
||||
'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ),
|
||||
'isPreview' => $this->get_schema_boolean( false ),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ReviewsByCategory class.
|
||||
*/
|
||||
class ReviewsByCategory extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'reviews-by-category';
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-reviews-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true );
|
||||
$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true );
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* ReviewsByProduct class.
|
||||
*/
|
||||
class ReviewsByProduct extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'reviews-by-product';
|
||||
|
||||
/**
|
||||
* Get the frontend script handle for this block type.
|
||||
*
|
||||
* @see $this->register_block_type()
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string
|
||||
*/
|
||||
protected function get_block_type_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-reviews-block-frontend',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( 'reviews-frontend' ),
|
||||
'dependencies' => [],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $attributes Any attributes that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $attributes = [] ) {
|
||||
parent::enqueue_data( $attributes );
|
||||
$this->asset_data_registry->add( 'reviewRatingsEnabled', wc_review_ratings_enabled(), true );
|
||||
$this->asset_data_registry->add( 'showAvatars', '1' === get_option( 'show_avatars' ), true );
|
||||
}
|
||||
}
|
40
packages/woocommerce-blocks/src/BlockTypes/SingleProduct.php
Normal file
40
packages/woocommerce-blocks/src/BlockTypes/SingleProduct.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* SingleProduct class.
|
||||
*/
|
||||
class SingleProduct extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'single-product';
|
||||
|
||||
/**
|
||||
* Get the editor script handle for this block type.
|
||||
*
|
||||
* @param string $key Data to get, or default to everything.
|
||||
* @return array|string;
|
||||
*/
|
||||
protected function get_block_type_editor_script( $key = null ) {
|
||||
$script = [
|
||||
'handle' => 'wc-' . $this->block_name . '-block',
|
||||
'path' => $this->asset_api->get_block_asset_build_path( $this->block_name ),
|
||||
'dependencies' => [ 'wc-blocks' ],
|
||||
];
|
||||
return $key ? $script[ $key ] : $script;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the block on the frontend.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @param string $content Block content.
|
||||
* @return string Rendered block type output.
|
||||
*/
|
||||
protected function render( $attributes, $content ) {
|
||||
return $this->inject_html_data_attributes( $content, $attributes );
|
||||
}
|
||||
}
|
28
packages/woocommerce-blocks/src/BlockTypes/StockFilter.php
Normal file
28
packages/woocommerce-blocks/src/BlockTypes/StockFilter.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
/**
|
||||
* AttributeFilter class.
|
||||
*/
|
||||
class StockFilter extends AbstractBlock {
|
||||
/**
|
||||
* Block name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $block_name = 'stock-filter';
|
||||
|
||||
/**
|
||||
* Extra data passed through from server to client for block.
|
||||
*
|
||||
* @param array $stock_statuses Any stock statuses that currently are available from the block.
|
||||
* Note, this will be empty in the editor context when the block is
|
||||
* not in the post content on editor load.
|
||||
*/
|
||||
protected function enqueue_data( array $stock_statuses = [] ) {
|
||||
parent::enqueue_data( $stock_statuses );
|
||||
$this->asset_data_registry->add( 'stockStatusOptions', wc_get_product_stock_status_options(), true );
|
||||
$this->asset_data_registry->add( 'hideOutOfStockItems', 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ), true );
|
||||
|
||||
}
|
||||
}
|
228
packages/woocommerce-blocks/src/BlockTypesController.php
Normal file
228
packages/woocommerce-blocks/src/BlockTypesController.php
Normal file
@ -0,0 +1,228 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
|
||||
/**
|
||||
* BlockTypesController class.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @internal
|
||||
*/
|
||||
final class BlockTypesController {
|
||||
|
||||
/**
|
||||
* Instance of the asset API.
|
||||
*
|
||||
* @var AssetApi
|
||||
*/
|
||||
protected $asset_api;
|
||||
|
||||
/**
|
||||
* Instance of the asset data registry.
|
||||
*
|
||||
* @var AssetDataRegistry
|
||||
*/
|
||||
protected $asset_data_registry;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetApi $asset_api Instance of the asset API.
|
||||
* @param AssetDataRegistry $asset_data_registry Instance of the asset data registry.
|
||||
*/
|
||||
public function __construct( AssetApi $asset_api, AssetDataRegistry $asset_data_registry ) {
|
||||
$this->asset_api = $asset_api;
|
||||
$this->asset_data_registry = $asset_data_registry;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class features.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'init', array( $this, 'register_blocks' ) );
|
||||
add_filter( 'render_block', array( $this, 'add_data_attributes' ), 10, 2 );
|
||||
add_action( 'woocommerce_login_form_end', array( $this, 'redirect_to_field' ) );
|
||||
add_filter( 'widget_types_to_hide_from_legacy_widget_block', array( $this, 'hide_legacy_widgets_with_block_equivalent' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register blocks, hooking up assets and render functions as needed.
|
||||
*/
|
||||
public function register_blocks() {
|
||||
$block_types = $this->get_block_types();
|
||||
|
||||
foreach ( $block_types as $block_type ) {
|
||||
$block_type_class = __NAMESPACE__ . '\\BlockTypes\\' . $block_type;
|
||||
$block_type_instance = new $block_type_class( $this->asset_api, $this->asset_data_registry, new IntegrationRegistry() );
|
||||
}
|
||||
|
||||
foreach ( self::get_atomic_blocks() as $block_type ) {
|
||||
$block_type_instance = new AtomicBlock( $this->asset_api, $this->asset_data_registry, new IntegrationRegistry(), $block_type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add data- attributes to blocks when rendered if the block is under the woocommerce/ namespace.
|
||||
*
|
||||
* @param string $content Block content.
|
||||
* @param array $block Parsed block data.
|
||||
* @return string
|
||||
*/
|
||||
public function add_data_attributes( $content, $block ) {
|
||||
$block_name = $block['blockName'];
|
||||
$block_namespace = strtok( $block_name, '/' );
|
||||
|
||||
/**
|
||||
* WooCommerce Blocks Namespaces
|
||||
*
|
||||
* This hook defines which block namespaces should have block name and attribute data- attributes appended on render.
|
||||
*
|
||||
* @param array $allowed_namespaces List of namespaces.
|
||||
*/
|
||||
$allowed_namespaces = array_merge( [ 'woocommerce', 'woocommerce-checkout' ], (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_namespace', [] ) );
|
||||
|
||||
/**
|
||||
* WooCommerce Blocks Block Names
|
||||
*
|
||||
* This hook defines which block names should have block name and attribute data- attributes appended on render.
|
||||
*
|
||||
* @param array $allowed_namespaces List of namespaces.
|
||||
*/
|
||||
$allowed_blocks = (array) apply_filters( '__experimental_woocommerce_blocks_add_data_attributes_to_block', [] );
|
||||
|
||||
if ( ! in_array( $block_namespace, $allowed_namespaces, true ) && ! in_array( $block_name, $allowed_blocks, true ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$attributes = (array) $block['attrs'];
|
||||
$escaped_data_attributes = [
|
||||
'data-block-name="' . esc_attr( $block['blockName'] ) . '"',
|
||||
];
|
||||
|
||||
foreach ( $attributes as $key => $value ) {
|
||||
if ( is_bool( $value ) ) {
|
||||
$value = $value ? 'true' : 'false';
|
||||
}
|
||||
if ( ! is_scalar( $value ) ) {
|
||||
$value = wp_json_encode( $value );
|
||||
}
|
||||
$escaped_data_attributes[] = 'data-' . esc_attr( strtolower( preg_replace( '/(?<!\ )[A-Z]/', '-$0', $key ) ) ) . '="' . esc_attr( $value ) . '"';
|
||||
}
|
||||
|
||||
return preg_replace( '/^<div /', '<div ' . implode( ' ', $escaped_data_attributes ) . ' ', trim( $content ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a redirect field to the login form so blocks can redirect users after login.
|
||||
*/
|
||||
public function redirect_to_field() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
if ( empty( $_GET['redirect_to'] ) ) {
|
||||
return;
|
||||
}
|
||||
echo '<input type="hidden" name="redirect" value="' . esc_attr( esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) ) . '" />'; // phpcs:ignore WordPress.Security.NonceVerification
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide legacy widgets with a feature complete block equivalent in the inserter
|
||||
* and prevent them from showing as an option in the Legacy Widget block.
|
||||
*
|
||||
* @param array $widget_types An array of widgets hidden in core.
|
||||
* @return array $widget_types An array inluding the WooCommerce widgets to hide.
|
||||
*/
|
||||
public function hide_legacy_widgets_with_block_equivalent( $widget_types ) {
|
||||
array_push( $widget_types, 'woocommerce_product_search', 'woocommerce_product_categories', 'woocommerce_recent_reviews' );
|
||||
return $widget_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of block types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_block_types() {
|
||||
global $wp_version, $pagenow;
|
||||
|
||||
$block_types = [
|
||||
'AllReviews',
|
||||
'FeaturedCategory',
|
||||
'FeaturedProduct',
|
||||
'HandpickedProducts',
|
||||
'ProductBestSellers',
|
||||
'ProductCategories',
|
||||
'ProductCategory',
|
||||
'ProductNew',
|
||||
'ProductOnSale',
|
||||
'ProductsByAttribute',
|
||||
'ProductTopRated',
|
||||
'ReviewsByProduct',
|
||||
'ReviewsByCategory',
|
||||
'ProductSearch',
|
||||
'ProductTag',
|
||||
'AllProducts',
|
||||
'PriceFilter',
|
||||
'AttributeFilter',
|
||||
'StockFilter',
|
||||
'ActiveFilters',
|
||||
];
|
||||
|
||||
if ( Package::feature()->is_feature_plugin_build() ) {
|
||||
$block_types[] = 'Checkout';
|
||||
$block_types[] = 'Cart';
|
||||
}
|
||||
|
||||
if ( Package::feature()->is_experimental_build() ) {
|
||||
$block_types[] = 'SingleProduct';
|
||||
$block_types[] = 'CartI2';
|
||||
$block_types[] = 'MiniCart';
|
||||
}
|
||||
|
||||
/**
|
||||
* This disables specific blocks in Widget Areas by not registering them.
|
||||
*/
|
||||
if ( in_array( $pagenow, [ 'widgets.php', 'themes.php', 'customize.php' ], true ) ) {
|
||||
$block_types = array_diff(
|
||||
$block_types,
|
||||
[
|
||||
'AllProducts',
|
||||
'PriceFilter',
|
||||
'AttributeFilter',
|
||||
'StockFilter',
|
||||
'ActiveFilters',
|
||||
'Cart',
|
||||
'Checkout',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $block_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get atomic blocks types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_atomic_blocks() {
|
||||
return [
|
||||
'product-title',
|
||||
'product-button',
|
||||
'product-image',
|
||||
'product-price',
|
||||
'product-rating',
|
||||
'product-sale-badge',
|
||||
'product-summary',
|
||||
'product-sku',
|
||||
'product-category-list',
|
||||
'product-tag-list',
|
||||
'product-stock-indicator',
|
||||
'product-add-to-cart',
|
||||
];
|
||||
}
|
||||
}
|
333
packages/woocommerce-blocks/src/Domain/Bootstrap.php
Normal file
333
packages/woocommerce-blocks/src/Domain/Bootstrap.php
Normal file
@ -0,0 +1,333 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\AssetsController as AssetsController;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\BlockTypesController;
|
||||
use Automattic\WooCommerce\Blocks\InboxNotifications;
|
||||
use Automattic\WooCommerce\Blocks\Installer;
|
||||
use Automattic\WooCommerce\Blocks\Registry\Container;
|
||||
use Automattic\WooCommerce\Blocks\RestApi;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\DraftOrders;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Formatters;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Formatters\MoneyFormatter;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Formatters\HtmlFormatter;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Formatters\CurrencyFormatter;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\RoutesController;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\SchemaController;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\GoogleAnalytics;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Container $container The Dependency Injection Container.
|
||||
*/
|
||||
public function __construct( Container $container ) {
|
||||
$this->container = $container;
|
||||
$this->package = $container->get( Package::class );
|
||||
if ( $this->has_core_dependencies() ) {
|
||||
$this->init();
|
||||
/**
|
||||
* Usable as a safe event hook for when the plugin has been loaded.
|
||||
*/
|
||||
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();
|
||||
|
||||
add_action(
|
||||
'admin_init',
|
||||
function() {
|
||||
InboxNotifications::create_surface_cart_checkout_blocks_notification();
|
||||
},
|
||||
10,
|
||||
0
|
||||
);
|
||||
|
||||
$is_rest = wc()->is_rest_api_request();
|
||||
|
||||
// Load assets in admin and on the frontend.
|
||||
if ( ! $is_rest ) {
|
||||
$this->add_build_notice();
|
||||
$this->container->get( AssetDataRegistry::class );
|
||||
$this->container->get( Installer::class );
|
||||
$this->container->get( AssetsController::class );
|
||||
}
|
||||
$this->container->get( DraftOrders::class )->init();
|
||||
$this->container->get( CreateAccount::class )->init();
|
||||
$this->container->get( ExtendRestApi::class );
|
||||
$this->container->get( RestApi::class );
|
||||
$this->container->get( GoogleAnalytics::class );
|
||||
$this->container->get( BlockTypesController::class );
|
||||
if ( $this->package->feature()->is_feature_plugin_build() ) {
|
||||
$this->container->get( PaymentsApi::class );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check core dependencies exist.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function has_core_dependencies() {
|
||||
$has_needed_dependencies = class_exists( 'WooCommerce', false );
|
||||
if ( $has_needed_dependencies ) {
|
||||
$plugin_data = \get_file_data(
|
||||
$this->package->get_path( 'woocommerce-gutenberg-products-block.php' ),
|
||||
[
|
||||
'RequiredWCVersion' => 'WC requires at least',
|
||||
]
|
||||
);
|
||||
if ( isset( $plugin_data['RequiredWCVersion'] ) && version_compare( \WC()->version, $plugin_data['RequiredWCVersion'], '<' ) ) {
|
||||
$has_needed_dependencies = false;
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function() {
|
||||
if ( should_display_compatibility_notices() ) {
|
||||
?>
|
||||
<div class="notice notice-error">
|
||||
<p><?php esc_html_e( 'The WooCommerce Blocks feature plugin requires a more recent version of WooCommerce and has been paused. Please update WooCommerce to the latest version to continue enjoying WooCommerce Blocks.', 'woocommerce' ); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return $has_needed_dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if files have been built or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_built() {
|
||||
return file_exists(
|
||||
$this->package->get_path( 'build/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 install command, %2$s is the build command, %3$s is the watch command. */
|
||||
esc_html__( 'WooCommerce Blocks development mode requires files to be built. From the plugin directory, run %1$s to install dependencies, %2$s to build the files or %3$s to build the files and watch for changes.', 'woocommerce' ),
|
||||
'<code>npm install</code>',
|
||||
'<code>npm run build</code>',
|
||||
'<code>npm start</code>'
|
||||
);
|
||||
echo '</p></div>';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register core dependencies with the container.
|
||||
*/
|
||||
protected function register_dependencies() {
|
||||
$this->container->register(
|
||||
FeatureGating::class,
|
||||
function ( Container $container ) {
|
||||
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( Container $container ) {
|
||||
return new PaymentMethodRegistry();
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
RestApi::class,
|
||||
function ( Container $container ) {
|
||||
return new RestApi( $container->get( RoutesController::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
Installer::class,
|
||||
function ( Container $container ) {
|
||||
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(
|
||||
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(
|
||||
Formatters::class,
|
||||
function( Container $container ) {
|
||||
$formatters = new Formatters();
|
||||
$formatters->register( 'money', MoneyFormatter::class );
|
||||
$formatters->register( 'html', HtmlFormatter::class );
|
||||
$formatters->register( 'currency', CurrencyFormatter::class );
|
||||
return $formatters;
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
SchemaController::class,
|
||||
function( Container $container ) {
|
||||
return new SchemaController( $container->get( ExtendRestApi::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
RoutesController::class,
|
||||
function( Container $container ) {
|
||||
return new RoutesController( $container->get( SchemaController::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
ExtendRestApi::class,
|
||||
function( Container $container ) {
|
||||
return new ExtendRestApi( $container->get( Package::class ), $container->get( Formatters::class ) );
|
||||
}
|
||||
);
|
||||
$this->container->register(
|
||||
GoogleAnalytics::class,
|
||||
function( Container $container ) {
|
||||
// Require Google Analytics Integration to be activated.
|
||||
if ( ! class_exists( 'WC_Google_Analytics_Integration', false ) ) {
|
||||
return;
|
||||
}
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new GoogleAnalytics( $asset_api );
|
||||
}
|
||||
);
|
||||
if ( $this->package->feature()->is_feature_plugin_build() ) {
|
||||
$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 );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register payment method integrations with the container.
|
||||
*
|
||||
* @internal Stripe is a temporary method that is used for setting up payment method integrations with Cart and
|
||||
* Checkout blocks. This logic should get moved to the payment gateway extensions.
|
||||
*/
|
||||
protected function register_payment_methods() {
|
||||
$this->container->register(
|
||||
Stripe::class,
|
||||
function( Container $container ) {
|
||||
$asset_api = $container->get( AssetApi::class );
|
||||
return new Stripe( $asset_api );
|
||||
}
|
||||
);
|
||||
$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 );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
110
packages/woocommerce-blocks/src/Domain/Package.php
Normal file
110
packages/woocommerce-blocks/src/Domain/Package.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package as NewPackage;
|
||||
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 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 the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_version() {
|
||||
return $this->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 = '' ) {
|
||||
// Append index.php so WP does not return the parent directory.
|
||||
return plugin_dir_url( $this->path . '/index.php' ) . $relative_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use \WP_REST_Request;
|
||||
use \WC_Order;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\Email\CustomerNewAccount;
|
||||
|
||||
/**
|
||||
* Service class implementing new create account behaviour for order processing.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single method for feature gating logic. Used to gate all non-private methods.
|
||||
*
|
||||
* @return True if Checkout sign-up feature should be made available.
|
||||
*/
|
||||
private function is_feature_enabled() {
|
||||
// Checkout signup is feature gated to WooCommerce 4.7 and newer;
|
||||
// uses updated my-account/lost-password screen from 4.7+ for
|
||||
// setting initial password.
|
||||
// This service is feature gated to plugin only, to match the
|
||||
// availability of the Checkout block (feature plugin only).
|
||||
return $this->package->feature()->is_feature_plugin_build() && defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '4.7', '>=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Init - register handlers for WooCommerce core email hooks.
|
||||
*/
|
||||
public function init() {
|
||||
if ( ! self::is_feature_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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['is_checkout_block_customer_signup'] ) ) {
|
||||
$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() ) {
|
||||
if ( ! self::is_feature_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $customer_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$new_account_email = new CustomerNewAccount( $this->package );
|
||||
$new_account_email->trigger( $customer_id, $new_customer_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user account for specified request (if necessary).
|
||||
* If a new account is created:
|
||||
* - The user is logged in.
|
||||
*
|
||||
* @param \WP_REST_Request $request The current request object being handled.
|
||||
*
|
||||
* @throws Exception On error.
|
||||
* @return int The new user id, or 0 if no user was created.
|
||||
*/
|
||||
public function from_order_request( \WP_REST_Request $request ) {
|
||||
if ( ! self::is_feature_enabled() || ! $this->should_create_customer_account( $request ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$customer_id = $this->create_customer_account(
|
||||
$request['billing_address']['email'],
|
||||
$request['billing_address']['first_name'],
|
||||
$request['billing_address']['last_name']
|
||||
);
|
||||
// Log the customer in and associate with the order.
|
||||
wc_set_customer_auth_cookie( $customer_id );
|
||||
|
||||
return $customer_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check request options and store (shop) config to determine if a user account
|
||||
* should be created as part of order processing.
|
||||
*
|
||||
* @param \WP_REST_Request $request The current request object being handled.
|
||||
*
|
||||
* @return boolean True if a new user account should be created.
|
||||
*/
|
||||
protected function should_create_customer_account( \WP_REST_Request $request ) {
|
||||
if ( is_user_logged_in() ) {
|
||||
// User is already logged in - no need to create an account.
|
||||
return false;
|
||||
}
|
||||
|
||||
// From here we know that the shopper is not logged in.
|
||||
// check for whether account creation is enabled at the global level.
|
||||
$checkout = WC()->checkout();
|
||||
if ( ! $checkout instanceof \WC_Checkout ) {
|
||||
// If checkout class is not available, we have major problems, don't create account.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false === filter_var( $checkout->is_registration_enabled(), FILTER_VALIDATE_BOOLEAN ) ) {
|
||||
// Registration is not enabled for the store, so return false.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( true === filter_var( $checkout->is_registration_required(), FILTER_VALIDATE_BOOLEAN ) ) {
|
||||
// Store requires an account for all checkouts (purchases).
|
||||
// Create an account independent of shopper option in $request.
|
||||
// Note - checkbox is not displayed to shopper in this case.
|
||||
return true;
|
||||
}
|
||||
|
||||
// From here we know that the store allows guest checkout;
|
||||
// shopper can choose whether they sign up (`should_create_account`).
|
||||
|
||||
if ( true === filter_var( $request['should_create_account'], FILTER_VALIDATE_BOOLEAN ) ) {
|
||||
// User has requested an account as part of checkout processing.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an account creation error to an exception.
|
||||
*
|
||||
* @param \WP_Error $error An error object.
|
||||
*
|
||||
* @return Exception.
|
||||
*/
|
||||
private function map_create_account_error( \WP_Error $error ) {
|
||||
switch ( $error->get_error_code() ) {
|
||||
// WordPress core error codes.
|
||||
case 'empty_username':
|
||||
case 'invalid_username':
|
||||
case 'empty_email':
|
||||
case 'invalid_email':
|
||||
case 'email_exists':
|
||||
case 'registerfail':
|
||||
return new \Exception( 'woocommerce_rest_checkout_create_account_failure' );
|
||||
}
|
||||
|
||||
return new \Exception( 'woocommerce_rest_checkout_create_account_failure' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new account for a customer (using a new blocks-specific PHP API).
|
||||
*
|
||||
* The account is created with a generated username. The customer is sent
|
||||
* an email notifying them about the account and containing a link to set
|
||||
* their (initial) password.
|
||||
*
|
||||
* Intended as a replacement for wc_create_new_customer in WC core.
|
||||
*
|
||||
* @throws \Exception If an error is encountered when creating the user account.
|
||||
*
|
||||
* @param string $user_email The email address to use for the new account.
|
||||
* @param string $first_name The first name to use for the new account.
|
||||
* @param string $last_name The last name to use for the new account.
|
||||
*
|
||||
* @return int User id if successful
|
||||
*/
|
||||
private function create_customer_account( $user_email, $first_name, $last_name ) {
|
||||
if ( empty( $user_email ) || ! is_email( $user_email ) ) {
|
||||
throw new \Exception( 'registration-error-invalid-email' );
|
||||
}
|
||||
|
||||
if ( email_exists( $user_email ) ) {
|
||||
throw new \Exception( 'registration-error-email-exists' );
|
||||
}
|
||||
|
||||
$username = wc_create_new_customer_username( $user_email );
|
||||
|
||||
// Handle password creation.
|
||||
$password = wp_generate_password();
|
||||
$password_generated = true;
|
||||
|
||||
// Use WP_Error to handle registration errors.
|
||||
$errors = new \WP_Error();
|
||||
|
||||
do_action( 'woocommerce_register_post', $username, $user_email, $errors );
|
||||
|
||||
$errors = apply_filters( 'woocommerce_registration_errors', $errors, $username, $user_email );
|
||||
|
||||
if ( $errors->get_error_code() ) {
|
||||
throw new \Exception( $errors->get_error_code() );
|
||||
}
|
||||
|
||||
$new_customer_data = apply_filters(
|
||||
'woocommerce_new_customer_data',
|
||||
array(
|
||||
'is_checkout_block_customer_signup' => true,
|
||||
'user_login' => $username,
|
||||
'user_pass' => $password,
|
||||
'user_email' => $user_email,
|
||||
'first_name' => $first_name,
|
||||
'last_name' => $last_name,
|
||||
'role' => 'customer',
|
||||
)
|
||||
);
|
||||
|
||||
$customer_id = wp_insert_user( $new_customer_data );
|
||||
|
||||
if ( is_wp_error( $customer_id ) ) {
|
||||
throw $this->map_create_account_error( $customer_id );
|
||||
}
|
||||
|
||||
// Set account flag to remind customer to update generated password.
|
||||
update_user_option( $customer_id, 'default_password_nag', true, true );
|
||||
|
||||
do_action( 'woocommerce_created_customer', $customer_id, $new_customer_data, $password_generated );
|
||||
|
||||
return $customer_id;
|
||||
}
|
||||
}
|
259
packages/woocommerce-blocks/src/Domain/Services/DraftOrders.php
Normal file
259
packages/woocommerce-blocks/src/Domain/Services/DraftOrders.php
Normal file
@ -0,0 +1,259 @@
|
||||
<?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() {
|
||||
if ( $this->package->feature()->is_feature_plugin_build() ) {
|
||||
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' ] );
|
||||
} else {
|
||||
// Maybe remove existing cronjob if present because it shouldn't be needed in the environment.
|
||||
add_action( 'admin_init', [ $this, 'uninstall' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installation related logic for Draft order functionality.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function install() {
|
||||
$this->maybe_create_cronjobs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cronjobs if they exist (but only from admin).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function uninstall() {
|
||||
$this->maybe_remove_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' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule cron jobs that are present.
|
||||
*/
|
||||
protected function maybe_remove_cronjobs() {
|
||||
if ( function_exists( 'as_next_scheduled_action' ) && as_next_scheduled_action( 'woocommerce_cleanup_draft_orders' ) ) {
|
||||
as_unschedule_all_actions( '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,176 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services\Email;
|
||||
|
||||
use \WP_User;
|
||||
use \WC_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 = __( 'Customer "new account" emails are sent to the customer when a customer signs up via checkout or account blocks.', '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,379 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Domain\Services;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Routes\RouteException;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Formatters;
|
||||
use Throwable;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Service class to provide utility functions to extend REST API.
|
||||
*/
|
||||
final class ExtendRestApi {
|
||||
/**
|
||||
* List of Store API schema that is allowed to be extended by extensions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $endpoints = [
|
||||
\Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartItemSchema::IDENTIFIER,
|
||||
\Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema::IDENTIFIER,
|
||||
\Automattic\WooCommerce\Blocks\StoreApi\Schemas\CheckoutSchema::IDENTIFIER,
|
||||
];
|
||||
|
||||
/**
|
||||
* Holds the Package instance
|
||||
*
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* Holds the formatters class instance.
|
||||
*
|
||||
* @var Formatters
|
||||
*/
|
||||
private $formatters;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Package $package An instance of the package class.
|
||||
* @param Formatters $formatters An instance of the formatters class.
|
||||
*/
|
||||
public function __construct( Package $package, Formatters $formatters ) {
|
||||
$this->package = $package;
|
||||
$this->formatters = $formatters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatter instance.
|
||||
*
|
||||
* @param string $name Formatter name.
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
public function get_formatter( $name ) {
|
||||
return $this->formatters->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data to be extended
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $extend_data = [];
|
||||
|
||||
/**
|
||||
* Data to be extended
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $callback_methods = [];
|
||||
|
||||
/**
|
||||
* Array of payment requirements
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $payment_requirements = [];
|
||||
|
||||
/**
|
||||
* An endpoint that validates registration method call
|
||||
*
|
||||
* @param array $args {
|
||||
* An array of elements that make up a post to update or insert.
|
||||
*
|
||||
* @type string $endpoint The endpoint to extend.
|
||||
* @type string $namespace Plugin namespace.
|
||||
* @type callable $schema_callback Callback executed to add schema data.
|
||||
* @type callable $data_callback Callback executed to add endpoint data.
|
||||
* @type string $schema_type The type of data, object or array.
|
||||
* }
|
||||
*
|
||||
* @throws Exception On failure to register.
|
||||
* @return boolean True on success.
|
||||
*/
|
||||
public function register_endpoint_data( $args ) {
|
||||
if ( ! is_string( $args['namespace'] ) ) {
|
||||
$this->throw_exception( 'You must provide a plugin namespace when extending a Store REST endpoint.' );
|
||||
}
|
||||
|
||||
if ( ! is_string( $args['endpoint'] ) || ! in_array( $args['endpoint'], $this->endpoints, true ) ) {
|
||||
$this->throw_exception(
|
||||
sprintf( 'You must provide a valid Store REST endpoint to extend, valid endpoints are: %1$s. You provided %2$s.', implode( ', ', $this->endpoints ), $args['endpoint'] )
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $args['schema_callback'] ) && ! is_callable( $args['schema_callback'] ) ) {
|
||||
$this->throw_exception( '$schema_callback must be a callable function.' );
|
||||
}
|
||||
|
||||
if ( isset( $args['data_callback'] ) && ! is_callable( $args['data_callback'] ) ) {
|
||||
$this->throw_exception( '$data_callback must be a callable function.' );
|
||||
}
|
||||
|
||||
if ( isset( $args['schema_type'] ) && ! in_array( $args['schema_type'], [ ARRAY_N, ARRAY_A ], true ) ) {
|
||||
$this->throw_exception(
|
||||
sprintf( 'Data type must be either ARRAY_N for a numeric array or ARRAY_A for an object like array. You provided %1$s.', $args['schema_type'] )
|
||||
);
|
||||
}
|
||||
|
||||
$this->extend_data[ $args['endpoint'] ][ $args['namespace'] ] = [
|
||||
'schema_callback' => isset( $args['schema_callback'] ) ? $args['schema_callback'] : null,
|
||||
'data_callback' => isset( $args['data_callback'] ) ? $args['data_callback'] : null,
|
||||
'schema_type' => isset( $args['schema_type'] ) ? $args['schema_type'] : ARRAY_A,
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add callback functions that can be executed by the cart/extensions endpoint.
|
||||
*
|
||||
* @param array $args {
|
||||
* An array of elements that make up the callback configuration.
|
||||
*
|
||||
* @type string $endpoint The endpoint to extend.
|
||||
* @type string $namespace Plugin namespace.
|
||||
* @type callable $callback The function/callable to execute.
|
||||
* }
|
||||
*
|
||||
* @throws RouteException On failure to register.
|
||||
* @returns boolean True on success.
|
||||
*/
|
||||
public function register_update_callback( $args ) {
|
||||
if ( ! array_key_exists( 'namespace', $args ) || ! is_string( $args['namespace'] ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_extensions_error',
|
||||
'You must provide a plugin namespace when extending a Store REST endpoint.',
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( 'callback', $args ) || ! is_callable( $args['callback'] ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_extensions_error',
|
||||
'There is no valid callback supplied to register_update_callback.',
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
$this->callback_methods[ $args['namespace'] ] = [
|
||||
'callback' => $args['callback'],
|
||||
];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get callback for a specific endpoint and namespace.
|
||||
*
|
||||
* @param string $namespace The namespace to get callbacks for.
|
||||
*
|
||||
* @return callable The callback registered by the extension.
|
||||
* @throws RouteException When callback is not callable or parameters are incorrect.
|
||||
*/
|
||||
public function get_update_callback( $namespace ) {
|
||||
$method = null;
|
||||
if ( ! is_string( $namespace ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_extensions_error',
|
||||
'You must provide a plugin namespace when extending a Store REST endpoint.',
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( $namespace, $this->callback_methods ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_extensions_error',
|
||||
sprintf( 'There is no such namespace registered: %1$s.', $namespace ),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( 'callback', $this->callback_methods[ $namespace ] ) || ! is_callable( $this->callback_methods[ $namespace ]['callback'] ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_cart_extensions_error',
|
||||
sprintf( 'There is no valid callback registered for: %1$s.', $namespace ),
|
||||
400
|
||||
);
|
||||
}
|
||||
return $this->callback_methods[ $namespace ]['callback'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered endpoint data
|
||||
*
|
||||
* @param string $endpoint A valid identifier.
|
||||
* @param array $passed_args Passed arguments from the Schema class.
|
||||
* @return object Returns an casted object with registered endpoint data.
|
||||
* @throws Exception If a registered callback throws an error, or silently logs it.
|
||||
*/
|
||||
public function get_endpoint_data( $endpoint, array $passed_args = [] ) {
|
||||
$registered_data = [];
|
||||
if ( ! isset( $this->extend_data[ $endpoint ] ) ) {
|
||||
return (object) $registered_data;
|
||||
}
|
||||
foreach ( $this->extend_data[ $endpoint ] as $namespace => $callbacks ) {
|
||||
$data = [];
|
||||
|
||||
if ( is_null( $callbacks['data_callback'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $callbacks['data_callback']( ...$passed_args );
|
||||
|
||||
if ( ! is_array( $data ) ) {
|
||||
throw new Exception( '$data_callback must return an array.' );
|
||||
}
|
||||
} catch ( Throwable $e ) {
|
||||
$this->throw_exception( $e );
|
||||
continue;
|
||||
}
|
||||
|
||||
$registered_data[ $namespace ] = $data;
|
||||
}
|
||||
return (object) $registered_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered endpoint schema
|
||||
*
|
||||
* @param string $endpoint A valid identifier.
|
||||
* @param array $passed_args Passed arguments from the Schema class.
|
||||
* @return array Returns an array with registered schema data.
|
||||
* @throws Exception If a registered callback throws an error, or silently logs it.
|
||||
*/
|
||||
public function get_endpoint_schema( $endpoint, array $passed_args = [] ) {
|
||||
$registered_schema = [];
|
||||
if ( ! isset( $this->extend_data[ $endpoint ] ) ) {
|
||||
return (object) $registered_schema;
|
||||
}
|
||||
|
||||
foreach ( $this->extend_data[ $endpoint ] as $namespace => $callbacks ) {
|
||||
$schema = [];
|
||||
|
||||
if ( is_null( $callbacks['schema_callback'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$schema = $callbacks['schema_callback']( ...$passed_args );
|
||||
|
||||
if ( ! is_array( $schema ) ) {
|
||||
throw new Exception( '$schema_callback must return an array.' );
|
||||
}
|
||||
} catch ( Throwable $e ) {
|
||||
$this->throw_exception( $e );
|
||||
continue;
|
||||
}
|
||||
|
||||
$schema = $this->format_extensions_properties( $namespace, $schema, $callbacks['schema_type'] );
|
||||
|
||||
$registered_schema[ $namespace ] = $schema;
|
||||
}
|
||||
return (object) $registered_schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers and validates payment requirements callbacks.
|
||||
*
|
||||
* @param array $args {
|
||||
* Array of registration data.
|
||||
*
|
||||
* @type callable $data_callback Callback executed to add payment requirements data.
|
||||
* }
|
||||
*
|
||||
* @throws Exception On failure to register.
|
||||
* @return boolean True on success.
|
||||
*/
|
||||
public function register_payment_requirements( $args ) {
|
||||
if ( ! is_callable( $args['data_callback'] ) ) {
|
||||
$this->throw_exception( '$data_callback must be a callable function.' );
|
||||
}
|
||||
|
||||
$this->payment_requirements[] = $args['data_callback'];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional payment requirements.
|
||||
*
|
||||
* @param array $initial_requirements list of requirements that should be added to the collected requirements.
|
||||
* @return array Returns a list of payment requirements.
|
||||
* @throws Exception If a registered callback throws an error, or silently logs it.
|
||||
*/
|
||||
public function get_payment_requirements( array $initial_requirements = [ 'products' ] ) {
|
||||
$requirements = $initial_requirements;
|
||||
if ( empty( $this->payment_requirements ) ) {
|
||||
return $initial_requirements;
|
||||
}
|
||||
|
||||
foreach ( $this->payment_requirements as $callback ) {
|
||||
$data = [];
|
||||
|
||||
try {
|
||||
$data = $callback();
|
||||
|
||||
if ( ! is_array( $data ) ) {
|
||||
throw new Exception( '$data_callback must return an array.' );
|
||||
}
|
||||
} catch ( Throwable $e ) {
|
||||
$this->throw_exception( $e );
|
||||
continue;
|
||||
}
|
||||
$requirements = array_merge( $requirements, $data );
|
||||
}
|
||||
|
||||
return array_unique( $requirements );
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws error and/or silently logs it.
|
||||
*
|
||||
* @param string|Throwable $exception_or_error Error message or Exception.
|
||||
* @throws Exception An error to throw if we have debug enabled and user is admin.
|
||||
*/
|
||||
private function throw_exception( $exception_or_error ) {
|
||||
if ( is_string( $exception_or_error ) ) {
|
||||
$exception = new Exception( $exception_or_error );
|
||||
} else {
|
||||
$exception = $exception_or_error;
|
||||
}
|
||||
// Always log an error.
|
||||
wc_caught_exception( $exception );
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format schema for an extension.
|
||||
*
|
||||
* @param string $namespace Error message or Exception.
|
||||
* @param array $schema An error to throw if we have debug enabled and user is admin.
|
||||
* @param string $schema_type How should data be shaped.
|
||||
*
|
||||
* @return array Formatted schema.
|
||||
*/
|
||||
private function format_extensions_properties( $namespace, $schema, $schema_type ) {
|
||||
if ( ARRAY_N === $schema_type ) {
|
||||
return [
|
||||
/* translators: %s: extension namespace */
|
||||
'description' => sprintf( __( 'Extension data registered by %s', 'woocommerce' ), $namespace ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'items' => $schema,
|
||||
];
|
||||
}
|
||||
return [
|
||||
/* translators: %s: extension namespace */
|
||||
'description' => sprintf( __( 'Extension data registered by %s', 'woocommerce' ), $namespace ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'properties' => $schema,
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
<?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;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into WP.
|
||||
*/
|
||||
protected function init() {
|
||||
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', 'build/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();
|
||||
|
||||
// Require tracking to be enabled with a valid GA ID.
|
||||
if ( ! stristr( $settings['ga_id'], 'G-' ) || apply_filters( 'woocommerce_ga_disable_tracking', ! wc_string_to_bool( $settings['ga_event_tracking_enabled'] ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! wp_script_is( 'google-tag-manager', 'registered' ) ) {
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
wp_register_script( 'google-tag-manager', 'https://www.googletagmanager.com/gtag/js?id=' . $settings['ga_id'], [], null, false );
|
||||
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 );
|
||||
}
|
||||
}
|
147
packages/woocommerce-blocks/src/InboxNotifications.php
Normal file
147
packages/woocommerce-blocks/src/InboxNotifications.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Notes\Note;
|
||||
use Automattic\WooCommerce\Admin\Notes\Notes;
|
||||
|
||||
/**
|
||||
* A class used to display inbox messages to merchants in the WooCommerce Admin dashboard.
|
||||
*
|
||||
* @package Automattic\WooCommerce\Blocks
|
||||
* @since x.x.x
|
||||
*/
|
||||
class InboxNotifications {
|
||||
|
||||
const SURFACE_CART_CHECKOUT_NOTE_NAME = 'surface_cart_checkout';
|
||||
const SURFACE_CART_CHECKOUT_PROBABILITY_OPTION = 'wc_blocks_surface_cart_checkout_probability';
|
||||
const PERCENT_USERS_TO_TARGET = 50;
|
||||
const INELIGIBLE_EXTENSIONS = [
|
||||
'automatewoo',
|
||||
'mailchimp-for-woocommerce',
|
||||
'mailpoet',
|
||||
'klarna-payments-for-woocommerce',
|
||||
'klarna-checkout-for-woocommerce',
|
||||
'woocommerce-gutenberg-products-block', // Disallow the notification if the store is using the feature plugin already.
|
||||
'woocommerce-all-products-for-subscriptions',
|
||||
'woocommerce-bookings',
|
||||
'woocommerce-box-office',
|
||||
'woocommerce-cart-add-ons',
|
||||
'woocommerce-checkout-add-ons',
|
||||
'woocommerce-checkout-field-editor',
|
||||
'woocommerce-conditional-shipping-and-payments',
|
||||
'woocommerce-dynamic-pricing',
|
||||
'woocommerce-eu-vat-number',
|
||||
'woocommerce-follow-up-emails',
|
||||
'woocommerce-gateway-amazon-payments-advanced',
|
||||
'woocommerce-gateway-authorize-net-cim',
|
||||
'woocommerce-google-analytics-pro',
|
||||
'woocommerce-memberships',
|
||||
'woocommerce-paypal-payments',
|
||||
'woocommerce-pre-orders',
|
||||
'woocommerce-product-bundles',
|
||||
'woocommerce-shipping-fedex',
|
||||
'woocommerce-smart-coupons',
|
||||
];
|
||||
const ELIGIBLE_COUNTRIES = [
|
||||
'GB',
|
||||
'US',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Deletes the note.
|
||||
*/
|
||||
public static function delete_surface_cart_checkout_blocks_notification() {
|
||||
Notes::delete_notes_with_name( self::SURFACE_CART_CHECKOUT_NOTE_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a notification letting merchants know about the Cart and Checkout Blocks.
|
||||
*/
|
||||
public static function create_surface_cart_checkout_blocks_notification() {
|
||||
|
||||
// If this is the feature plugin, then we don't need to do this. This should only show when Blocks is bundled
|
||||
// with WooCommerce Core.
|
||||
if ( Package::feature()->is_feature_plugin_build() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WC_Data_Store' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data_store = \WC_Data_Store::load( 'admin-note' );
|
||||
$note_ids = $data_store->get_notes_with_name( self::SURFACE_CART_CHECKOUT_NOTE_NAME );
|
||||
|
||||
// Calculate store's eligibility to be shown the notice, starting with whether they have any plugins we know to
|
||||
// be incompatible with Blocks. This check is done before checking if the note exists already because we want to
|
||||
// delete the note if the merchant activates an ineligible plugin.
|
||||
foreach ( self::INELIGIBLE_EXTENSIONS as $extension ) {
|
||||
if ( is_plugin_active( $extension . '/' . $extension . '.php' ) ) {
|
||||
|
||||
// Delete the notification here, we shouldn't show it if it's not going to work with the merchant's site.
|
||||
self::delete_surface_cart_checkout_blocks_notification();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( (array) $note_ids as $note_id ) {
|
||||
$note = Notes::get_note( $note_id );
|
||||
|
||||
// Return now because the note already exists.
|
||||
if ( $note->get_name() === self::SURFACE_CART_CHECKOUT_NOTE_NAME ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Next check the store is located in one of the eligible countries.
|
||||
$raw_country = get_option( 'woocommerce_default_country' );
|
||||
$country = explode( ':', $raw_country )[0];
|
||||
if ( ! in_array( $country, self::ELIGIBLE_COUNTRIES, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick a random number between 1 and 100 and add this to the wp_options table. This can then be used to target
|
||||
// a percentage of users. We do this here so we target a truer percentage of eligible users than if we did it
|
||||
// before checking plugins/country.
|
||||
$existing_probability = get_option( self::SURFACE_CART_CHECKOUT_PROBABILITY_OPTION );
|
||||
if ( false === $existing_probability ) {
|
||||
$existing_probability = wp_rand( 0, 100 );
|
||||
add_option( self::SURFACE_CART_CHECKOUT_PROBABILITY_OPTION, $existing_probability );
|
||||
}
|
||||
|
||||
// Finally, check if the store's generated % chance is below the % of users we want to surface this to.
|
||||
if ( $existing_probability > self::PERCENT_USERS_TO_TARGET ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, the store meets all the criteria to be shown the notice! Woo!
|
||||
$note = new Note();
|
||||
$note->set_title(
|
||||
__(
|
||||
'Introducing the Cart and Checkout blocks!',
|
||||
'woocommerce'
|
||||
)
|
||||
);
|
||||
$note->set_content(
|
||||
__(
|
||||
"Increase your store's revenue with the conversion optimized Cart & Checkout WooCommerce blocks available in the WooCommerce Blocks extension.",
|
||||
'woocommerce'
|
||||
)
|
||||
);
|
||||
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
|
||||
$note->set_source( 'woo-gutenberg-products-block' );
|
||||
$note->set_name( self::SURFACE_CART_CHECKOUT_NOTE_NAME );
|
||||
$note->add_action(
|
||||
'learn_more',
|
||||
'Learn More',
|
||||
'https://woocommerce.com/checkout-blocks/'
|
||||
);
|
||||
$note->save();
|
||||
|
||||
}
|
||||
}
|
117
packages/woocommerce-blocks/src/Installer.php
Normal file
117
packages/woocommerce-blocks/src/Installer.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
/**
|
||||
* Installer class.
|
||||
* Handles installation of Blocks plugin dependencies.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Installer {
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installation tasks ran on admin_init callback.
|
||||
*/
|
||||
public function install() {
|
||||
$this->maybe_create_tables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class features.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'admin_init', array( $this, 'install' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the database tables which the plugin needs to function.
|
||||
*/
|
||||
public function maybe_create_tables() {
|
||||
global $wpdb;
|
||||
|
||||
$schema_version = 260;
|
||||
$db_schema_version = (int) get_option( 'wc_blocks_db_schema_version', 0 );
|
||||
|
||||
if ( $db_schema_version >= $schema_version && 0 !== $db_schema_version ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$show_errors = $wpdb->hide_errors();
|
||||
$table_name = $wpdb->prefix . 'wc_reserved_stock';
|
||||
$collate = $wpdb->has_cap( 'collation' ) ? $wpdb->get_charset_collate() : '';
|
||||
$exists = $this->maybe_create_table(
|
||||
$wpdb->prefix . 'wc_reserved_stock',
|
||||
"
|
||||
CREATE TABLE {$wpdb->prefix}wc_reserved_stock (
|
||||
`order_id` bigint(20) NOT NULL,
|
||||
`product_id` bigint(20) NOT NULL,
|
||||
`stock_quantity` double NOT NULL DEFAULT 0,
|
||||
`timestamp` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`expires` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`order_id`, `product_id`)
|
||||
) $collate;
|
||||
"
|
||||
);
|
||||
|
||||
if ( $show_errors ) {
|
||||
$wpdb->show_errors();
|
||||
}
|
||||
|
||||
if ( ! $exists ) {
|
||||
return $this->add_create_table_notice( $table_name );
|
||||
}
|
||||
|
||||
// Update succeeded. This is only updated when successful and validated.
|
||||
// $schema_version should be incremented when changes to schema are made within this method.
|
||||
update_option( 'wc_blocks_db_schema_version', $schema_version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create database table, if it doesn't already exist.
|
||||
*
|
||||
* Based on admin/install-helper.php maybe_create_table function.
|
||||
*
|
||||
* @param string $table_name Database table name.
|
||||
* @param string $create_sql Create database table SQL.
|
||||
* @return bool False on error, true if already exists or success.
|
||||
*/
|
||||
protected function maybe_create_table( $table_name, $create_sql ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( in_array( $table_name, $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ), 0 ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$wpdb->query( $create_sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
return in_array( $table_name, $wpdb->get_col( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ), 0 ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a notice if table creation fails.
|
||||
*
|
||||
* @param string $table_name Name of the missing table.
|
||||
*/
|
||||
protected function add_create_table_notice( $table_name ) {
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function() use ( $table_name ) {
|
||||
echo '<div class="error"><p>';
|
||||
printf(
|
||||
/* translators: %1$s table name, %2$s database user, %3$s database name. */
|
||||
esc_html__( 'WooCommerce %1$s table creation failed. Does the %2$s user have CREATE privileges on the %3$s database?', 'woocommerce' ),
|
||||
'<code>' . esc_html( $table_name ) . '</code>',
|
||||
'<code>' . esc_html( DB_USER ) . '</code>',
|
||||
'<code>' . esc_html( DB_NAME ) . '</code>'
|
||||
);
|
||||
echo '</p></div>';
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Integrations;
|
||||
|
||||
/**
|
||||
* Integration.Interface
|
||||
*
|
||||
* Integrations must use this interface when registering themselves with blocks,
|
||||
*/
|
||||
interface IntegrationInterface {
|
||||
/**
|
||||
* The name of the integration.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name();
|
||||
|
||||
/**
|
||||
* When called invokes any initialization/setup for the integration.
|
||||
*/
|
||||
public function initialize();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the frontend context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_script_handles();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the editor context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_editor_script_handles();
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to the block on the client side.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_script_data();
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Integrations;
|
||||
|
||||
/**
|
||||
* Class used for tracking registered integrations with various Block types.
|
||||
*/
|
||||
class IntegrationRegistry {
|
||||
/**
|
||||
* Integration identifier is used to construct hook names and is given when the integration registry is initialized.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $registry_identifier = '';
|
||||
|
||||
/**
|
||||
* Registered integrations, as `$name => $instance` pairs.
|
||||
*
|
||||
* @var IntegrationInterface[]
|
||||
*/
|
||||
protected $registered_integrations = [];
|
||||
|
||||
/**
|
||||
* Initializes all registered integrations.
|
||||
*
|
||||
* Integration identifier is used to construct hook names and is given when the integration registry is initialized.
|
||||
*
|
||||
* @param string $registry_identifier Identifier for this registry.
|
||||
*/
|
||||
public function initialize( $registry_identifier = '' ) {
|
||||
if ( $registry_identifier ) {
|
||||
$this->registry_identifier = $registry_identifier;
|
||||
}
|
||||
|
||||
if ( empty( $this->registry_identifier ) ) {
|
||||
_doing_it_wrong( __METHOD__, esc_html( __( 'Integration registry requires an identifier.', 'woocommerce' ) ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook: integration_registration.
|
||||
*
|
||||
* Runs before integrations are initialized allowing new integration to be registered for use. This should be
|
||||
* used as the primary hook for integrations to include their scripts, styles, and other code extending the
|
||||
* blocks.
|
||||
*
|
||||
* @param IntegrationRegistry $this Instance of the IntegrationRegistry class which exposes the IntegrationRegistry::register() method.
|
||||
*/
|
||||
do_action( 'woocommerce_blocks_' . $this->registry_identifier . '_registration', $this );
|
||||
|
||||
foreach ( $this->get_all_registered() as $registered_integration ) {
|
||||
$registered_integration->initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an integration.
|
||||
*
|
||||
* @param IntegrationInterface $integration An instance of IntegrationInterface.
|
||||
*
|
||||
* @return boolean True means registered successfully.
|
||||
*/
|
||||
public function register( IntegrationInterface $integration ) {
|
||||
$name = $integration->get_name();
|
||||
|
||||
if ( $this->is_registered( $name ) ) {
|
||||
/* translators: %s: Integration name. */
|
||||
_doing_it_wrong( __METHOD__, esc_html( sprintf( __( '"%s" is already registered.', 'woocommerce' ), $name ) ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->registered_integrations[ $name ] = $integration;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an integration is already registered.
|
||||
*
|
||||
* @param string $name Integration name.
|
||||
* @return bool True if the integration is registered, false otherwise.
|
||||
*/
|
||||
public function is_registered( $name ) {
|
||||
return isset( $this->registered_integrations[ $name ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-register an integration.
|
||||
*
|
||||
* @param string|IntegrationInterface $name Integration name, or alternatively a IntegrationInterface instance.
|
||||
* @return boolean|IntegrationInterface Returns the unregistered integration instance if unregistered successfully.
|
||||
*/
|
||||
public function unregister( $name ) {
|
||||
if ( $name instanceof IntegrationInterface ) {
|
||||
$name = $name->get_name();
|
||||
}
|
||||
|
||||
if ( ! $this->is_registered( $name ) ) {
|
||||
/* translators: %s: Integration name. */
|
||||
_doing_it_wrong( __METHOD__, esc_html( sprintf( __( 'Integration "%s" is not registered.', 'woocommerce' ), $name ) ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
$unregistered = $this->registered_integrations[ $name ];
|
||||
unset( $this->registered_integrations[ $name ] );
|
||||
return $unregistered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a registered Integration by name.
|
||||
*
|
||||
* @param string $name Integration name.
|
||||
* @return IntegrationInterface|null The registered integration, or null if it is not registered.
|
||||
*/
|
||||
public function get_registered( $name ) {
|
||||
return $this->is_registered( $name ) ? $this->registered_integrations[ $name ] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all registered integrations.
|
||||
*
|
||||
* @return IntegrationInterface[]
|
||||
*/
|
||||
public function get_all_registered() {
|
||||
return $this->registered_integrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered integration's script handles for the editor.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_all_registered_editor_script_handles() {
|
||||
$script_handles = [];
|
||||
$registered_integrations = $this->get_all_registered();
|
||||
|
||||
foreach ( $registered_integrations as $registered_integration ) {
|
||||
$script_handles = array_merge(
|
||||
$script_handles,
|
||||
$registered_integration->get_editor_script_handles()
|
||||
);
|
||||
}
|
||||
|
||||
return array_unique( array_filter( $script_handles ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered integration's script handles.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_all_registered_script_handles() {
|
||||
$script_handles = [];
|
||||
$registered_integrations = $this->get_all_registered();
|
||||
|
||||
foreach ( $registered_integrations as $registered_integration ) {
|
||||
$script_handles = array_merge(
|
||||
$script_handles,
|
||||
$registered_integration->get_script_handles()
|
||||
);
|
||||
}
|
||||
|
||||
return array_unique( array_filter( $script_handles ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered integration's script data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_registered_script_data() {
|
||||
$script_data = [];
|
||||
$registered_integrations = $this->get_all_registered();
|
||||
|
||||
foreach ( $registered_integrations as $registered_integration ) {
|
||||
$script_data[ $registered_integration->get_name() . '_data' ] = $registered_integration->get_script_data();
|
||||
}
|
||||
|
||||
return array_filter( $script_data );
|
||||
}
|
||||
}
|
44
packages/woocommerce-blocks/src/Library.php
Normal file
44
packages/woocommerce-blocks/src/Library.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\BlockTypes\AtomicBlock;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api as AssetApi;
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
|
||||
/**
|
||||
* Library class.
|
||||
*
|
||||
* @deprecated 5.0.0 This class will be removed in a future release. This has been replaced by BlockTypesController.
|
||||
* @internal
|
||||
*/
|
||||
class Library {
|
||||
|
||||
/**
|
||||
* Initialize block library features.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function init() {
|
||||
_deprecated_function( 'Library::init', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom tables within $wpdb object.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function define_tables() {
|
||||
_deprecated_function( 'Library::define_tables', '5.0.0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register blocks, hooking up assets and render functions as needed.
|
||||
*
|
||||
* @deprecated 5.0.0
|
||||
*/
|
||||
public static function register_blocks() {
|
||||
_deprecated_function( 'Library::register_blocks', '5.0.0' );
|
||||
}
|
||||
}
|
129
packages/woocommerce-blocks/src/Package.php
Normal file
129
packages/woocommerce-blocks/src/Package.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Domain\Package as NewPackage;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Bootstrap;
|
||||
use Automattic\WooCommerce\Blocks\Registry\Container;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\FeatureGating;
|
||||
|
||||
/**
|
||||
* Main package class.
|
||||
*
|
||||
* Returns information about the package and handles init.
|
||||
*
|
||||
* In the context of this plugin, it handles init and is called from the main
|
||||
* plugin file (woocommerce-gutenberg-products-block.php).
|
||||
*
|
||||
* In the context of WooCommere core, it handles init and is called from
|
||||
* WooCommerce's package loader. The main plugin file is _not_ loaded.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Package {
|
||||
|
||||
/**
|
||||
* For back compat this is provided. Ideally, you should register your
|
||||
* class with Automattic\Woocommerce\Blocks\Container and make Package a
|
||||
* dependency.
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @return Package The Package instance class
|
||||
*/
|
||||
protected static function get_package() {
|
||||
return self::container()->get( NewPackage::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the package - load the blocks library and define constants.
|
||||
*
|
||||
* @since 2.5.0 Handled by new NewPackage.
|
||||
*/
|
||||
public static function init() {
|
||||
self::container()->get( Bootstrap::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version of the package.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_version() {
|
||||
return self::get_package()->get_version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path to the package.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_path() {
|
||||
return self::get_package()->get_path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the the FeatureGating class.
|
||||
*
|
||||
* @return FeatureGating
|
||||
*/
|
||||
public static function feature() {
|
||||
return self::get_package()->feature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_experimental_build() {
|
||||
return self::get_package()->is_experimental_build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we're executing the code in an feature plugin or experimental build mode.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_feature_plugin_build() {
|
||||
return self::get_package()->is_feature_plugin_build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the dependency injection container for woocommerce blocks.
|
||||
*
|
||||
* @param boolean $reset Used to reset the container to a fresh instance.
|
||||
* Note: this means all dependencies will be
|
||||
* reconstructed.
|
||||
*/
|
||||
public static function container( $reset = false ) {
|
||||
static $container;
|
||||
if (
|
||||
! $container instanceof Container
|
||||
|| $reset
|
||||
) {
|
||||
$container = new Container();
|
||||
// register Package.
|
||||
$container->register(
|
||||
NewPackage::class,
|
||||
function ( $container ) {
|
||||
// leave for automated version bumping.
|
||||
$version = '6.1.0';
|
||||
return new NewPackage(
|
||||
$version,
|
||||
dirname( __DIR__ ),
|
||||
new FeatureGating()
|
||||
);
|
||||
}
|
||||
);
|
||||
// register Bootstrap.
|
||||
$container->register(
|
||||
Bootstrap::class,
|
||||
function ( $container ) {
|
||||
return new Bootstrap(
|
||||
$container
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
return $container;
|
||||
}
|
||||
}
|
235
packages/woocommerce-blocks/src/Payments/Api.php
Normal file
235
packages/woocommerce-blocks/src/Payments/Api.php
Normal file
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\NoticeHandler;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer;
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\CashOnDelivery;
|
||||
|
||||
/**
|
||||
* The Api class provides an interface to payment method registration.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
class Api {
|
||||
/**
|
||||
* Reference to the PaymentMethodRegistry instance.
|
||||
*
|
||||
* @var PaymentMethodRegistry
|
||||
*/
|
||||
private $payment_method_registry;
|
||||
|
||||
/**
|
||||
* Reference to the AssetDataRegistry instance.
|
||||
*
|
||||
* @var AssetDataRegistry
|
||||
*/
|
||||
private $asset_registry;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param PaymentMethodRegistry $payment_method_registry An instance of Payment Method Registry.
|
||||
* @param AssetDataRegistry $asset_registry Used for registering data to pass along to the request.
|
||||
*/
|
||||
public function __construct( PaymentMethodRegistry $payment_method_registry, AssetDataRegistry $asset_registry ) {
|
||||
$this->payment_method_registry = $payment_method_registry;
|
||||
$this->asset_registry = $asset_registry;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class features.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'init', array( $this->payment_method_registry, 'initialize' ), 5 );
|
||||
add_filter( 'woocommerce_blocks_register_script_dependencies', array( $this, 'add_payment_method_script_dependencies' ), 10, 2 );
|
||||
add_action( 'woocommerce_blocks_checkout_enqueue_data', array( $this, 'add_payment_method_script_data' ) );
|
||||
add_action( 'woocommerce_blocks_cart_enqueue_data', array( $this, 'add_payment_method_script_data' ) );
|
||||
add_action( 'woocommerce_blocks_payment_method_type_registration', array( $this, 'register_payment_method_integrations' ) );
|
||||
add_action( 'woocommerce_rest_checkout_process_payment_with_context', array( $this, 'process_legacy_payment' ), 999, 2 );
|
||||
add_action( 'wp_print_scripts', array( $this, 'verify_payment_methods_dependencies' ), 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add payment method script handles as script dependencies.
|
||||
*
|
||||
* @param array $dependencies Array of script dependencies.
|
||||
* @param string $handle Script handle.
|
||||
* @return array
|
||||
*/
|
||||
public function add_payment_method_script_dependencies( $dependencies, $handle ) {
|
||||
if ( ! in_array( $handle, [ 'wc-checkout-block', 'wc-checkout-block-frontend', 'wc-cart-block', 'wc-cart-block-frontend', 'wc-cart-i2-block', 'wc-cart-i2-block-frontend' ], true ) ) {
|
||||
return $dependencies;
|
||||
}
|
||||
return array_merge( $dependencies, $this->payment_method_registry->get_all_active_payment_method_script_dependencies() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the payment gateway is enabled.
|
||||
*
|
||||
* @param object $gateway Payment gateway.
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_payment_gateway_enabled( $gateway ) {
|
||||
return filter_var( $gateway->enabled, FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add payment method data to Asset Registry.
|
||||
*/
|
||||
public function add_payment_method_script_data() {
|
||||
// Enqueue the order of enabled gateways as `paymentGatewaySortOrder`.
|
||||
if ( ! $this->asset_registry->exists( 'paymentGatewaySortOrder' ) ) {
|
||||
$payment_gateways = WC()->payment_gateways->payment_gateways();
|
||||
$enabled_gateways = array_filter( $payment_gateways, array( $this, 'is_payment_gateway_enabled' ) );
|
||||
$this->asset_registry->add( 'paymentGatewaySortOrder', array_keys( $enabled_gateways ) );
|
||||
}
|
||||
|
||||
// Enqueue all registered gateway data (settings/config etc).
|
||||
$script_data = $this->payment_method_registry->get_all_registered_script_data();
|
||||
foreach ( $script_data as $asset_data_key => $asset_data_value ) {
|
||||
if ( ! $this->asset_registry->exists( $asset_data_key ) ) {
|
||||
$this->asset_registry->add( $asset_data_key, $asset_data_value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register payment method integrations bundled with blocks.
|
||||
*
|
||||
* @param PaymentMethodRegistry $payment_method_registry Payment method registry instance.
|
||||
*/
|
||||
public function register_payment_method_integrations( PaymentMethodRegistry $payment_method_registry ) {
|
||||
// This is temporarily registering Stripe until it's moved to the extension.
|
||||
if ( class_exists( '\WC_Stripe', false ) && ! $payment_method_registry->is_registered( 'stripe' ) ) {
|
||||
$payment_method_registry->register(
|
||||
Package::container()->get( Stripe::class )
|
||||
);
|
||||
}
|
||||
$payment_method_registry->register(
|
||||
Package::container()->get( Cheque::class )
|
||||
);
|
||||
$payment_method_registry->register(
|
||||
Package::container()->get( PayPal::class )
|
||||
);
|
||||
$payment_method_registry->register(
|
||||
Package::container()->get( BankTransfer::class )
|
||||
);
|
||||
$payment_method_registry->register(
|
||||
Package::container()->get( CashOnDelivery::class )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to process a payment for the checkout API if no payment methods support the
|
||||
* woocommerce_rest_checkout_process_payment_with_context action.
|
||||
*
|
||||
* @param PaymentContext $context Holds context for the payment.
|
||||
* @param PaymentResult $result Result of the payment.
|
||||
*/
|
||||
public function process_legacy_payment( PaymentContext $context, PaymentResult &$result ) {
|
||||
if ( $result->status ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$post_data = $_POST;
|
||||
|
||||
// Set constants.
|
||||
wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true );
|
||||
|
||||
// Add the payment data from the API to the POST global.
|
||||
$_POST = $context->payment_data;
|
||||
|
||||
// Call the process payment method of the chosen gateway.
|
||||
$payment_method_object = $context->get_payment_method_instance();
|
||||
|
||||
if ( ! $payment_method_object instanceof \WC_Payment_Gateway ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment_method_object->validate_fields();
|
||||
|
||||
// If errors were thrown, we need to abort.
|
||||
NoticeHandler::convert_notices_to_exceptions( 'woocommerce_rest_payment_error' );
|
||||
|
||||
// Process Payment.
|
||||
$gateway_result = $payment_method_object->process_payment( $context->order->get_id() );
|
||||
|
||||
// Restore $_POST data.
|
||||
$_POST = $post_data;
|
||||
|
||||
// If `process_payment` added notices, clear them. Notices are not displayed from the API -- payment should fail,
|
||||
// and a generic notice will be shown instead if payment failed.
|
||||
wc_clear_notices();
|
||||
|
||||
// Handle result.
|
||||
$result->set_status( isset( $gateway_result['result'] ) && 'success' === $gateway_result['result'] ? 'success' : 'failure' );
|
||||
|
||||
// set payment_details from result.
|
||||
$result->set_payment_details( array_merge( $result->payment_details, $gateway_result ) );
|
||||
$result->set_redirect_url( $gateway_result['redirect'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify all dependencies of registered payment methods have been registered.
|
||||
* If not, remove that payment method script from the list of dependencies
|
||||
* of Cart and Checkout block scripts so it doesn't break the blocks and show
|
||||
* an error in the admin.
|
||||
*/
|
||||
public function verify_payment_methods_dependencies() {
|
||||
$wp_scripts = wp_scripts();
|
||||
$payment_method_scripts = $this->payment_method_registry->get_all_active_payment_method_script_dependencies();
|
||||
|
||||
foreach ( $payment_method_scripts as $payment_method_script ) {
|
||||
if (
|
||||
! array_key_exists( $payment_method_script, $wp_scripts->registered ) ||
|
||||
! property_exists( $wp_scripts->registered[ $payment_method_script ], 'deps' )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$deps = $wp_scripts->registered[ $payment_method_script ]->deps;
|
||||
foreach ( $deps as $dep ) {
|
||||
if ( ! wp_script_is( $dep, 'registered' ) ) {
|
||||
$error_handle = $dep . '-dependency-error';
|
||||
$error_message = sprintf(
|
||||
'Payment gateway with handle \'%1$s\' has been deactivated in Cart and Checkout blocks because its dependency \'%2$s\' is not registered. Read the docs about registering assets for payment methods: https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/extensibility/payment-method-integration.md#registering-assets',
|
||||
esc_html( $payment_method_script ),
|
||||
esc_html( $dep )
|
||||
);
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( $error_message );
|
||||
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter,WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
||||
wp_register_script( $error_handle, '' );
|
||||
wp_enqueue_script( $error_handle );
|
||||
wp_add_inline_script(
|
||||
$error_handle,
|
||||
sprintf( 'console.error( "%s" );', $error_message )
|
||||
);
|
||||
|
||||
$cart_checkout_scripts = [ 'wc-cart-block', 'wc-cart-block-frontend', 'wc-checkout-block', 'wc-checkout-block-frontend', 'wc-cart-i2-block', 'wc-cart-i2-block-frontend' ];
|
||||
foreach ( $cart_checkout_scripts as $script_handle ) {
|
||||
if (
|
||||
! array_key_exists( $script_handle, $wp_scripts->registered ) ||
|
||||
! property_exists( $wp_scripts->registered[ $script_handle ], 'deps' )
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Remove payment method script from dependencies.
|
||||
$wp_scripts->registered[ $script_handle ]->deps = array_diff(
|
||||
$wp_scripts->registered[ $script_handle ]->deps,
|
||||
[ $payment_method_script ]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface;
|
||||
|
||||
/**
|
||||
* AbstractPaymentMethodType class.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
abstract class AbstractPaymentMethodType implements PaymentMethodTypeInterface {
|
||||
/**
|
||||
* Payment method name defined by payment methods extending this class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* Settings from the WP options table
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [];
|
||||
|
||||
/**
|
||||
* Get a setting from the settings array if set.
|
||||
*
|
||||
* @param string $name Setting name.
|
||||
* @param mixed $default Value that is returned if the setting does not exist.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_setting( $name, $default = '' ) {
|
||||
return isset( $this->settings[ $name ] ) ? $this->settings[ $name ] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the payment method.
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the frontend context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the admin context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles_for_admin() {
|
||||
return $this->get_payment_method_script_handles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of supported features.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_supported_features() {
|
||||
return [ 'products' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to payment methods
|
||||
* client side.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the frontend context.
|
||||
*
|
||||
* Alias of get_payment_method_script_handles. Defined by IntegrationInterface.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_script_handles() {
|
||||
return $this->get_payment_method_script_handles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the admin context.
|
||||
*
|
||||
* Alias of get_payment_method_script_handles_for_admin. Defined by IntegrationInterface.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_editor_script_handles() {
|
||||
return $this->get_payment_method_script_handles_for_admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to the block on the client side.
|
||||
*
|
||||
* Alias of get_payment_method_data. Defined by IntegrationInterface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_script_data() {
|
||||
return $this->get_payment_method_data();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api;
|
||||
|
||||
/**
|
||||
* Bank Transfer (BACS) payment method integration
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
final class BankTransfer extends AbstractPaymentMethodType {
|
||||
/**
|
||||
* Payment method name/id/slug (matches id in WC_Gateway_BACS in core).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'bacs';
|
||||
|
||||
/**
|
||||
* An instance of the Asset Api
|
||||
*
|
||||
* @var Api
|
||||
*/
|
||||
private $asset_api;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Api $asset_api An instance of Api.
|
||||
*/
|
||||
public function __construct( Api $asset_api ) {
|
||||
$this->asset_api = $asset_api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the payment method type.
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->settings = get_option( 'woocommerce_bacs_settings', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active() {
|
||||
return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of scripts/handles to be registered for this payment method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_script_handles() {
|
||||
$this->asset_api->register_script(
|
||||
'wc-payment-method-bacs',
|
||||
'build/wc-payment-method-bacs.js'
|
||||
);
|
||||
return [ 'wc-payment-method-bacs' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of key=>value pairs of data made available to the payment methods script.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data() {
|
||||
return [
|
||||
'title' => $this->get_setting( 'title' ),
|
||||
'description' => $this->get_setting( 'description' ),
|
||||
'supports' => $this->get_supported_features(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api;
|
||||
|
||||
/**
|
||||
* Cash on Delivery (COD) payment method integration
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
final class CashOnDelivery extends AbstractPaymentMethodType {
|
||||
/**
|
||||
* Payment method name/id/slug (matches id in WC_Gateway_COD in core).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'cod';
|
||||
|
||||
/**
|
||||
* An instance of the Asset Api
|
||||
*
|
||||
* @var Api
|
||||
*/
|
||||
private $asset_api;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Api $asset_api An instance of Api.
|
||||
*/
|
||||
public function __construct( Api $asset_api ) {
|
||||
$this->asset_api = $asset_api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the payment method type.
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->settings = get_option( 'woocommerce_cod_settings', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active() {
|
||||
return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return enable_for_virtual option.
|
||||
*
|
||||
* @return boolean True if store allows COD payment for orders containing only virtual products.
|
||||
*/
|
||||
private function get_enable_for_virtual() {
|
||||
return filter_var( $this->get_setting( 'enable_for_virtual', false ), FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return enable_for_methods option.
|
||||
*
|
||||
* @return array Array of shipping methods (string ids) that allow COD. (If empty, all support COD.)
|
||||
*/
|
||||
private function get_enable_for_methods() {
|
||||
$enable_for_methods = $this->get_setting( 'enable_for_methods', [] );
|
||||
if ( '' === $enable_for_methods ) {
|
||||
return [];
|
||||
}
|
||||
return $enable_for_methods;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of scripts/handles to be registered for this payment method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_script_handles() {
|
||||
$this->asset_api->register_script(
|
||||
'wc-payment-method-cod',
|
||||
'build/wc-payment-method-cod.js'
|
||||
);
|
||||
return [ 'wc-payment-method-cod' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of key=>value pairs of data made available to the payment methods script.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data() {
|
||||
return [
|
||||
'title' => $this->get_setting( 'title' ),
|
||||
'description' => $this->get_setting( 'description' ),
|
||||
'enableForVirtual' => $this->get_enable_for_virtual(),
|
||||
'enableForShippingMethods' => $this->get_enable_for_methods(),
|
||||
'supports' => $this->get_supported_features(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;
|
||||
|
||||
use Exception;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api;
|
||||
|
||||
/**
|
||||
* Cheque payment method integration
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
final class Cheque extends AbstractPaymentMethodType {
|
||||
/**
|
||||
* Payment method name defined by payment methods extending this class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'cheque';
|
||||
|
||||
/**
|
||||
* An instance of the Asset Api
|
||||
*
|
||||
* @var Api
|
||||
*/
|
||||
private $asset_api;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Api $asset_api An instance of Api.
|
||||
*/
|
||||
public function __construct( Api $asset_api ) {
|
||||
$this->asset_api = $asset_api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the payment method type.
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->settings = get_option( 'woocommerce_cheque_settings', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active() {
|
||||
return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of scripts/handles to be registered for this payment method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_script_handles() {
|
||||
$this->asset_api->register_script(
|
||||
'wc-payment-method-cheque',
|
||||
'build/wc-payment-method-cheque.js'
|
||||
);
|
||||
return [ 'wc-payment-method-cheque' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of key=>value pairs of data made available to the payment methods script.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data() {
|
||||
return [
|
||||
'title' => $this->get_setting( 'title' ),
|
||||
'description' => $this->get_setting( 'description' ),
|
||||
'supports' => $this->get_supported_features(),
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;
|
||||
|
||||
use WC_Gateway_Paypal;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api;
|
||||
|
||||
/**
|
||||
* PayPal Standard payment method integration
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
final class PayPal extends AbstractPaymentMethodType {
|
||||
/**
|
||||
* Payment method name defined by payment methods extending this class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'paypal';
|
||||
|
||||
/**
|
||||
* An instance of the Asset Api
|
||||
*
|
||||
* @var Api
|
||||
*/
|
||||
private $asset_api;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Api $asset_api An instance of Api.
|
||||
*/
|
||||
public function __construct( Api $asset_api ) {
|
||||
$this->asset_api = $asset_api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the payment method type.
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->settings = get_option( 'woocommerce_paypal_settings', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active() {
|
||||
return filter_var( $this->get_setting( 'enabled', false ), FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of scripts/handles to be registered for this payment method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_script_handles() {
|
||||
$this->asset_api->register_script(
|
||||
'wc-payment-method-paypal',
|
||||
'build/wc-payment-method-paypal.js'
|
||||
);
|
||||
return [ 'wc-payment-method-paypal' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of key=>value pairs of data made available to the payment methods script.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data() {
|
||||
return [
|
||||
'title' => $this->get_setting( 'title' ),
|
||||
'description' => $this->get_setting( 'description' ),
|
||||
'supports' => $this->get_supported_features(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of supported features.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_supported_features() {
|
||||
$gateway = new WC_Gateway_Paypal();
|
||||
$features = array_filter( $gateway->supports, array( $gateway, 'supports' ) );
|
||||
return apply_filters( '__experimental_woocommerce_blocks_payment_gateway_features_list', $features, $this->get_name() );
|
||||
}
|
||||
}
|
348
packages/woocommerce-blocks/src/Payments/Integrations/Stripe.php
Normal file
348
packages/woocommerce-blocks/src/Payments/Integrations/Stripe.php
Normal file
@ -0,0 +1,348 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments\Integrations;
|
||||
|
||||
use Exception;
|
||||
use WC_Stripe_Payment_Request;
|
||||
use WC_Stripe_Helper;
|
||||
use WC_Gateway_Stripe;
|
||||
use Automattic\WooCommerce\Blocks\Assets\Api;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentContext;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentResult;
|
||||
|
||||
/**
|
||||
* Stripe payment method integration
|
||||
*
|
||||
* Temporary integration of the stripe payment method for the new cart and
|
||||
* checkout blocks. Once the api is demonstrated to be stable, this integration
|
||||
* will be moved to the Stripe extension
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
final class Stripe extends AbstractPaymentMethodType {
|
||||
/**
|
||||
* Payment method name defined by payment methods extending this class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'stripe';
|
||||
|
||||
/**
|
||||
* An instance of the Asset Api
|
||||
*
|
||||
* @var Api
|
||||
*/
|
||||
private $asset_api;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Api $asset_api An instance of Api.
|
||||
*/
|
||||
public function __construct( Api $asset_api ) {
|
||||
$this->asset_api = $asset_api;
|
||||
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_payment_request_order_meta' ], 8, 2 );
|
||||
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_stripe_intents' ], 9999, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the payment method type.
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->settings = get_option( 'woocommerce_stripe_settings', [] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active() {
|
||||
return ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of scripts/handles to be registered for this payment method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_script_handles() {
|
||||
$this->asset_api->register_script(
|
||||
'wc-payment-method-stripe',
|
||||
'build/wc-payment-method-stripe.js',
|
||||
[]
|
||||
);
|
||||
|
||||
return [ 'wc-payment-method-stripe' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of key=>value pairs of data made available to the payment methods script.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data() {
|
||||
return [
|
||||
'stripeTotalLabel' => $this->get_total_label(),
|
||||
'publicKey' => $this->get_publishable_key(),
|
||||
'allowPrepaidCard' => $this->get_allow_prepaid_card(),
|
||||
'title' => $this->get_title(),
|
||||
'button' => [
|
||||
'type' => $this->get_button_type(),
|
||||
'theme' => $this->get_button_theme(),
|
||||
'height' => $this->get_button_height(),
|
||||
'locale' => $this->get_button_locale(),
|
||||
],
|
||||
'inline_cc_form' => $this->get_inline_cc_form(),
|
||||
'icons' => $this->get_icons(),
|
||||
'showSavedCards' => $this->get_show_saved_cards(),
|
||||
'allowPaymentRequest' => $this->get_allow_payment_request(),
|
||||
'showSaveOption' => $this->get_show_save_option(),
|
||||
'supports' => $this->get_supported_features(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if store allows cards to be saved during checkout.
|
||||
*
|
||||
* @return bool True if merchant allows shopper to save card (payment method) during checkout).
|
||||
*/
|
||||
private function get_show_saved_cards() {
|
||||
return isset( $this->settings['saved_cards'] ) ? 'yes' === $this->settings['saved_cards'] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the checkbox to enable the user to save their payment method should be shown.
|
||||
*
|
||||
* @return bool True if the save payment checkbox should be displayed to the user.
|
||||
*/
|
||||
private function get_show_save_option() {
|
||||
$saved_cards = $this->get_show_saved_cards();
|
||||
// This assumes that Stripe supports `tokenization` - currently this is true, based on
|
||||
// https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95 .
|
||||
// See https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271 and
|
||||
// https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API .
|
||||
return apply_filters( 'wc_stripe_display_save_payment_method_checkbox', filter_var( $saved_cards, FILTER_VALIDATE_BOOLEAN ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label to use accompanying the total in the stripe statement.
|
||||
*
|
||||
* @return string Statement descriptor.
|
||||
*/
|
||||
private function get_total_label() {
|
||||
return ! empty( $this->settings['statement_descriptor'] ) ? WC_Stripe_Helper::clean_statement_descriptor( $this->settings['statement_descriptor'] ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the publishable api key for the Stripe service.
|
||||
*
|
||||
* @return string Public api key.
|
||||
*/
|
||||
private function get_publishable_key() {
|
||||
$test_mode = ( ! empty( $this->settings['testmode'] ) && 'yes' === $this->settings['testmode'] );
|
||||
$setting_key = $test_mode ? 'test_publishable_key' : 'publishable_key';
|
||||
return ! empty( $this->settings[ $setting_key ] ) ? $this->settings[ $setting_key ] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to allow prepaid cards for payments.
|
||||
*
|
||||
* @return bool True means to allow prepaid card (default).
|
||||
*/
|
||||
private function get_allow_prepaid_card() {
|
||||
return apply_filters( 'wc_stripe_allow_prepaid_card', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title string to use in the UI (customisable via admin settings screen).
|
||||
*
|
||||
* @return string Title / label string
|
||||
*/
|
||||
private function get_title() {
|
||||
return isset( $this->settings['title'] ) ? $this->settings['title'] : __( 'Credit / Debit Card', 'woocommerce' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if store allows Payment Request buttons - e.g. Apple Pay / Chrome Pay.
|
||||
*
|
||||
* @return bool True if merchant has opted into payment request.
|
||||
*/
|
||||
private function get_allow_payment_request() {
|
||||
$option = isset( $this->settings['payment_request'] ) ? $this->settings['payment_request'] : false;
|
||||
return filter_var( $option, FILTER_VALIDATE_BOOLEAN );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the button type for the payment button.
|
||||
*
|
||||
* @return string Defaults to 'default'.
|
||||
*/
|
||||
private function get_button_type() {
|
||||
return isset( $this->settings['payment_request_button_type'] ) ? $this->settings['payment_request_button_type'] : 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the theme to use for the payment button.
|
||||
*
|
||||
* @return string Defaults to 'dark'.
|
||||
*/
|
||||
private function get_button_theme() {
|
||||
return isset( $this->settings['payment_request_button_theme'] ) ? $this->settings['payment_request_button_theme'] : 'dark';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height for the payment button.
|
||||
*
|
||||
* @return string A pixel value for the height (defaults to '64').
|
||||
*/
|
||||
private function get_button_height() {
|
||||
return isset( $this->settings['payment_request_button_height'] ) ? str_replace( 'px', '', $this->settings['payment_request_button_height'] ) : '64';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the inline cc option.
|
||||
*
|
||||
* @return boolean True if the inline CC form option is enabled.
|
||||
*/
|
||||
private function get_inline_cc_form() {
|
||||
return isset( $this->settings['inline_cc_form'] ) && 'yes' === $this->settings['inline_cc_form'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the locale for the payment button.
|
||||
*
|
||||
* @return string Defaults to en_US.
|
||||
*/
|
||||
private function get_button_locale() {
|
||||
return apply_filters( 'wc_stripe_payment_request_button_locale', substr( get_locale(), 0, 2 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the icons urls.
|
||||
*
|
||||
* @return array Arrays of icons metadata.
|
||||
*/
|
||||
private function get_icons() {
|
||||
$icons_src = [
|
||||
'visa' => [
|
||||
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/visa.svg',
|
||||
'alt' => __( 'Visa', 'woocommerce' ),
|
||||
],
|
||||
'amex' => [
|
||||
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/amex.svg',
|
||||
'alt' => __( 'American Express', 'woocommerce' ),
|
||||
],
|
||||
'mastercard' => [
|
||||
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/mastercard.svg',
|
||||
'alt' => __( 'Mastercard', 'woocommerce' ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( 'USD' === get_woocommerce_currency() ) {
|
||||
$icons_src['discover'] = [
|
||||
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/discover.svg',
|
||||
'alt' => __( 'Discover', 'woocommerce' ),
|
||||
];
|
||||
$icons_src['jcb'] = [
|
||||
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/jcb.svg',
|
||||
'alt' => __( 'JCB', 'woocommerce' ),
|
||||
];
|
||||
$icons_src['diners'] = [
|
||||
'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/diners.svg',
|
||||
'alt' => __( 'Diners', 'woocommerce' ),
|
||||
];
|
||||
}
|
||||
return $icons_src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add payment request data to the order meta as hooked on the
|
||||
* woocommerce_rest_checkout_process_payment_with_context action.
|
||||
*
|
||||
* @param PaymentContext $context Holds context for the payment.
|
||||
* @param PaymentResult $result Result object for the payment.
|
||||
*/
|
||||
public function add_payment_request_order_meta( PaymentContext $context, PaymentResult &$result ) {
|
||||
$data = $context->payment_data;
|
||||
if ( ! empty( $data['payment_request_type'] ) && 'stripe' === $context->payment_method ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$post_data = $_POST;
|
||||
$_POST = $context->payment_data;
|
||||
$this->add_order_meta( $context->order, $data['payment_request_type'] );
|
||||
$_POST = $post_data;
|
||||
}
|
||||
|
||||
// hook into stripe error processing so that we can capture the error to
|
||||
// payment details (which is added to notices and thus not helpful for
|
||||
// this context).
|
||||
if ( 'stripe' === $context->payment_method ) {
|
||||
add_action(
|
||||
'wc_gateway_stripe_process_payment_error',
|
||||
function( $error ) use ( &$result ) {
|
||||
$payment_details = $result->payment_details;
|
||||
$payment_details['errorMessage'] = wp_strip_all_tags( $error->getLocalizedMessage() );
|
||||
$result->set_payment_details( $payment_details );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles any potential stripe intents on the order that need handled.
|
||||
*
|
||||
* This is configured to execute after legacy payment processing has
|
||||
* happened on the woocommerce_rest_checkout_process_payment_with_context
|
||||
* action hook.
|
||||
*
|
||||
* @param PaymentContext $context Holds context for the payment.
|
||||
* @param PaymentResult $result Result object for the payment.
|
||||
*/
|
||||
public function add_stripe_intents( PaymentContext $context, PaymentResult &$result ) {
|
||||
if ( 'stripe' === $context->payment_method
|
||||
&& (
|
||||
! empty( $result->payment_details['payment_intent_secret'] )
|
||||
|| ! empty( $result->payment_details['setup_intent_secret'] )
|
||||
)
|
||||
) {
|
||||
$payment_details = $result->payment_details;
|
||||
$payment_details['verification_endpoint'] = add_query_arg(
|
||||
[
|
||||
'order' => $context->order->get_id(),
|
||||
'nonce' => wp_create_nonce( 'wc_stripe_confirm_pi' ),
|
||||
'redirect_to' => rawurlencode( $result->redirect_url ),
|
||||
],
|
||||
home_url() . \WC_Ajax::get_endpoint( 'wc_stripe_verify_intent' )
|
||||
);
|
||||
$result->set_payment_details( $payment_details );
|
||||
$result->set_status( 'success' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles adding information about the payment request type used to the order meta.
|
||||
*
|
||||
* @param \WC_Order $order The order being processed.
|
||||
* @param string $payment_request_type The payment request type used for payment.
|
||||
*/
|
||||
private function add_order_meta( \WC_Order $order, string $payment_request_type ) {
|
||||
if ( 'apple_pay' === $payment_request_type ) {
|
||||
$order->set_payment_method_title( 'Apple Pay (Stripe)' );
|
||||
$order->save();
|
||||
}
|
||||
|
||||
if ( 'payment_request_api' === $payment_request_type ) {
|
||||
$order->set_payment_method_title( 'Chrome Payment Request (Stripe)' );
|
||||
$order->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of supported features.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_supported_features() {
|
||||
$gateway = new WC_Gateway_Stripe();
|
||||
return array_filter( $gateway->supports, array( $gateway, 'supports' ) );
|
||||
}
|
||||
}
|
84
packages/woocommerce-blocks/src/Payments/PaymentContext.php
Normal file
84
packages/woocommerce-blocks/src/Payments/PaymentContext.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments;
|
||||
|
||||
/**
|
||||
* PaymentContext class.
|
||||
*/
|
||||
class PaymentContext {
|
||||
/**
|
||||
* Payment method ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $payment_method = '';
|
||||
|
||||
/**
|
||||
* Order object for the order being paid.
|
||||
*
|
||||
* @var \WC_Order
|
||||
*/
|
||||
protected $order;
|
||||
|
||||
/**
|
||||
* Holds data to send to the payment gateway to support payment.
|
||||
*
|
||||
* @var array Key value pairs.
|
||||
*/
|
||||
protected $payment_data = [];
|
||||
|
||||
/**
|
||||
* Magic getter for protected properties.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
if ( in_array( $name, [ 'payment_method', 'order', 'payment_data' ], true ) ) {
|
||||
return $this->$name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the chosen payment method ID context.
|
||||
*
|
||||
* @param string $payment_method Payment method ID.
|
||||
*/
|
||||
public function set_payment_method( $payment_method ) {
|
||||
$this->payment_method = (string) $payment_method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the payment method instance for the current set payment method.
|
||||
*
|
||||
* @return {\WC_Payment_Gateway|null} An instance of the payment gateway if it exists.
|
||||
*/
|
||||
public function get_payment_method_instance() {
|
||||
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
if ( ! isset( $available_gateways[ $this->payment_method ] ) ) {
|
||||
return;
|
||||
}
|
||||
return $available_gateways[ $this->payment_method ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the order context.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
public function set_order( \WC_Order $order ) {
|
||||
$this->order = $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set payment data context.
|
||||
*
|
||||
* @param array $payment_data Array of key value pairs of data.
|
||||
*/
|
||||
public function set_payment_data( $payment_data = [] ) {
|
||||
$this->payment_data = [];
|
||||
|
||||
foreach ( $payment_data as $key => $value ) {
|
||||
$this->payment_data[ (string) $key ] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
|
||||
/**
|
||||
* Class used for interacting with payment method types.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
final class PaymentMethodRegistry extends IntegrationRegistry {
|
||||
/**
|
||||
* Integration identifier is used to construct hook names and is given when the integration registry is initialized.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $registry_identifier = 'payment_method_type';
|
||||
|
||||
/**
|
||||
* Retrieves all registered payment methods that are also active.
|
||||
*
|
||||
* @return PaymentMethodTypeInterface[]
|
||||
*/
|
||||
public function get_all_active_registered() {
|
||||
return array_filter(
|
||||
$this->get_all_registered(),
|
||||
function( $payment_method ) {
|
||||
return $payment_method->is_active();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered payment method script handles, but only for active payment methods.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_all_active_payment_method_script_dependencies() {
|
||||
$script_handles = [];
|
||||
$payment_methods = $this->get_all_active_registered();
|
||||
|
||||
foreach ( $payment_methods as $payment_method ) {
|
||||
$script_handles = array_merge(
|
||||
$script_handles,
|
||||
is_admin() ? $payment_method->get_payment_method_script_handles_for_admin() : $payment_method->get_payment_method_script_handles()
|
||||
);
|
||||
}
|
||||
|
||||
return array_unique( array_filter( $script_handles ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered payment method script data, but only for active payment methods.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_registered_script_data() {
|
||||
$script_data = [];
|
||||
$payment_methods = $this->get_all_active_registered();
|
||||
|
||||
foreach ( $payment_methods as $payment_method ) {
|
||||
$script_data[ $payment_method->get_name() . '_data' ] = $payment_method->get_payment_method_data();
|
||||
}
|
||||
|
||||
return array_filter( $script_data );
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;
|
||||
|
||||
interface PaymentMethodTypeInterface extends IntegrationInterface {
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the frontend context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the admin context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles_for_admin();
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to payment methods
|
||||
* client side.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data();
|
||||
|
||||
/**
|
||||
* Get array of supported features.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_supported_features();
|
||||
}
|
94
packages/woocommerce-blocks/src/Payments/PaymentResult.php
Normal file
94
packages/woocommerce-blocks/src/Payments/PaymentResult.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Payments;
|
||||
|
||||
/**
|
||||
* PaymentResult class.
|
||||
*/
|
||||
class PaymentResult {
|
||||
/**
|
||||
* List of valid payment statuses.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $valid_statuses = [ 'success', 'failure', 'pending', 'error' ];
|
||||
|
||||
/**
|
||||
* Current payment status.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $status = '';
|
||||
|
||||
/**
|
||||
* Array of details about the payment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $payment_details = [];
|
||||
|
||||
/**
|
||||
* Redirect URL for checkout.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirect_url = '';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $status Sets the payment status for the result.
|
||||
*/
|
||||
public function __construct( $status = '' ) {
|
||||
if ( $status ) {
|
||||
$this->set_status( $status );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter for protected properties.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
if ( in_array( $name, [ 'status', 'payment_details', 'redirect_url' ], true ) ) {
|
||||
return $this->$name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set payment status.
|
||||
*
|
||||
* @throws \Exception When an invalid status is provided.
|
||||
*
|
||||
* @param string $payment_status Status to set.
|
||||
*/
|
||||
public function set_status( $payment_status ) {
|
||||
if ( ! in_array( $payment_status, $this->valid_statuses, true ) ) {
|
||||
throw new \Exception( sprintf( 'Invalid payment status %s. Use one of %s', $payment_status, implode( ', ', $this->valid_statuses ) ) );
|
||||
}
|
||||
$this->status = $payment_status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set payment details.
|
||||
*
|
||||
* @param array $payment_details Array of key value pairs of data.
|
||||
*/
|
||||
public function set_payment_details( $payment_details = [] ) {
|
||||
$this->payment_details = [];
|
||||
|
||||
foreach ( $payment_details as $key => $value ) {
|
||||
$this->payment_details[ (string) $key ] = (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set redirect URL.
|
||||
*
|
||||
* @param array $redirect_url URL to redirect the customer to after checkout.
|
||||
*/
|
||||
public function set_redirect_url( $redirect_url = [] ) {
|
||||
$this->redirect_url = esc_url_raw( $redirect_url );
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Registry;
|
||||
|
||||
/**
|
||||
* An abstract class for dependency types.
|
||||
*
|
||||
* Dependency types are instances of a dependency used by the
|
||||
* Dependency Injection Container for storing dependencies to invoke as they
|
||||
* are needed.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
abstract class AbstractDependencyType {
|
||||
|
||||
/**
|
||||
* Holds a callable or value provided for this type.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $callable_or_value;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param mixed $callable_or_value A callable or value for the dependency
|
||||
* type instance.
|
||||
*/
|
||||
public function __construct( $callable_or_value ) {
|
||||
$this->callable_or_value = $callable_or_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolver for the internal dependency value.
|
||||
*
|
||||
* @param Container $container The Dependency Injection Container.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function resolve_value( Container $container ) {
|
||||
$callback = $this->callable_or_value;
|
||||
return \is_callable( $callback )
|
||||
? $callback( $container )
|
||||
: $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value stored internally for this DependencyType
|
||||
*
|
||||
* @param Container $container The Dependency Injection Container.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function get( Container $container );
|
||||
}
|
98
packages/woocommerce-blocks/src/Registry/Container.php
Normal file
98
packages/woocommerce-blocks/src/Registry/Container.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Registry;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* A simple Dependency Injection Container
|
||||
*
|
||||
* This is used to manage dependencies used throughout the plugin.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Container {
|
||||
|
||||
/**
|
||||
* A map of Dependency Type objects used to resolve dependencies.
|
||||
*
|
||||
* @var AbstractDependencyType[]
|
||||
*/
|
||||
private $registry = [];
|
||||
|
||||
/**
|
||||
* Public api for adding a factory to the container.
|
||||
*
|
||||
* Factory dependencies will have the instantiation callback invoked
|
||||
* every time the dependency is requested.
|
||||
*
|
||||
* Typical Usage:
|
||||
*
|
||||
* ```
|
||||
* $container->register( MyClass::class, $container->factory( $mycallback ) );
|
||||
* ```
|
||||
*
|
||||
* @param Closure $instantiation_callback This will be invoked when the
|
||||
* dependency is required. It will
|
||||
* receive an instance of this
|
||||
* container so the callback can
|
||||
* retrieve dependencies from the
|
||||
* container.
|
||||
*
|
||||
* @return FactoryType An instance of the FactoryType dependency.
|
||||
*/
|
||||
public function factory( Closure $instantiation_callback ) {
|
||||
return new FactoryType( $instantiation_callback );
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for registering a new dependency with the container.
|
||||
*
|
||||
* By default, the $value will be added as a shared dependency. This means
|
||||
* that it will be a single instance shared among any other classes having
|
||||
* that dependency.
|
||||
*
|
||||
* If you want a new instance every time it's required, then wrap the value
|
||||
* in a call to the factory method (@see Container::factory for example)
|
||||
*
|
||||
* Note: Currently if the provided id already is registered in the container,
|
||||
* the provided value is ignored.
|
||||
*
|
||||
* @param string $id A unique string identifier for the provided value.
|
||||
* Typically it's the fully qualified name for the
|
||||
* dependency.
|
||||
* @param mixed $value The value for the dependency. Typically, this is a
|
||||
* closure that will create the class instance needed.
|
||||
*/
|
||||
public function register( $id, $value ) {
|
||||
if ( empty( $this->registry[ $id ] ) ) {
|
||||
if ( ! $value instanceof FactoryType ) {
|
||||
$value = new SharedType( $value );
|
||||
}
|
||||
$this->registry[ $id ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for retrieving the dependency stored in the container for the
|
||||
* given identifier.
|
||||
*
|
||||
* @param string $id The identifier for the dependency being retrieved.
|
||||
* @throws Exception If there is no dependency for the given identifier in
|
||||
* the container.
|
||||
*
|
||||
* @return mixed Typically a class instance.
|
||||
*/
|
||||
public function get( $id ) {
|
||||
if ( ! isset( $this->registry[ $id ] ) ) {
|
||||
// this is a developer facing exception, hence it is not localized.
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Cannot construct an instance of %s because it has not been registered.',
|
||||
$id
|
||||
)
|
||||
);
|
||||
}
|
||||
return $this->registry[ $id ]->get( $this );
|
||||
}
|
||||
}
|
21
packages/woocommerce-blocks/src/Registry/FactoryType.php
Normal file
21
packages/woocommerce-blocks/src/Registry/FactoryType.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Registry;
|
||||
|
||||
/**
|
||||
* Definition for the FactoryType dependency type.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class FactoryType extends AbstractDependencyType {
|
||||
/**
|
||||
* Invokes and returns the value from the stored internal callback.
|
||||
*
|
||||
* @param Container $container An instance of the dependency injection
|
||||
* container.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( Container $container ) {
|
||||
return $this->resolve_value( $container );
|
||||
}
|
||||
}
|
32
packages/woocommerce-blocks/src/Registry/SharedType.php
Normal file
32
packages/woocommerce-blocks/src/Registry/SharedType.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Registry;
|
||||
|
||||
/**
|
||||
* A definition for the SharedType dependency type.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class SharedType extends AbstractDependencyType {
|
||||
|
||||
/**
|
||||
* Holds a cached instance of the value stored (or returned) internally.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $shared_instance;
|
||||
|
||||
/**
|
||||
* Returns the internal stored and shared value after initial generation.
|
||||
*
|
||||
* @param Container $container An instance of the dependency injection
|
||||
* container.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( Container $container ) {
|
||||
if ( empty( $this->shared_instance ) ) {
|
||||
$this->shared_instance = $this->resolve_value( $container );
|
||||
}
|
||||
return $this->shared_instance;
|
||||
}
|
||||
}
|
111
packages/woocommerce-blocks/src/RestApi.php
Normal file
111
packages/woocommerce-blocks/src/RestApi.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\RoutesController;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\SchemaController;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\ExtendRestApi;
|
||||
|
||||
|
||||
/**
|
||||
* RestApi class.
|
||||
* Registers controllers in the blocks REST API namespace.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class RestApi {
|
||||
/**
|
||||
* Stores Rest Routes instance
|
||||
*
|
||||
* @var RoutesController
|
||||
*/
|
||||
private $routes;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param RoutesController $routes Rest Routes instance.
|
||||
*/
|
||||
public function __construct( RoutesController $routes ) {
|
||||
$this->routes = $routes;
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize class features.
|
||||
*/
|
||||
protected function init() {
|
||||
add_action( 'rest_api_init', array( $this, 'register_rest_routes' ), 10 );
|
||||
add_filter( 'rest_authentication_errors', array( $this, 'store_api_authentication' ) );
|
||||
add_action( 'set_logged_in_cookie', array( $this, 'store_api_logged_in_cookie' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_rest_routes() {
|
||||
$this->routes->register_routes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get routes for a namespace.
|
||||
*
|
||||
* @param string $namespace Namespace to retrieve.
|
||||
* @return array|null
|
||||
*/
|
||||
public function get_routes_from_namespace( $namespace ) {
|
||||
$rest_server = rest_get_server();
|
||||
$namespace_index = $rest_server->get_namespace_index(
|
||||
[
|
||||
'namespace' => $namespace,
|
||||
'context' => 'view',
|
||||
]
|
||||
);
|
||||
|
||||
$response_data = $namespace_index->get_data();
|
||||
|
||||
return isset( $response_data['routes'] ) ? $response_data['routes'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Store API does not require authentication.
|
||||
*
|
||||
* @param \WP_Error|mixed $result Error from another authentication handler, null if we should handle it, or another value if not.
|
||||
* @return \WP_Error|null|bool
|
||||
*/
|
||||
public function store_api_authentication( $result ) {
|
||||
// Pass through errors from other authentication methods used before this one.
|
||||
if ( ! empty( $result ) || ! self::is_request_to_store_api() ) {
|
||||
return $result;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the login cookies are set, they are not available until the next page reload. For the Store API, specifically
|
||||
* for returning updated nonces, we need this to be available immediately.
|
||||
*
|
||||
* @param string $logged_in_cookie The value for the logged in cookie.
|
||||
*/
|
||||
public function store_api_logged_in_cookie( $logged_in_cookie ) {
|
||||
if ( ! defined( 'LOGGED_IN_COOKIE' ) || ! self::is_request_to_store_api() ) {
|
||||
return;
|
||||
}
|
||||
$_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is request to the Store API.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_request_to_store_api() {
|
||||
if ( empty( $_SERVER['REQUEST_URI'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rest_prefix = trailingslashit( rest_get_url_prefix() );
|
||||
$request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );
|
||||
|
||||
return false !== strpos( $request_uri, $rest_prefix . 'wc/store' );
|
||||
}
|
||||
}
|
47
packages/woocommerce-blocks/src/StoreApi/Formatters.php
Normal file
47
packages/woocommerce-blocks/src/StoreApi/Formatters.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi;
|
||||
|
||||
use \Exception;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Formatters\DefaultFormatter;
|
||||
|
||||
/**
|
||||
* Formatters class.
|
||||
*
|
||||
* Allows formatter classes to be registered. Formatters are exposed to extensions via the ExtendRestApi class.
|
||||
*/
|
||||
class Formatters {
|
||||
/**
|
||||
* Holds an array of formatter class instances.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $formatters = [];
|
||||
|
||||
/**
|
||||
* Get a new instance of a formatter class.
|
||||
*
|
||||
* @throws Exception An Exception is thrown if a non-existing formatter is used and the user is admin.
|
||||
*
|
||||
* @param string $name Name of the formatter.
|
||||
* @return FormatterInterface Formatter class instance.
|
||||
*/
|
||||
public function __get( $name ) {
|
||||
if ( ! isset( $this->formatters[ $name ] ) ) {
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'manage_woocommerce' ) ) {
|
||||
throw new Exception( $name . ' formatter does not exist' );
|
||||
}
|
||||
return new DefaultFormatter();
|
||||
}
|
||||
return $this->formatters[ $name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a formatter class for usage.
|
||||
*
|
||||
* @param string $name Name of the formatter.
|
||||
* @param string $class A formatter class name.
|
||||
*/
|
||||
public function register( $name, $class ) {
|
||||
$this->formatters[ $name ] = new $class();
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters;
|
||||
|
||||
/**
|
||||
* Currency Formatter.
|
||||
*
|
||||
* Formats an array of monetary values by inserting currency data.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CurrencyFormatter implements FormatterInterface {
|
||||
/**
|
||||
* Format a given value and return the result.
|
||||
*
|
||||
* @param array $value Value to format.
|
||||
* @param array $options Options that influence the formatting.
|
||||
* @return array
|
||||
*/
|
||||
public function format( $value, array $options = [] ) {
|
||||
$position = get_option( 'woocommerce_currency_pos' );
|
||||
$symbol = html_entity_decode( get_woocommerce_currency_symbol() );
|
||||
$prefix = '';
|
||||
$suffix = '';
|
||||
|
||||
switch ( $position ) {
|
||||
case 'left_space':
|
||||
$prefix = $symbol . ' ';
|
||||
break;
|
||||
case 'left':
|
||||
$prefix = $symbol;
|
||||
break;
|
||||
case 'right_space':
|
||||
$suffix = ' ' . $symbol;
|
||||
break;
|
||||
case 'right':
|
||||
$suffix = $symbol;
|
||||
break;
|
||||
}
|
||||
|
||||
return array_merge(
|
||||
(array) $value,
|
||||
[
|
||||
'currency_code' => get_woocommerce_currency(),
|
||||
'currency_symbol' => $symbol,
|
||||
'currency_minor_unit' => wc_get_price_decimals(),
|
||||
'currency_decimal_separator' => wc_get_price_decimal_separator(),
|
||||
'currency_thousand_separator' => wc_get_price_thousand_separator(),
|
||||
'currency_prefix' => $prefix,
|
||||
'currency_suffix' => $suffix,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters;
|
||||
|
||||
/**
|
||||
* Default Formatter.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class DefaultFormatter implements FormatterInterface {
|
||||
/**
|
||||
* Format a given value and return the result.
|
||||
*
|
||||
* @param mixed $value Value to format.
|
||||
* @param array $options Options that influence the formatting.
|
||||
* @return mixed
|
||||
*/
|
||||
public function format( $value, array $options = [] ) {
|
||||
return $value;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters;
|
||||
|
||||
/**
|
||||
* FormatterInterface.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
interface FormatterInterface {
|
||||
/**
|
||||
* Format a given value and return the result.
|
||||
*
|
||||
* @param mixed $value Value to format.
|
||||
* @param array $options Options that influence the formatting.
|
||||
* @return mixed
|
||||
*/
|
||||
public function format( $value, array $options = [] );
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters;
|
||||
|
||||
/**
|
||||
* Html Formatter.
|
||||
*
|
||||
* Formats HTML in API responses.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class HtmlFormatter implements FormatterInterface {
|
||||
/**
|
||||
* Format a given value and return the result.
|
||||
*
|
||||
* The wptexturize, convert_chars, and trim functions are also used in the `the_title` filter.
|
||||
* The function wp_kses_post removes disallowed HTML tags.
|
||||
*
|
||||
* @param string|array $value Value to format.
|
||||
* @param array $options Options that influence the formatting.
|
||||
* @return string
|
||||
*/
|
||||
public function format( $value, array $options = [] ) {
|
||||
if ( is_array( $value ) ) {
|
||||
return array_map( [ $this, 'format' ], $value );
|
||||
}
|
||||
return is_scalar( $value ) ? wp_kses_post( trim( convert_chars( wptexturize( $value ) ) ) ) : $value;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Formatters;
|
||||
|
||||
/**
|
||||
* Money Formatter.
|
||||
*
|
||||
* Formats monetary values using store settings.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class MoneyFormatter implements FormatterInterface {
|
||||
/**
|
||||
* Format a given value and return the result.
|
||||
*
|
||||
* @param mixed $value Value to format.
|
||||
* @param array $options Options that influence the formatting.
|
||||
* @return mixed
|
||||
*/
|
||||
public function format( $value, array $options = [] ) {
|
||||
$options = wp_parse_args(
|
||||
$options,
|
||||
[
|
||||
'decimals' => wc_get_price_decimals(),
|
||||
'rounding_mode' => PHP_ROUND_HALF_UP,
|
||||
]
|
||||
);
|
||||
|
||||
return (string) intval(
|
||||
round(
|
||||
( (float) wc_format_decimal( $value ) ) * ( 10 ** absint( $options['decimals'] ) ),
|
||||
0,
|
||||
absint( $options['rounding_mode'] )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema;
|
||||
|
||||
/**
|
||||
* Abstract Cart Route
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
abstract class AbstractCartRoute extends AbstractRoute {
|
||||
/**
|
||||
* Schema class for this route's response.
|
||||
*
|
||||
* @var AbstractSchema|CartSchema
|
||||
*/
|
||||
protected $schema;
|
||||
|
||||
/**
|
||||
* Schema class for the cart.
|
||||
*
|
||||
* @var CartSchema
|
||||
*/
|
||||
protected $cart_schema;
|
||||
|
||||
/**
|
||||
* Cart controller class instance.
|
||||
*
|
||||
* @var CartController
|
||||
*/
|
||||
protected $cart_controller;
|
||||
|
||||
/**
|
||||
* Constructor accepts two types of schema; one for the item being returned, and one for the cart as a whole. These
|
||||
* may be the same depending on the route.
|
||||
*
|
||||
* @param CartSchema $cart_schema Schema class for the cart.
|
||||
* @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema.
|
||||
* @param CartController $cart_controller Cart controller class.
|
||||
*/
|
||||
public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null, CartController $cart_controller ) {
|
||||
$this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema;
|
||||
$this->cart_schema = $cart_schema;
|
||||
$this->cart_controller = $cart_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route response based on the type of request.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public function get_response( \WP_REST_Request $request ) {
|
||||
$this->cart_controller->load_cart();
|
||||
$this->calculate_totals();
|
||||
|
||||
if ( $this->requires_nonce( $request ) ) {
|
||||
$nonce_check = $this->check_nonce( $request );
|
||||
|
||||
if ( is_wp_error( $nonce_check ) ) {
|
||||
return $this->add_nonce_headers( $this->error_to_response( $nonce_check ) );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$response = parent::get_response( $request );
|
||||
} catch ( RouteException $error ) {
|
||||
$response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() );
|
||||
} catch ( \Exception $error ) {
|
||||
$response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$response = $this->error_to_response( $response );
|
||||
}
|
||||
|
||||
return $this->add_nonce_headers( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add nonce headers to a response object.
|
||||
*
|
||||
* @param \WP_REST_Response $response The response object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function add_nonce_headers( \WP_REST_Response $response ) {
|
||||
$response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response->header( 'X-WC-Store-API-Nonce-Timestamp', time() );
|
||||
$response->header( 'X-WC-Store-API-User', get_current_user_id() );
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a nonce is required for the route.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
protected function requires_nonce( \WP_REST_Request $request ) {
|
||||
return 'GET' !== $request->get_method();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the cart totals are calculated before an API response is generated.
|
||||
*/
|
||||
protected function calculate_totals() {
|
||||
wc()->cart->get_cart();
|
||||
wc()->cart->calculate_fees();
|
||||
wc()->cart->calculate_shipping();
|
||||
wc()->cart->calculate_totals();
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a draft order, releases stock.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_release_stock() {
|
||||
$draft_order = wc()->session->get( 'store_api_draft_order', 0 );
|
||||
|
||||
if ( ! $draft_order ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wc_release_stock_for_order( $draft_order );
|
||||
}
|
||||
|
||||
/**
|
||||
* For non-GET endpoints, require and validate a nonce to prevent CSRF attacks.
|
||||
*
|
||||
* Nonces will mismatch if the logged in session cookie is different! If using a client to test, set this cookie
|
||||
* to match the logged in cookie in your browser.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_Error|boolean
|
||||
*/
|
||||
protected function check_nonce( \WP_REST_Request $request ) {
|
||||
$nonce = $request->get_header( 'X-WC-Store-API-Nonce' );
|
||||
|
||||
if ( apply_filters( 'woocommerce_store_api_disable_nonce_check', false ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( null === $nonce ) {
|
||||
return $this->get_route_error_response( 'woocommerce_rest_missing_nonce', __( 'Missing the X-WC-Store-API-Nonce header. This endpoint requires a valid nonce.', 'woocommerce' ), 401 );
|
||||
}
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'wc_store_api' ) ) {
|
||||
return $this->get_route_error_response( 'woocommerce_rest_invalid_nonce', __( 'X-WC-Store-API-Nonce is invalid.', 'woocommerce' ), 403 );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response when something went wrong.
|
||||
*
|
||||
* @param string $error_code String based error code.
|
||||
* @param string $error_message User facing error message.
|
||||
* @param int $http_status_code HTTP status. Defaults to 500.
|
||||
* @param array $additional_data Extra data (key value pairs) to expose in the error response.
|
||||
* @return \WP_Error WP Error object.
|
||||
*/
|
||||
protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) {
|
||||
switch ( $http_status_code ) {
|
||||
case 409:
|
||||
// If there was a conflict, return the cart so the client can resolve it.
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
|
||||
return new \WP_Error(
|
||||
$error_code,
|
||||
$error_message,
|
||||
array_merge(
|
||||
$additional_data,
|
||||
[
|
||||
'status' => $http_status_code,
|
||||
'cart' => $this->cart_schema->get_item_response( $cart ),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
return new \WP_Error( $error_code, $error_message, [ 'status' => $http_status_code ] );
|
||||
}
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\InvalidStockLevelsInCartException;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* AbstractRoute class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
abstract class AbstractRoute implements RouteInterface {
|
||||
/**
|
||||
* Schema class instance.
|
||||
*
|
||||
* @var AbstractSchema
|
||||
*/
|
||||
protected $schema;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AbstractSchema $schema Schema class for this route.
|
||||
*/
|
||||
public function __construct( AbstractSchema $schema ) {
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the namespace for this route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace() {
|
||||
return 'wc/store';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item schema properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return $this->schema->get_item_schema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route response based on the type of request.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
public function get_response( \WP_REST_Request $request ) {
|
||||
$response = null;
|
||||
try {
|
||||
switch ( $request->get_method() ) {
|
||||
case 'POST':
|
||||
$response = $this->get_route_post_response( $request );
|
||||
break;
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
$response = $this->get_route_update_response( $request );
|
||||
break;
|
||||
case 'DELETE':
|
||||
$response = $this->get_route_delete_response( $request );
|
||||
break;
|
||||
default:
|
||||
$response = $this->get_route_response( $request );
|
||||
break;
|
||||
}
|
||||
} catch ( RouteException $error ) {
|
||||
$response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() );
|
||||
} catch ( InvalidStockLevelsInCartException $error ) {
|
||||
$response = $this->get_route_error_response_from_object( $error->getError(), $error->getCode(), $error->getAdditionalData() );
|
||||
} catch ( \Exception $error ) {
|
||||
$response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$response = $this->error_to_response( $response );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an error to a response object. Based on \WP_REST_Server.
|
||||
*
|
||||
* @param WP_Error $error WP_Error instance.
|
||||
* @return WP_REST_Response List of associative arrays with code and message keys.
|
||||
*/
|
||||
protected function error_to_response( $error ) {
|
||||
$error_data = $error->get_error_data();
|
||||
$status = isset( $error_data, $error_data['status'] ) ? $error_data['status'] : 500;
|
||||
$errors = [];
|
||||
|
||||
foreach ( (array) $error->errors as $code => $messages ) {
|
||||
foreach ( (array) $messages as $message ) {
|
||||
$errors[] = array(
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $error->get_error_data( $code ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$data = array_shift( $errors );
|
||||
|
||||
if ( count( $errors ) ) {
|
||||
$data['additional_errors'] = $errors;
|
||||
}
|
||||
|
||||
return new \WP_REST_Response( $data, $status );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response for GET requests.
|
||||
*
|
||||
* When implemented, should return a \WP_REST_Response.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response for POST requests.
|
||||
*
|
||||
* When implemented, should return a \WP_REST_Response.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response for PUT requests.
|
||||
*
|
||||
* When implemented, should return a \WP_REST_Response.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*/
|
||||
protected function get_route_update_response( \WP_REST_Request $request ) {
|
||||
throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response for DELETE requests.
|
||||
*
|
||||
* When implemented, should return a \WP_REST_Response.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*/
|
||||
protected function get_route_delete_response( \WP_REST_Request $request ) {
|
||||
throw new RouteException( 'woocommerce_rest_invalid_endpoint', __( 'Method not implemented', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response when something went wrong.
|
||||
*
|
||||
* @param string $error_code String based error code.
|
||||
* @param string $error_message User facing error message.
|
||||
* @param int $http_status_code HTTP status. Defaults to 500.
|
||||
* @param array $additional_data Extra data (key value pairs) to expose in the error response.
|
||||
* @return \WP_Error WP Error object.
|
||||
*/
|
||||
protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) {
|
||||
return new \WP_Error( $error_code, $error_message, array_merge( $additional_data, [ 'status' => $http_status_code ] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response when something went wrong and the supplied error is a WP_Error. This currently only happens
|
||||
* when an item in the cart is out of stock, partially out of stock, can only be bought individually, or when the
|
||||
* item is not purchasable.
|
||||
*
|
||||
* @param WP_Error $error_object The WP_Error object containing the error.
|
||||
* @param int $http_status_code HTTP status. Defaults to 500.
|
||||
* @param array $additional_data Extra data (key value pairs) to expose in the error response.
|
||||
* @return WP_Error WP Error object.
|
||||
*/
|
||||
protected function get_route_error_response_from_object( $error_object, $http_status_code = 500, $additional_data = [] ) {
|
||||
$error_object->add_data( array_merge( $additional_data, [ 'status' => $http_status_code ] ) );
|
||||
return $error_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single item for response.
|
||||
*
|
||||
* @param mixed $item Item to format to schema.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response $response Response data.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, \WP_REST_Request $request ) {
|
||||
$response = rest_ensure_response( $this->schema->get_item_response( $item ) );
|
||||
$response->add_links( $this->prepare_links( $item, $request ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the context param.
|
||||
*
|
||||
* Ensures consistent descriptions between endpoints, and populates enum from schema.
|
||||
*
|
||||
* @param array $args Optional. Additional arguments for context parameter. Default empty array.
|
||||
* @return array Context parameter details.
|
||||
*/
|
||||
protected function get_context_param( $args = array() ) {
|
||||
$param_details = array(
|
||||
'description' => __( 'Scope under which the request is made; determines fields present in response.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
if ( empty( $schema['properties'] ) ) {
|
||||
return array_merge( $param_details, $args );
|
||||
}
|
||||
|
||||
$contexts = array();
|
||||
|
||||
foreach ( $schema['properties'] as $attributes ) {
|
||||
if ( ! empty( $attributes['context'] ) ) {
|
||||
$contexts = array_merge( $contexts, $attributes['context'] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $contexts ) ) {
|
||||
$param_details['enum'] = array_unique( $contexts );
|
||||
rsort( $param_details['enum'] );
|
||||
}
|
||||
|
||||
return array_merge( $param_details, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a response for insertion into a collection.
|
||||
*
|
||||
* @param \WP_REST_Response $response Response object.
|
||||
* @return array|mixed Response data, ready for insertion into collection data.
|
||||
*/
|
||||
protected function prepare_response_for_collection( \WP_REST_Response $response ) {
|
||||
$data = (array) $response->get_data();
|
||||
$server = rest_get_server();
|
||||
$links = $server::get_compact_response_links( $response );
|
||||
|
||||
if ( ! empty( $links ) ) {
|
||||
$data['_links'] = $links;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param mixed $item Item to prepare.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $item, $request ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the collections.
|
||||
*
|
||||
* @return array Query parameters for the collection.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param(),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\Pagination;
|
||||
use WP_Term_Query;
|
||||
|
||||
/**
|
||||
* AbstractTermsRoute class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
abstract class AbstractTermsRoute extends AbstractRoute {
|
||||
/**
|
||||
* Get the query params for collections of attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = array();
|
||||
$params['context'] = $this->get_context_param();
|
||||
$params['context']['default'] = 'view';
|
||||
|
||||
$params['page'] = array(
|
||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'minimum' => 1,
|
||||
);
|
||||
|
||||
$params['per_page'] = array(
|
||||
'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 0,
|
||||
'maximum' => 100,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['search'] = array(
|
||||
'description' => __( 'Limit results to those matching a string.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['exclude'] = array(
|
||||
'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
|
||||
$params['include'] = array(
|
||||
'description' => __( 'Limit result set to specific ids.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
|
||||
$params['order'] = array(
|
||||
'description' => __( 'Sort ascending or descending.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'asc',
|
||||
'enum' => array( 'asc', 'desc' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['orderby'] = array(
|
||||
'description' => __( 'Sort by term property.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'name',
|
||||
'enum' => array(
|
||||
'name',
|
||||
'slug',
|
||||
'count',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['hide_empty'] = array(
|
||||
'description' => __( 'If true, empty terms will not be returned.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => true,
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get terms matching passed in args.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy to get terms from.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_terms_response( $taxonomy, $request ) {
|
||||
$page = (int) $request['page'];
|
||||
$per_page = $request['per_page'] ? (int) $request['per_page'] : 0;
|
||||
$prepared_args = array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'exclude' => $request['exclude'],
|
||||
'include' => $request['include'],
|
||||
'order' => $request['order'],
|
||||
'orderby' => $request['orderby'],
|
||||
'hide_empty' => (bool) $request['hide_empty'],
|
||||
'number' => $per_page,
|
||||
'offset' => $per_page > 0 ? ( $page - 1 ) * $per_page : 0,
|
||||
'search' => $request['search'],
|
||||
);
|
||||
|
||||
$term_query = new WP_Term_Query();
|
||||
$objects = $term_query->query( $prepared_args );
|
||||
$return = [];
|
||||
|
||||
foreach ( $objects as $object ) {
|
||||
$data = $this->prepare_item_for_response( $object, $request );
|
||||
$return[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $return );
|
||||
|
||||
// See if pagination is needed before calculating.
|
||||
if ( $per_page > 0 && ( count( $objects ) === $per_page || $page > 1 ) ) {
|
||||
$term_count = $this->get_term_count( $taxonomy, $prepared_args );
|
||||
$response = ( new Pagination() )->add_headers( $response, $request, $term_count, ceil( $term_count / $per_page ) );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of terms for current query.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy to get terms from.
|
||||
* @param array $args Array of args to pass to wp_count_terms.
|
||||
* @return int
|
||||
*/
|
||||
protected function get_term_count( $taxonomy, $args ) {
|
||||
$count_args = $args;
|
||||
unset( $count_args['number'], $count_args['offset'] );
|
||||
return (int) wp_count_terms( $taxonomy, $count_args );
|
||||
}
|
||||
}
|
117
packages/woocommerce-blocks/src/StoreApi/Routes/Batch.php
Normal file
117
packages/woocommerce-blocks/src/StoreApi/Routes/Batch.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Batch Route class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class Batch extends AbstractRoute implements RouteInterface {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/batch';
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Get arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return array(
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'methods' => 'POST',
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'validation' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'require-all-validate', 'normal' ),
|
||||
'default' => 'normal',
|
||||
),
|
||||
'requests' => array(
|
||||
'required' => true,
|
||||
'type' => 'array',
|
||||
'maxItems' => 25,
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'method' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'POST', 'PUT', 'PATCH', 'DELETE' ),
|
||||
'default' => 'POST',
|
||||
),
|
||||
'path' => array(
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'body' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(),
|
||||
'additionalProperties' => true,
|
||||
),
|
||||
'headers' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(),
|
||||
'additionalProperties' => array(
|
||||
'type' => array( 'string', 'array' ),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route response.
|
||||
*
|
||||
* @see WP_REST_Server::serve_batch_request_v1
|
||||
* https://developer.wordpress.org/reference/classes/wp_rest_server/serve_batch_request_v1/
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function get_response( WP_REST_Request $request ) {
|
||||
try {
|
||||
foreach ( $request['requests'] as $args ) {
|
||||
if ( ! stristr( $args['path'], 'wc/store' ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_invalid_path', __( 'Invalid path provided.', 'woocommerce' ), 400 );
|
||||
}
|
||||
}
|
||||
$response = rest_get_server()->serve_batch_request_v1( $request );
|
||||
} catch ( RouteException $error ) {
|
||||
$response = $this->get_route_error_response( $error->getErrorCode(), $error->getMessage(), $error->getCode(), $error->getAdditionalData() );
|
||||
} catch ( \Exception $error ) {
|
||||
$response = $this->get_route_error_response( 'unknown_server_error', $error->getMessage(), 500 );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$response = $this->error_to_response( $response );
|
||||
}
|
||||
|
||||
$response->header( 'X-WC-Store-API-Nonce', wp_create_nonce( 'wc_store_api' ) );
|
||||
$response->header( 'X-WC-Store-API-Nonce-Timestamp', time() );
|
||||
$response->header( 'X-WC-Store-API-User', get_current_user_id() );
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
48
packages/woocommerce-blocks/src/StoreApi/Routes/Cart.php
Normal file
48
packages/woocommerce-blocks/src/StoreApi/Routes/Cart.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* Cart class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class Cart extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
return rest_ensure_response( $this->schema->get_item_response( $this->cart_controller->get_cart_instance() ) );
|
||||
}
|
||||
}
|
100
packages/woocommerce-blocks/src/StoreApi/Routes/CartAddItem.php
Normal file
100
packages/woocommerce-blocks/src/StoreApi/Routes/CartAddItem.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartAddItem class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartAddItem extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/add-item';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'id' => [
|
||||
'description' => __( 'The cart item product or variation ID.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'arg_options' => [
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
],
|
||||
'quantity' => [
|
||||
'description' => __( 'Quantity of this item in the cart.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'arg_options' => [
|
||||
'sanitize_callback' => 'wc_stock_amount',
|
||||
],
|
||||
],
|
||||
'variation' => [
|
||||
'description' => __( 'Chosen attributes (for variations).', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'attribute' => [
|
||||
'description' => __( 'Variation attribute name.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
'value' => [
|
||||
'description' => __( 'Variation attribute value.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
// Do not allow key to be specified during creation.
|
||||
if ( ! empty( $request['key'] ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_item_exists', __( 'Cannot create an existing cart item.', 'woocommerce' ), 400 );
|
||||
}
|
||||
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$result = $this->cart_controller->add_to_cart(
|
||||
[
|
||||
'id' => $request['id'],
|
||||
'quantity' => $request['quantity'],
|
||||
'variation' => $request['variation'],
|
||||
]
|
||||
);
|
||||
|
||||
$response = rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
$response->set_status( 201 );
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartApplyCoupon class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartApplyCoupon extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/apply-coupon';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'code' => [
|
||||
'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
if ( ! wc_coupons_enabled() ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$coupon_code = wc_format_coupon_code( wp_unslash( $request['code'] ) );
|
||||
|
||||
try {
|
||||
$this->cart_controller->apply_coupon( $coupon_code );
|
||||
} catch ( \WC_REST_Exception $e ) {
|
||||
throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
}
|
||||
}
|
133
packages/woocommerce-blocks/src/StoreApi/Routes/CartCoupons.php
Normal file
133
packages/woocommerce-blocks/src/StoreApi/Routes/CartCoupons.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartCoupons class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartCoupons extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/coupons';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
|
||||
],
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'permission_callback' => '__return_true',
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of cart coupons.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$cart_coupons = $this->cart_controller->get_cart_coupons();
|
||||
$items = [];
|
||||
|
||||
foreach ( $cart_coupons as $coupon_code ) {
|
||||
$response = rest_ensure_response( $this->schema->get_item_response( $coupon_code ) );
|
||||
$response->add_links( $this->prepare_links( $coupon_code, $request ) );
|
||||
|
||||
$response = $this->prepare_response_for_collection( $response );
|
||||
$items[] = $response;
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $items );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a coupon to the cart and return the result.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
if ( ! wc_coupons_enabled() ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
try {
|
||||
$this->cart_controller->apply_coupon( $request['code'] );
|
||||
} catch ( \WC_REST_Exception $e ) {
|
||||
throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() );
|
||||
}
|
||||
|
||||
$response = $this->prepare_item_for_response( $request['code'], $request );
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all coupons in the cart.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_delete_response( \WP_REST_Request $request ) {
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
|
||||
$cart->remove_coupons();
|
||||
$cart->calculate_totals();
|
||||
|
||||
return new \WP_REST_Response( [], 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param string $coupon_code Coupon code.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $coupon_code, $request ) {
|
||||
$base = $this->get_namespace() . $this->get_path();
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( trailingslashit( $base ) . $coupon_code ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( $base ),
|
||||
),
|
||||
);
|
||||
return $links;
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartCouponsByCode class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartCouponsByCode extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/coupons/(?P<code>[\w-]+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
'args' => [
|
||||
'code' => [
|
||||
'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
|
||||
],
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single cart coupon.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
if ( ! $this->cart_controller->has_coupon( $request['code'] ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $request['code'], $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single cart coupon.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_delete_response( \WP_REST_Request $request ) {
|
||||
if ( ! $this->cart_controller->has_coupon( $request['code'] ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon does not exist in the cart.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
|
||||
$cart->remove_coupon( $request['code'] );
|
||||
$cart->calculate_totals();
|
||||
|
||||
return new \WP_REST_Response( null, 204 );
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartExtensions class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartExtensions extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/extensions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'namespace' => [
|
||||
'description' => __( 'Extension\'s name - this will be used to ensure the data in the request is routed appropriately.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'data' => [
|
||||
'description' => __( 'Additional data to pass to the extension', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
try {
|
||||
return $this->schema->get_item_response( $request );
|
||||
} catch ( \WC_REST_Exception $e ) {
|
||||
throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() );
|
||||
}
|
||||
}
|
||||
}
|
128
packages/woocommerce-blocks/src/StoreApi/Routes/CartItems.php
Normal file
128
packages/woocommerce-blocks/src/StoreApi/Routes/CartItems.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartItems class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartItems extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/items';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
|
||||
],
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'get_response' ),
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of cart items.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$cart_items = $this->cart_controller->get_cart_items();
|
||||
$items = [];
|
||||
|
||||
foreach ( $cart_items as $cart_item ) {
|
||||
$data = $this->prepare_item_for_response( $cart_item, $request );
|
||||
$items[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $items );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates one item from the collection.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
// Do not allow key to be specified during creation.
|
||||
if ( ! empty( $request['key'] ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_item_exists', __( 'Cannot create an existing cart item.', 'woocommerce' ), 400 );
|
||||
}
|
||||
|
||||
$result = $this->cart_controller->add_to_cart(
|
||||
[
|
||||
'id' => $request['id'],
|
||||
'quantity' => $request['quantity'],
|
||||
'variation' => $request['variation'],
|
||||
]
|
||||
);
|
||||
|
||||
$response = rest_ensure_response( $this->prepare_item_for_response( $this->cart_controller->get_cart_item( $result ), $request ) );
|
||||
$response->set_status( 201 );
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all items in the cart.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_delete_response( \WP_REST_Request $request ) {
|
||||
$this->cart_controller->empty_cart();
|
||||
return new \WP_REST_Response( [], 200 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param array $cart_item Object to prepare.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $cart_item, $request ) {
|
||||
$base = $this->get_namespace() . $this->get_path();
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( trailingslashit( $base ) . $cart_item['key'] ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( $base ),
|
||||
),
|
||||
);
|
||||
return $links;
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartItemsByKey class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartItemsByKey extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/items/(?P<key>[\w-]{32})';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
'args' => [
|
||||
'key' => [
|
||||
'description' => __( 'Unique identifier for the item within the cart.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
|
||||
],
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'get_response' ),
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::DELETABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single cart items.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$cart_item = $this->cart_controller->get_cart_item( $request['key'] );
|
||||
|
||||
if ( empty( $cart_item ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
$data = $this->prepare_item_for_response( $cart_item, $request );
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a single cart item.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_update_response( \WP_REST_Request $request ) {
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
|
||||
if ( isset( $request['quantity'] ) ) {
|
||||
$this->cart_controller->set_cart_item_quantity( $request['key'], $request['quantity'] );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->prepare_item_for_response( $this->cart_controller->get_cart_item( $request['key'] ), $request ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single cart item.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_delete_response( \WP_REST_Request $request ) {
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$cart_item = $this->cart_controller->get_cart_item( $request['key'] );
|
||||
|
||||
if ( empty( $cart_item ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item does not exist.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
$cart->remove_cart_item( $request['key'] );
|
||||
|
||||
return new \WP_REST_Response( null, 204 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param array $cart_item Object to prepare.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $cart_item, $request ) {
|
||||
$base = $this->get_namespace() . $this->get_path();
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( trailingslashit( $base ) . $cart_item['key'] ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( $base ),
|
||||
),
|
||||
);
|
||||
return $links;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartRemoveCoupon class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartRemoveCoupon extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/remove-coupon';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'code' => [
|
||||
'description' => __( 'Unique identifier for the coupon within the cart.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
if ( ! wc_coupons_enabled() ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_coupon_disabled', __( 'Coupons are disabled.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$coupon_code = wc_format_coupon_code( $request['code'] );
|
||||
$coupon = new \WC_Coupon( $coupon_code );
|
||||
|
||||
if ( $coupon->get_code() !== $coupon_code || ! $coupon->is_valid() ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_coupon_error', __( 'Invalid coupon code.', 'woocommerce' ), 400 );
|
||||
}
|
||||
|
||||
if ( ! $this->cart_controller->has_coupon( $coupon_code ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_coupon_invalid_code', __( 'Coupon cannot be removed because it is not already applied to the cart.', 'woocommerce' ), 409 );
|
||||
}
|
||||
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$cart->remove_coupon( $coupon_code );
|
||||
$cart->calculate_totals();
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartRemoveItem class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartRemoveItem extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/remove-item';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'key' => [
|
||||
'description' => __( 'Unique identifier (key) for the cart item.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$cart_item = $this->cart_controller->get_cart_item( $request['key'] );
|
||||
|
||||
if ( empty( $cart_item ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_invalid_key', __( 'Cart item no longer exists or is invalid.', 'woocommerce' ), 409 );
|
||||
}
|
||||
|
||||
$cart->remove_cart_item( $request['key'] );
|
||||
$this->maybe_release_stock();
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartSelectShippingRate class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartSelectShippingRate extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/select-shipping-rate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'package_id' => array(
|
||||
'description' => __( 'The ID of the package being shipped.', 'woocommerce' ),
|
||||
'type' => [ 'integer', 'string' ],
|
||||
'required' => true,
|
||||
),
|
||||
'rate_id' => [
|
||||
'description' => __( 'The chosen rate ID for the package.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
if ( ! wc_shipping_enabled() ) {
|
||||
throw new RouteException( 'woocommerce_rest_shipping_disabled', __( 'Shipping is disabled.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
if ( ! isset( $request['package_id'] ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_cart_missing_package_id', __( 'Invalid Package ID.', 'woocommerce' ), 400 );
|
||||
}
|
||||
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$package_id = wc_clean( wp_unslash( $request['package_id'] ) );
|
||||
$rate_id = wc_clean( wp_unslash( $request['rate_id'] ) );
|
||||
|
||||
try {
|
||||
$this->cart_controller->select_shipping_rate( $package_id, $rate_id );
|
||||
} catch ( \WC_Rest_Exception $e ) {
|
||||
throw new RouteException( $e->getErrorCode(), $e->getMessage(), $e->getCode() );
|
||||
}
|
||||
|
||||
$cart->calculate_shipping();
|
||||
$cart->calculate_totals();
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\BillingAddressSchema;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\ShippingAddressSchema;
|
||||
|
||||
/**
|
||||
* CartUpdateCustomer class.
|
||||
*
|
||||
* Updates the customer billing and shipping address and returns an updated cart--things such as taxes may be recalculated.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartUpdateCustomer extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the namespace for this route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace() {
|
||||
return 'wc/store';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/update-customer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'billing_address' => [
|
||||
'description' => __( 'Billing address.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'properties' => $this->schema->billing_address_schema->get_properties(),
|
||||
'sanitize_callback' => [ $this->schema->billing_address_schema, 'sanitize_callback' ],
|
||||
'validate_callback' => [ $this->schema->billing_address_schema, 'validate_callback' ],
|
||||
],
|
||||
'shipping_address' => [
|
||||
'description' => __( 'Shipping address.', 'woocommerce' ),
|
||||
'type' => 'object',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'properties' => $this->schema->shipping_address_schema->get_properties(),
|
||||
'sanitize_callback' => [ $this->schema->shipping_address_schema, 'sanitize_callback' ],
|
||||
'validate_callback' => [ $this->schema->shipping_address_schema, 'validate_callback' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
$billing = isset( $request['billing_address'] ) ? $request['billing_address'] : [];
|
||||
$shipping = isset( $request['shipping_address'] ) ? $request['shipping_address'] : [];
|
||||
|
||||
// If the cart does not need shipping, shipping address is forced to match billing address unless defined.
|
||||
if ( ! $cart->needs_shipping() && ! isset( $request['shipping_address'] ) ) {
|
||||
$shipping = isset( $request['billing_address'] ) ? $request['billing_address'] : [
|
||||
'first_name' => wc()->customer->get_billing_first_name(),
|
||||
'last_name' => wc()->customer->get_billing_last_name(),
|
||||
'company' => wc()->customer->get_billing_company(),
|
||||
'address_1' => wc()->customer->get_billing_address_1(),
|
||||
'address_2' => wc()->customer->get_billing_address_2(),
|
||||
'city' => wc()->customer->get_billing_city(),
|
||||
'state' => wc()->customer->get_billing_state(),
|
||||
'postcode' => wc()->customer->get_billing_postcode(),
|
||||
'country' => wc()->customer->get_billing_country(),
|
||||
'phone' => wc()->customer->get_billing_phone(),
|
||||
];
|
||||
}
|
||||
wc()->customer->set_props(
|
||||
array(
|
||||
'billing_first_name' => isset( $billing['first_name'] ) ? $billing['first_name'] : null,
|
||||
'billing_last_name' => isset( $billing['last_name'] ) ? $billing['last_name'] : null,
|
||||
'billing_company' => isset( $billing['company'] ) ? $billing['company'] : null,
|
||||
'billing_address_1' => isset( $billing['address_1'] ) ? $billing['address_1'] : null,
|
||||
'billing_address_2' => isset( $billing['address_2'] ) ? $billing['address_2'] : null,
|
||||
'billing_city' => isset( $billing['city'] ) ? $billing['city'] : null,
|
||||
'billing_state' => isset( $billing['state'] ) ? $billing['state'] : null,
|
||||
'billing_postcode' => isset( $billing['postcode'] ) ? $billing['postcode'] : null,
|
||||
'billing_country' => isset( $billing['country'] ) ? $billing['country'] : null,
|
||||
'billing_phone' => isset( $billing['phone'] ) ? $billing['phone'] : null,
|
||||
'billing_email' => isset( $request['billing_address'], $request['billing_address']['email'] ) ? $request['billing_address']['email'] : null,
|
||||
'shipping_first_name' => isset( $shipping['first_name'] ) ? $shipping['first_name'] : null,
|
||||
'shipping_last_name' => isset( $shipping['last_name'] ) ? $shipping['last_name'] : null,
|
||||
'shipping_company' => isset( $shipping['company'] ) ? $shipping['company'] : null,
|
||||
'shipping_address_1' => isset( $shipping['address_1'] ) ? $shipping['address_1'] : null,
|
||||
'shipping_address_2' => isset( $shipping['address_2'] ) ? $shipping['address_2'] : null,
|
||||
'shipping_city' => isset( $shipping['city'] ) ? $shipping['city'] : null,
|
||||
'shipping_state' => isset( $shipping['state'] ) ? $shipping['state'] : null,
|
||||
'shipping_postcode' => isset( $shipping['postcode'] ) ? $shipping['postcode'] : null,
|
||||
'shipping_country' => isset( $shipping['country'] ) ? $shipping['country'] : null,
|
||||
)
|
||||
);
|
||||
|
||||
$shipping_phone_value = isset( $shipping['phone'] ) ? $shipping['phone'] : null;
|
||||
|
||||
// @todo Remove custom shipping_phone handling (requires WC 5.6+)
|
||||
if ( is_callable( [ wc()->customer, 'set_shipping_phone' ] ) ) {
|
||||
wc()->customer->set_shipping_phone( $shipping_phone_value );
|
||||
} else {
|
||||
wc()->customer->update_meta_data( 'shipping_phone', $shipping_phone_value );
|
||||
}
|
||||
|
||||
wc()->customer->save();
|
||||
|
||||
$this->calculate_totals();
|
||||
$this->maybe_update_order();
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a draft order, update customer data there also.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_update_order() {
|
||||
$draft_order_id = wc()->session->get( 'store_api_draft_order', 0 );
|
||||
$draft_order = $draft_order_id ? wc_get_order( $draft_order_id ) : false;
|
||||
|
||||
if ( ! $draft_order ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$draft_order->set_props(
|
||||
[
|
||||
'billing_first_name' => wc()->customer->get_billing_first_name(),
|
||||
'billing_last_name' => wc()->customer->get_billing_last_name(),
|
||||
'billing_company' => wc()->customer->get_billing_company(),
|
||||
'billing_address_1' => wc()->customer->get_billing_address_1(),
|
||||
'billing_address_2' => wc()->customer->get_billing_address_2(),
|
||||
'billing_city' => wc()->customer->get_billing_city(),
|
||||
'billing_state' => wc()->customer->get_billing_state(),
|
||||
'billing_postcode' => wc()->customer->get_billing_postcode(),
|
||||
'billing_country' => wc()->customer->get_billing_country(),
|
||||
'billing_email' => wc()->customer->get_billing_email(),
|
||||
'billing_phone' => wc()->customer->get_billing_phone(),
|
||||
'shipping_first_name' => wc()->customer->get_shipping_first_name(),
|
||||
'shipping_last_name' => wc()->customer->get_shipping_last_name(),
|
||||
'shipping_company' => wc()->customer->get_shipping_company(),
|
||||
'shipping_address_1' => wc()->customer->get_shipping_address_1(),
|
||||
'shipping_address_2' => wc()->customer->get_shipping_address_2(),
|
||||
'shipping_city' => wc()->customer->get_shipping_city(),
|
||||
'shipping_state' => wc()->customer->get_shipping_state(),
|
||||
'shipping_postcode' => wc()->customer->get_shipping_postcode(),
|
||||
'shipping_country' => wc()->customer->get_shipping_country(),
|
||||
]
|
||||
);
|
||||
|
||||
$shipping_phone_value = is_callable( [ wc()->customer, 'get_shipping_phone' ] ) ? wc()->customer->get_shipping_phone() : wc()->customer->get_meta( 'shipping_phone', true );
|
||||
|
||||
if ( is_callable( [ $draft_order, 'set_shipping_phone' ] ) ) {
|
||||
$draft_order->set_shipping_phone( $shipping_phone_value );
|
||||
} else {
|
||||
$draft_order->update_meta_data( '_shipping_phone', $shipping_phone_value );
|
||||
}
|
||||
|
||||
$draft_order->save();
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* CartUpdateItem class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class CartUpdateItem extends AbstractCartRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/cart/update-item';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'key' => [
|
||||
'description' => __( 'Unique identifier (key) for the cart item to update.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'quantity' => [
|
||||
'description' => __( 'New quantity of the item in the cart.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
],
|
||||
],
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the request and return a valid response for this endpoint.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
$cart = $this->cart_controller->get_cart_instance();
|
||||
|
||||
if ( isset( $request['quantity'] ) ) {
|
||||
$this->cart_controller->set_cart_item_quantity( $request['key'], $request['quantity'] );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $cart ) );
|
||||
}
|
||||
}
|
635
packages/woocommerce-blocks/src/StoreApi/Routes/Checkout.php
Normal file
635
packages/woocommerce-blocks/src/StoreApi/Routes/Checkout.php
Normal file
@ -0,0 +1,635 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\InvalidStockLevelsInCartException;
|
||||
use Automattic\WooCommerce\Blocks\Package;
|
||||
use Automattic\WooCommerce\Blocks\Domain\Services\CreateAccount;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\AbstractSchema;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Schemas\CartSchema;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\CartController;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\OrderController;
|
||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStock;
|
||||
use Automattic\WooCommerce\Checkout\Helpers\ReserveStockException;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentResult;
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentContext;
|
||||
|
||||
/**
|
||||
* Checkout class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class Checkout extends AbstractCartRoute {
|
||||
/**
|
||||
* Holds the current order being processed.
|
||||
*
|
||||
* @var \WC_Order
|
||||
*/
|
||||
private $order = null;
|
||||
|
||||
/**
|
||||
* Order controller class instance.
|
||||
*
|
||||
* @var OrderController
|
||||
*/
|
||||
protected $order_controller;
|
||||
|
||||
/**
|
||||
* Constructor accepts two types of schema; one for the item being returned, and one for the cart as a whole. These
|
||||
* may be the same depending on the route.
|
||||
*
|
||||
* @param CartSchema $cart_schema Schema class for the cart.
|
||||
* @param AbstractSchema $item_schema Schema class for this route's items if it differs from the cart schema.
|
||||
* @param CartController $cart_controller Cart controller class.
|
||||
* @param OrderController $order_controller Order controller class.
|
||||
*/
|
||||
public function __construct( CartSchema $cart_schema, AbstractSchema $item_schema = null, CartController $cart_controller, OrderController $order_controller ) {
|
||||
$this->schema = is_null( $item_schema ) ? $cart_schema : $item_schema;
|
||||
$this->cart_schema = $cart_schema;
|
||||
$this->cart_controller = $cart_controller;
|
||||
$this->order_controller = $order_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/checkout';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a nonce is required for the route.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request.
|
||||
* @return bool
|
||||
*/
|
||||
protected function requires_nonce( \WP_REST_Request $request ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'context' => $this->get_context_param( [ 'default' => 'view' ] ),
|
||||
],
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::CREATABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array_merge(
|
||||
[
|
||||
'payment_data' => [
|
||||
'description' => __( 'Data to pass through to the payment method when processing payment.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'key' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
'value' => [
|
||||
'type' => [ 'string', 'boolean' ],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
$this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE )
|
||||
),
|
||||
],
|
||||
[
|
||||
'methods' => \WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'get_response' ),
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->schema->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
'allow_batch' => [ 'v1' => true ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single item for response. Handles setting the status based on the payment result.
|
||||
*
|
||||
* @param mixed $item Item to format to schema.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response $response Response data.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, \WP_REST_Request $request ) {
|
||||
$response = parent::prepare_item_for_response( $item, $request );
|
||||
$status_codes = [
|
||||
'success' => 200,
|
||||
'pending' => 202,
|
||||
'failure' => 400,
|
||||
'error' => 500,
|
||||
];
|
||||
|
||||
if ( isset( $item->payment_result ) && $item->payment_result instanceof PaymentResult ) {
|
||||
$response->set_status( $status_codes[ $item->payment_result->status ] ?? 200 );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the cart into a new draft order, or update an existing draft order, and return an updated cart response.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$this->create_or_update_draft_order();
|
||||
|
||||
return $this->prepare_item_for_response(
|
||||
(object) [
|
||||
'order' => $this->order,
|
||||
'payment_result' => new PaymentResult(),
|
||||
],
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current order.
|
||||
*
|
||||
* @internal Customer data is updated first so OrderController::update_addresses_from_cart uses up to date data.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_update_response( \WP_REST_Request $request ) {
|
||||
$this->update_customer_from_request( $request );
|
||||
$this->create_or_update_draft_order();
|
||||
$this->update_order_from_request( $request );
|
||||
|
||||
return $this->prepare_item_for_response(
|
||||
(object) [
|
||||
'order' => $this->order,
|
||||
'payment_result' => new PaymentResult(),
|
||||
],
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update and process an order.
|
||||
*
|
||||
* 1. Obtain Draft Order
|
||||
* 2. Process Request
|
||||
* 3. Process Customer
|
||||
* 4. Validate Order
|
||||
* 5. Process Payment
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @throws InvalidStockLevelsInCartException On error.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_post_response( \WP_REST_Request $request ) {
|
||||
/**
|
||||
* Validate items etc are allowed in the order before the order is processed. This will fix violations and tell
|
||||
* the customer.
|
||||
*/
|
||||
$this->cart_controller->validate_cart_items();
|
||||
$this->cart_controller->validate_cart_coupons();
|
||||
|
||||
/**
|
||||
* Obtain Draft Order and process request data.
|
||||
*
|
||||
* Note: Customer data is persisted from the request first so that OrderController::update_addresses_from_cart
|
||||
* uses the up to date customer address.
|
||||
*/
|
||||
$this->update_customer_from_request( $request );
|
||||
$this->create_or_update_draft_order();
|
||||
$this->update_order_from_request( $request );
|
||||
|
||||
/**
|
||||
* Process customer data.
|
||||
*
|
||||
* Update order with customer details, and sign up a user account as necessary.
|
||||
*/
|
||||
$this->process_customer( $request );
|
||||
|
||||
/**
|
||||
* Validate order.
|
||||
*
|
||||
* This logic ensures the order is valid before payment is attempted.
|
||||
*/
|
||||
$this->order_controller->validate_order_before_payment( $this->order );
|
||||
|
||||
/**
|
||||
* WooCommerce Blocks Checkout Order Processed (experimental).
|
||||
*
|
||||
* This hook informs extensions that $order has completed processing and is ready for payment.
|
||||
*
|
||||
* This is similar to existing core hook woocommerce_checkout_order_processed. We're using a new action:
|
||||
* - To keep the interface focused (only pass $order, not passing request data).
|
||||
* - This also explicitly indicates these orders are from checkout block/StoreAPI.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3238
|
||||
* @internal This Hook is experimental and may change or be removed.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
do_action( '__experimental_woocommerce_blocks_checkout_order_processed', $this->order );
|
||||
|
||||
/**
|
||||
* Process the payment and return the results.
|
||||
*/
|
||||
$payment_result = new PaymentResult();
|
||||
|
||||
if ( $this->order->needs_payment() ) {
|
||||
$this->process_payment( $request, $payment_result );
|
||||
} else {
|
||||
$this->process_without_payment( $request, $payment_result );
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response(
|
||||
(object) [
|
||||
'order' => wc_get_order( $this->order ),
|
||||
'payment_result' => $payment_result,
|
||||
],
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response when something went wrong.
|
||||
*
|
||||
* @param string $error_code String based error code.
|
||||
* @param string $error_message User facing error message.
|
||||
* @param int $http_status_code HTTP status. Defaults to 500.
|
||||
* @param array $additional_data Extra data (key value pairs) to expose in the error response.
|
||||
* @return \WP_Error WP Error object.
|
||||
*/
|
||||
protected function get_route_error_response( $error_code, $error_message, $http_status_code = 500, $additional_data = [] ) {
|
||||
$error_from_message = new \WP_Error(
|
||||
$error_code,
|
||||
$error_message
|
||||
);
|
||||
// 409 is when there was a conflict, so we return the cart so the client can resolve it.
|
||||
if ( 409 === $http_status_code ) {
|
||||
return $this->add_data_to_error_object( $error_from_message, $additional_data, $http_status_code, true );
|
||||
}
|
||||
return $this->add_data_to_error_object( $error_from_message, $additional_data, $http_status_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route response when something went wrong.
|
||||
*
|
||||
* @param \WP_Error $error_object User facing error message.
|
||||
* @param int $http_status_code HTTP status. Defaults to 500.
|
||||
* @param array $additional_data Extra data (key value pairs) to expose in the error response.
|
||||
* @return \WP_Error WP Error object.
|
||||
*/
|
||||
protected function get_route_error_response_from_object( $error_object, $http_status_code = 500, $additional_data = [] ) {
|
||||
// 409 is when there was a conflict, so we return the cart so the client can resolve it.
|
||||
if ( 409 === $http_status_code ) {
|
||||
return $this->add_data_to_error_object( $error_object, $additional_data, $http_status_code, true );
|
||||
}
|
||||
return $this->add_data_to_error_object( $error_object, $additional_data, $http_status_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional data to the \WP_Error object.
|
||||
*
|
||||
* @param \WP_Error $error The error object to add the cart to.
|
||||
* @param array $data The data to add to the error object.
|
||||
* @param int $http_status_code The HTTP status code this error should return.
|
||||
* @param bool $include_cart Whether the cart should be included in the error data.
|
||||
* @returns \WP_Error The \WP_Error with the cart added.
|
||||
*/
|
||||
private function add_data_to_error_object( $error, $data, $http_status_code, bool $include_cart = false ) {
|
||||
$data = array_merge( $data, [ 'status' => $http_status_code ] );
|
||||
if ( $include_cart ) {
|
||||
$data = array_merge( $data, [ 'cart' => wc()->api->get_endpoint_data( '/wc/store/cart' ) ] );
|
||||
}
|
||||
$error->add_data( $data );
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets draft order data from the customer session.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_draft_order_id() {
|
||||
return wc()->session->get( 'store_api_draft_order', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates draft order data in the customer session.
|
||||
*
|
||||
* @param integer $order_id Draft order ID.
|
||||
*/
|
||||
private function set_draft_order_id( $order_id ) {
|
||||
wc()->session->set( 'store_api_draft_order', $order_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the passed argument is a draft order or an order that is
|
||||
* pending/failed and the cart hasn't changed.
|
||||
*
|
||||
* @param \WC_Order $order_object Order object to check.
|
||||
* @return boolean Whether the order is valid as a draft order.
|
||||
*/
|
||||
private function is_valid_draft_order( $order_object ) {
|
||||
if ( ! $order_object instanceof \WC_Order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Draft orders are okay.
|
||||
if ( $order_object->has_status( 'checkout-draft' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pending and failed orders can be retried if the cart hasn't changed.
|
||||
if ( $order_object->needs_payment() && $order_object->has_cart_hash( wc()->cart->get_cart_hash() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a draft order based on the cart.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
*/
|
||||
private function create_or_update_draft_order() {
|
||||
$this->order = $this->get_draft_order_id() ? wc_get_order( $this->get_draft_order_id() ) : null;
|
||||
|
||||
if ( ! $this->is_valid_draft_order( $this->order ) ) {
|
||||
$this->order = $this->order_controller->create_order_from_cart();
|
||||
} else {
|
||||
$this->order_controller->update_order_from_cart( $this->order );
|
||||
}
|
||||
|
||||
/**
|
||||
* WooCommerce Blocks Checkout Update Order Meta (experimental).
|
||||
*
|
||||
* This hook gives extensions the chance to add or update meta data on the $order.
|
||||
*
|
||||
* This is similar to existing core hook woocommerce_checkout_update_order_meta.
|
||||
* We're using a new action:
|
||||
* - To keep the interface focused (only pass $order, not passing request data).
|
||||
* - This also explicitly indicates these orders are from checkout block/StoreAPI.
|
||||
*
|
||||
* @see https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/3686
|
||||
* @internal This Hook is experimental and may change or be removed.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
do_action( '__experimental_woocommerce_blocks_checkout_update_order_meta', $this->order );
|
||||
|
||||
// Confirm order is valid before proceeding further.
|
||||
if ( ! $this->order instanceof \WC_Order ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_checkout_missing_order',
|
||||
__( 'Unable to create order', 'woocommerce' ),
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
// Store order ID to session.
|
||||
$this->set_draft_order_id( $this->order->get_id() );
|
||||
|
||||
// Try to reserve stock for 10 mins, if available.
|
||||
try {
|
||||
$reserve_stock = new ReserveStock();
|
||||
$reserve_stock->reserve_stock_for_order( $this->order, 10 );
|
||||
} catch ( ReserveStockException $e ) {
|
||||
$error_data = $e->getErrorData();
|
||||
throw new RouteException(
|
||||
$e->getErrorCode(),
|
||||
$e->getMessage(),
|
||||
$e->getCode()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current customer session using data from the request (e.g. address data).
|
||||
*
|
||||
* Address session data is synced to the order itself later on by OrderController::update_order_from_cart()
|
||||
*
|
||||
* @param \WP_REST_Request $request Full details about the request.
|
||||
*/
|
||||
private function update_customer_from_request( \WP_REST_Request $request ) {
|
||||
$customer = wc()->customer;
|
||||
|
||||
if ( isset( $request['billing_address'] ) ) {
|
||||
foreach ( $request['billing_address'] as $key => $value ) {
|
||||
if ( is_callable( [ $customer, "set_billing_$key" ] ) ) {
|
||||
$customer->{"set_billing_$key"}( $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request['shipping_address'] ) ) {
|
||||
foreach ( $request['shipping_address'] as $key => $value ) {
|
||||
if ( is_callable( [ $customer, "set_shipping_$key" ] ) ) {
|
||||
$customer->{"set_shipping_$key"}( $value );
|
||||
} elseif ( 'phone' === $key ) {
|
||||
$customer->update_meta_data( 'shipping_phone', $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$customer->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current order using the posted values from the request.
|
||||
*
|
||||
* @param \WP_REST_Request $request Full details about the request.
|
||||
*/
|
||||
private function update_order_from_request( \WP_REST_Request $request ) {
|
||||
$this->order->set_customer_note( $request['customer_note'] ?? '' );
|
||||
$this->order->set_payment_method( $this->get_request_payment_method( $request ) );
|
||||
|
||||
/**
|
||||
* WooCommerce Blocks Checkout Update Order From Request (experimental).
|
||||
*
|
||||
* This hook gives extensions the chance to update orders based on the data in the request. This can be used in
|
||||
* conjunction with the ExtendRestAPI class to post custom data and then process it.
|
||||
*
|
||||
* @internal This Hook is experimental and may change or be removed.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @param \WP_REST_Request $request Full details about the request.
|
||||
*/
|
||||
do_action( '__experimental_woocommerce_blocks_checkout_update_order_from_request', $this->order, $request );
|
||||
|
||||
$this->order->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* For orders which do not require payment, just update status.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @param PaymentResult $payment_result Payment result object.
|
||||
*/
|
||||
private function process_without_payment( \WP_REST_Request $request, PaymentResult $payment_result ) {
|
||||
// Transition the order to pending, and then completed. This ensures transactional emails fire for pending_to_complete events.
|
||||
$this->order->update_status( 'pending' );
|
||||
$this->order->payment_complete();
|
||||
|
||||
// Mark the payment as successful.
|
||||
$payment_result->set_status( 'success' );
|
||||
$payment_result->set_redirect_url( $this->order->get_checkout_order_received_url() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires an action hook instructing active payment gateways to process the payment for an order and provide a result.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @param PaymentResult $payment_result Payment result object.
|
||||
*/
|
||||
private function process_payment( \WP_REST_Request $request, PaymentResult $payment_result ) {
|
||||
try {
|
||||
// Transition the order to pending before making payment.
|
||||
$this->order->update_status( 'pending' );
|
||||
|
||||
// Prepare the payment context object to pass through payment hooks.
|
||||
$context = new PaymentContext();
|
||||
$context->set_payment_method( $this->get_request_payment_method_id( $request ) );
|
||||
$context->set_payment_data( $this->get_request_payment_data( $request ) );
|
||||
$context->set_order( $this->order );
|
||||
|
||||
/**
|
||||
* Process payment with context.
|
||||
*
|
||||
* @hook woocommerce_rest_checkout_process_payment_with_context
|
||||
*
|
||||
* @throws \Exception If there is an error taking payment, an \Exception object can be thrown with an error message.
|
||||
*
|
||||
* @param PaymentContext $context Holds context for the payment, including order ID and payment method.
|
||||
* @param PaymentResult $payment_result Result object for the transaction.
|
||||
*/
|
||||
do_action_ref_array( 'woocommerce_rest_checkout_process_payment_with_context', [ $context, &$payment_result ] );
|
||||
|
||||
if ( ! $payment_result instanceof PaymentResult ) {
|
||||
throw new RouteException( 'woocommerce_rest_checkout_invalid_payment_result', __( 'Invalid payment result received from payment method.', 'woocommerce' ), 500 );
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
throw new RouteException( 'woocommerce_rest_checkout_process_payment_error', $e->getMessage(), 400 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chosen payment method ID from the request.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return string
|
||||
*/
|
||||
private function get_request_payment_method_id( \WP_REST_Request $request ) {
|
||||
$payment_method_id = wc_clean( wp_unslash( $request['payment_method'] ?? '' ) );
|
||||
|
||||
if ( empty( $payment_method_id ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_checkout_missing_payment_method',
|
||||
__( 'No payment method provided.', 'woocommerce' ),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return $payment_method_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chosen payment method from the request.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WC_Payment_Gateway
|
||||
*/
|
||||
private function get_request_payment_method( \WP_REST_Request $request ) {
|
||||
$payment_method_id = $this->get_request_payment_method_id( $request );
|
||||
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
|
||||
if ( ! isset( $available_gateways[ $payment_method_id ] ) ) {
|
||||
throw new RouteException(
|
||||
'woocommerce_rest_checkout_payment_method_disabled',
|
||||
__( 'This payment gateway is not available.', 'woocommerce' ),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return $available_gateways[ $payment_method_id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and formats payment request data.
|
||||
*
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return array
|
||||
*/
|
||||
private function get_request_payment_data( \WP_REST_Request $request ) {
|
||||
static $payment_data = [];
|
||||
if ( ! empty( $payment_data ) ) {
|
||||
return $payment_data;
|
||||
}
|
||||
if ( ! empty( $request['payment_data'] ) ) {
|
||||
foreach ( $request['payment_data'] as $data ) {
|
||||
$payment_data[ sanitize_key( $data['key'] ) ] = wc_clean( $data['value'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $payment_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order processing relating to customer account.
|
||||
*
|
||||
* Creates a customer account as needed (based on request & store settings) and updates the order with the new customer ID.
|
||||
* Updates the order with user details (e.g. address).
|
||||
*
|
||||
* @throws RouteException API error object with error details.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
*/
|
||||
private function process_customer( \WP_REST_Request $request ) {
|
||||
try {
|
||||
$create_account = Package::container()->get( CreateAccount::class );
|
||||
$create_account->from_order_request( $request );
|
||||
$this->order->set_customer_id( get_current_user_id() );
|
||||
$this->order->save();
|
||||
} catch ( \Exception $error ) {
|
||||
switch ( $error->getMessage() ) {
|
||||
case 'registration-error-invalid-email':
|
||||
throw new RouteException(
|
||||
'registration-error-invalid-email',
|
||||
__( 'Please provide a valid email address.', 'woocommerce' ),
|
||||
400
|
||||
);
|
||||
case 'registration-error-email-exists':
|
||||
throw new RouteException(
|
||||
'registration-error-email-exists',
|
||||
__( 'An account is already registered with your email address. Please log in before proceeding.', 'woocommerce' ),
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Persist customer address data to account.
|
||||
$this->order_controller->sync_customer_data_with_order( $this->order );
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ProductAttributeTerms class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductAttributeTerms extends AbstractTermsRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/attributes/(?P<attribute_id>[\d]+)/terms';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
'args' => array(
|
||||
'attribute_id' => array(
|
||||
'description' => __( 'Unique identifier for the attribute.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of attribute terms.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$attribute = wc_get_attribute( $request['attribute_id'] );
|
||||
|
||||
if ( ! $attribute || ! taxonomy_exists( $attribute->slug ) ) {
|
||||
throw new RouteException( 'woocommerce_rest_taxonomy_invalid', __( 'Attribute does not exist.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
return $this->get_terms_response( $attribute->slug, $request );
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ProductAttributes class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductAttributes extends AbstractRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/attributes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of attributes.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$ids = wc_get_attribute_taxonomy_ids();
|
||||
$return = [];
|
||||
|
||||
foreach ( $ids as $id ) {
|
||||
$object = wc_get_attribute( $id );
|
||||
$data = $this->prepare_item_for_response( $object, $request );
|
||||
$return[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $return );
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ProductAttributesById class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductAttributesById extends AbstractRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/attributes/(?P<id>[\d]+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param(
|
||||
array(
|
||||
'default' => 'view',
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single item.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$object = wc_get_attribute( (int) $request['id'] );
|
||||
|
||||
if ( ! $object || 0 === $object->id ) {
|
||||
throw new RouteException( 'woocommerce_rest_attribute_invalid_id', __( 'Invalid attribute ID.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
$data = $this->prepare_item_for_response( $object, $request );
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ProductCategories class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductCategories extends AbstractTermsRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/categories';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of terms.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
return $this->get_terms_response( 'product_cat', $request );
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ProductCategoriesById class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductCategoriesById extends AbstractRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/categories/(?P<id>[\d]+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param(
|
||||
array(
|
||||
'default' => 'view',
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single item.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$object = get_term( (int) $request['id'], 'product_cat' );
|
||||
|
||||
if ( ! $object || 0 === $object->id ) {
|
||||
throw new RouteException( 'woocommerce_rest_category_invalid_id', __( 'Invalid category ID.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
$data = $this->prepare_item_for_response( $object, $request );
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\ProductQueryFilters;
|
||||
|
||||
/**
|
||||
* ProductCollectionData route.
|
||||
* Get aggregate data from a collection of products.
|
||||
*
|
||||
* Supports the same parameters as /products, but returns a different response.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductCollectionData extends AbstractRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/collection-data';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of posts and add the post title filter option to \WP_Query.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$data = [
|
||||
'min_price' => null,
|
||||
'max_price' => null,
|
||||
'attribute_counts' => null,
|
||||
'stock_status_counts' => null,
|
||||
'rating_counts' => null,
|
||||
];
|
||||
$filters = new ProductQueryFilters();
|
||||
|
||||
if ( ! empty( $request['calculate_price_range'] ) ) {
|
||||
$filter_request = clone $request;
|
||||
$filter_request->set_param( 'min_price', null );
|
||||
$filter_request->set_param( 'max_price', null );
|
||||
|
||||
$price_results = $filters->get_filtered_price( $filter_request );
|
||||
$data['min_price'] = $price_results->min_price;
|
||||
$data['max_price'] = $price_results->max_price;
|
||||
}
|
||||
|
||||
if ( ! empty( $request['calculate_stock_status_counts'] ) ) {
|
||||
$filter_request = clone $request;
|
||||
$counts = $filters->get_stock_status_counts( $filter_request );
|
||||
|
||||
$data['stock_status_counts'] = [];
|
||||
|
||||
foreach ( $counts as $key => $value ) {
|
||||
$data['stock_status_counts'][] = (object) [
|
||||
'status' => $key,
|
||||
'count' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request['calculate_attribute_counts'] ) ) {
|
||||
$taxonomy__or_queries = [];
|
||||
$taxonomy__and_queries = [];
|
||||
|
||||
foreach ( $request['calculate_attribute_counts'] as $attributes_to_count ) {
|
||||
if ( ! empty( $attributes_to_count['taxonomy'] ) ) {
|
||||
if ( empty( $attributes_to_count['query_type'] ) || 'or' === $attributes_to_count['query_type'] ) {
|
||||
$taxonomy__or_queries[] = $attributes_to_count['taxonomy'];
|
||||
} else {
|
||||
$taxonomy__and_queries[] = $attributes_to_count['taxonomy'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['attribute_counts'] = [];
|
||||
// Or type queries need special handling because the attribute, if set, needs removing from the query first otherwise counts would not be correct.
|
||||
if ( $taxonomy__or_queries ) {
|
||||
foreach ( $taxonomy__or_queries as $taxonomy ) {
|
||||
$filter_request = clone $request;
|
||||
$filter_attributes = $filter_request->get_param( 'attributes' );
|
||||
|
||||
if ( ! empty( $filter_attributes ) ) {
|
||||
$filter_attributes = array_filter(
|
||||
$filter_attributes,
|
||||
function( $query ) use ( $taxonomy ) {
|
||||
return $query['attribute'] !== $taxonomy;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$filter_request->set_param( 'attributes', $filter_attributes );
|
||||
$counts = $filters->get_attribute_counts( $filter_request, [ $taxonomy ] );
|
||||
|
||||
foreach ( $counts as $key => $value ) {
|
||||
$data['attribute_counts'][] = (object) [
|
||||
'term' => $key,
|
||||
'count' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $taxonomy__and_queries ) {
|
||||
$counts = $filters->get_attribute_counts( $request, $taxonomy__and_queries );
|
||||
|
||||
foreach ( $counts as $key => $value ) {
|
||||
$data['attribute_counts'][] = (object) [
|
||||
'term' => $key,
|
||||
'count' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $request['calculate_rating_counts'] ) ) {
|
||||
$filter_request = clone $request;
|
||||
$counts = $filters->get_rating_counts( $filter_request );
|
||||
$data['rating_counts'] = [];
|
||||
|
||||
foreach ( $counts as $key => $value ) {
|
||||
$data['rating_counts'][] = (object) [
|
||||
'rating' => $key,
|
||||
'count' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $data ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections of products.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = ( new Products( $this->schema ) )->get_collection_params();
|
||||
|
||||
$params['calculate_price_range'] = [
|
||||
'description' => __( 'If true, calculates the minimum and maximum product prices for the collection.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
];
|
||||
|
||||
$params['calculate_stock_status_counts'] = [
|
||||
'description' => __( 'If true, calculates stock counts for products in the collection.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
];
|
||||
|
||||
$params['calculate_attribute_counts'] = [
|
||||
'description' => __( 'If requested, calculates attribute term counts for products in the collection.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'taxonomy' => [
|
||||
'description' => __( 'Taxonomy name.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'query_type' => [
|
||||
'description' => __( 'Query type being performed which may affect counts. Valid values include "and" and "or".', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => [ 'and', 'or' ],
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
'default' => [],
|
||||
];
|
||||
|
||||
$params['calculate_rating_counts'] = [
|
||||
'description' => __( 'If true, calculates rating counts for products in the collection.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
];
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use WP_Comment_Query;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\Pagination;
|
||||
|
||||
/**
|
||||
* ProductReviews class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductReviews extends AbstractRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/reviews';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of reviews.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$prepared_args = array(
|
||||
'type' => 'review',
|
||||
'status' => 'approve',
|
||||
'no_found_rows' => false,
|
||||
'offset' => $request['offset'],
|
||||
'order' => $request['order'],
|
||||
'number' => $request['per_page'],
|
||||
'post__in' => $request['product_id'],
|
||||
);
|
||||
|
||||
/**
|
||||
* Map category id to list of product ids.
|
||||
*/
|
||||
if ( ! empty( $request['category_id'] ) ) {
|
||||
$category_ids = $request['category_id'];
|
||||
$child_ids = [];
|
||||
foreach ( $category_ids as $category_id ) {
|
||||
$child_ids = array_merge( $child_ids, get_term_children( $category_id, 'product_cat' ) );
|
||||
}
|
||||
$category_ids = array_unique( array_merge( $category_ids, $child_ids ) );
|
||||
$product_ids = get_objects_in_term( $category_ids, 'product_cat' );
|
||||
$prepared_args['post__in'] = isset( $prepared_args['post__in'] ) ? array_merge( $prepared_args['post__in'], $product_ids ) : $product_ids;
|
||||
}
|
||||
|
||||
if ( 'rating' === $request['orderby'] ) {
|
||||
$prepared_args['meta_query'] = array( // phpcs:ignore
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => 'rating',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => 'rating',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
);
|
||||
}
|
||||
$prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] );
|
||||
|
||||
if ( empty( $request['offset'] ) ) {
|
||||
$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
|
||||
}
|
||||
|
||||
$query = new WP_Comment_Query();
|
||||
$query_result = $query->query( $prepared_args );
|
||||
$response_objects = array();
|
||||
|
||||
foreach ( $query_result as $review ) {
|
||||
$data = $this->prepare_item_for_response( $review, $request );
|
||||
$response_objects[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$total_reviews = (int) $query->found_comments;
|
||||
$max_pages = (int) $query->max_num_pages;
|
||||
|
||||
if ( $total_reviews < 1 ) {
|
||||
// Out-of-bounds, run the query again without LIMIT for total count.
|
||||
unset( $prepared_args['number'], $prepared_args['offset'] );
|
||||
|
||||
$query = new WP_Comment_Query();
|
||||
$prepared_args['count'] = true;
|
||||
|
||||
$total_reviews = $query->query( $prepared_args );
|
||||
$max_pages = $request['per_page'] ? ceil( $total_reviews / $request['per_page'] ) : 1;
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $response_objects );
|
||||
$response = ( new Pagination() )->add_headers( $response, $request, $total_reviews, $max_pages );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends internal property prefix to query parameters to match our response fields.
|
||||
*
|
||||
* @param string $query_param Query parameter.
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize_query_param( $query_param ) {
|
||||
$prefix = 'comment_';
|
||||
|
||||
switch ( $query_param ) {
|
||||
case 'id':
|
||||
$normalized = $prefix . 'ID';
|
||||
break;
|
||||
case 'product':
|
||||
$normalized = $prefix . 'post_ID';
|
||||
break;
|
||||
case 'rating':
|
||||
$normalized = 'meta_value_num';
|
||||
break;
|
||||
default:
|
||||
$normalized = $prefix . $query_param;
|
||||
break;
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections of products.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = array();
|
||||
$params['context'] = $this->get_context_param();
|
||||
$params['context']['default'] = 'view';
|
||||
|
||||
$params['page'] = array(
|
||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'minimum' => 1,
|
||||
);
|
||||
|
||||
$params['per_page'] = array(
|
||||
'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'default' => 10,
|
||||
'minimum' => 0,
|
||||
'maximum' => 100,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['offset'] = array(
|
||||
'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['order'] = array(
|
||||
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'desc',
|
||||
'enum' => array( 'asc', 'desc' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['orderby'] = array(
|
||||
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'date',
|
||||
'enum' => array(
|
||||
'date',
|
||||
'date_gmt',
|
||||
'id',
|
||||
'rating',
|
||||
'product',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['category_id'] = array(
|
||||
'description' => __( 'Limit result set to reviews from specific category IDs.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['product_id'] = array(
|
||||
'description' => __( 'Limit result set to reviews from specific product IDs.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ProductTags class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductTags extends AbstractTermsRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/tags';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of terms.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
return $this->get_terms_response( 'product_tag', $request );
|
||||
}
|
||||
}
|
389
packages/woocommerce-blocks/src/StoreApi/Routes/Products.php
Normal file
389
packages/woocommerce-blocks/src/StoreApi/Routes/Products.php
Normal file
@ -0,0 +1,389 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\Pagination;
|
||||
use Automattic\WooCommerce\Blocks\StoreApi\Utilities\ProductQuery;
|
||||
|
||||
/**
|
||||
* Products class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class Products extends AbstractRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => $this->get_collection_params(),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of posts and add the post title filter option to \WP_Query.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$response = new \WP_REST_Response();
|
||||
$product_query = new ProductQuery();
|
||||
|
||||
// Only get objects during GET requests.
|
||||
if ( \WP_REST_Server::READABLE === $request->get_method() ) {
|
||||
$query_results = $product_query->get_objects( $request );
|
||||
$response_objects = [];
|
||||
|
||||
foreach ( $query_results['objects'] as $object ) {
|
||||
$data = rest_ensure_response( $this->schema->get_item_response( $object ) );
|
||||
$response_objects[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$response->set_data( $response_objects );
|
||||
} else {
|
||||
$query_results = $product_query->get_results( $request );
|
||||
}
|
||||
|
||||
$response = ( new Pagination() )->add_headers( $response, $request, $query_results['total'], $query_results['pages'] );
|
||||
$response->header( 'Last-Modified', $product_query->get_last_modified() );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare links for the request.
|
||||
*
|
||||
* @param \WC_Product $item Product object.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $item, $request ) {
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( $this->get_namespace() . $this->get_path() . '/' . $item->get_id() ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( $this->get_namespace() . $this->get_path() ),
|
||||
),
|
||||
);
|
||||
|
||||
if ( $item->get_parent_id() ) {
|
||||
$links['up'] = array(
|
||||
'href' => rest_url( $this->get_namespace() . $this->get_path() . '/' . $item->get_parent_id() ),
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query params for collections of products.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$params = [];
|
||||
$params['context'] = $this->get_context_param();
|
||||
$params['context']['default'] = 'view';
|
||||
|
||||
$params['page'] = array(
|
||||
'description' => __( 'Current page of the collection.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
'minimum' => 1,
|
||||
);
|
||||
|
||||
$params['per_page'] = array(
|
||||
'description' => __( 'Maximum number of items to be returned in result set. Defaults to no limit if left blank.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'default' => 10,
|
||||
'minimum' => 0,
|
||||
'maximum' => 100,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['search'] = array(
|
||||
'description' => __( 'Limit results to those matching a string.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['after'] = array(
|
||||
'description' => __( 'Limit response to resources created after a given ISO8601 compliant date.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['before'] = array(
|
||||
'description' => __( 'Limit response to resources created before a given ISO8601 compliant date.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['date_column'] = array(
|
||||
'description' => __( 'When limiting response using after/before, which date column to compare against.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'date',
|
||||
'enum' => array(
|
||||
'date',
|
||||
'date_gmt',
|
||||
'modified',
|
||||
'modified_gmt',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['exclude'] = array(
|
||||
'description' => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => [],
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
|
||||
$params['include'] = array(
|
||||
'description' => __( 'Limit result set to specific ids.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => [],
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
|
||||
$params['offset'] = array(
|
||||
'description' => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['order'] = array(
|
||||
'description' => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'desc',
|
||||
'enum' => array( 'asc', 'desc' ),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['orderby'] = array(
|
||||
'description' => __( 'Sort collection by object attribute.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'default' => 'date',
|
||||
'enum' => array(
|
||||
'date',
|
||||
'modified',
|
||||
'id',
|
||||
'include',
|
||||
'title',
|
||||
'slug',
|
||||
'price',
|
||||
'popularity',
|
||||
'rating',
|
||||
'menu_order',
|
||||
'comment_count',
|
||||
),
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['parent'] = array(
|
||||
'description' => __( 'Limit result set to those of particular parent IDs.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => [],
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
|
||||
$params['parent_exclude'] = array(
|
||||
'description' => __( 'Limit result set to all items except those of a particular parent ID.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'default' => [],
|
||||
);
|
||||
|
||||
$params['type'] = array(
|
||||
'description' => __( 'Limit result set to products assigned a specific type.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => array_merge( array_keys( wc_get_product_types() ), [ 'variation' ] ),
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['sku'] = array(
|
||||
'description' => __( 'Limit result set to products with specific SKU(s). Use commas to separate.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['featured'] = array(
|
||||
'description' => __( 'Limit result set to featured products.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wc_string_to_bool',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['category'] = array(
|
||||
'description' => __( 'Limit result set to products assigned a specific category ID.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['category_operator'] = array(
|
||||
'description' => __( 'Operator to compare product category terms.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => [ 'in', 'not in', 'and' ],
|
||||
'default' => 'in',
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['tag'] = array(
|
||||
'description' => __( 'Limit result set to products assigned a specific tag ID.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['tag_operator'] = array(
|
||||
'description' => __( 'Operator to compare product tags.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => [ 'in', 'not in', 'and' ],
|
||||
'default' => 'in',
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['on_sale'] = array(
|
||||
'description' => __( 'Limit result set to products on sale.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'wc_string_to_bool',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['min_price'] = array(
|
||||
'description' => __( 'Limit result set to products based on a minimum price, provided using the smallest unit of the currency.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['max_price'] = array(
|
||||
'description' => __( 'Limit result set to products based on a maximum price, provided using the smallest unit of the currency.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['stock_status'] = array(
|
||||
'description' => __( 'Limit result set to products with specified stock status.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array_keys( wc_get_product_stock_status_options() ),
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
),
|
||||
'default' => [],
|
||||
);
|
||||
|
||||
$params['attributes'] = array(
|
||||
'description' => __( 'Limit result set to products with selected global attributes.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'attribute' => array(
|
||||
'description' => __( 'Attribute taxonomy name.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'wc_sanitize_taxonomy_name',
|
||||
),
|
||||
'term_id' => array(
|
||||
'description' => __( 'List of attribute term IDs.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'List of attribute slug(s). If a term ID is provided, this will be ignored.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
'sanitize_callback' => 'wp_parse_slug_list',
|
||||
),
|
||||
'operator' => array(
|
||||
'description' => __( 'Operator to compare product attribute terms.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => [ 'in', 'not in', 'and' ],
|
||||
),
|
||||
),
|
||||
),
|
||||
'default' => [],
|
||||
);
|
||||
|
||||
$params['attribute_relation'] = array(
|
||||
'description' => __( 'The logical relationship between attributes when filtering across multiple at once.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => [ 'in', 'and' ],
|
||||
'default' => 'and',
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['catalog_visibility'] = array(
|
||||
'description' => __( 'Determines if hidden or visible catalog products are shown.', 'woocommerce' ),
|
||||
'type' => 'string',
|
||||
'enum' => array( 'any', 'visible', 'catalog', 'search', 'hidden' ),
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => 'rest_validate_request_arg',
|
||||
);
|
||||
|
||||
$params['rating'] = array(
|
||||
'description' => __( 'Limit result set to products with a certain average rating.', 'woocommerce' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
'enum' => range( 1, 5 ),
|
||||
),
|
||||
'default' => [],
|
||||
'sanitize_callback' => 'wp_parse_id_list',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ProductsById class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class ProductsById extends AbstractRoute {
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path() {
|
||||
return '/products/(?P<id>[\d]+)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args() {
|
||||
return [
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
[
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [ $this, 'get_response' ],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param(
|
||||
array(
|
||||
'default' => 'view',
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
'schema' => [ $this->schema, 'get_public_item_schema' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single item.
|
||||
*
|
||||
* @throws RouteException On error.
|
||||
* @param \WP_REST_Request $request Request object.
|
||||
* @return \WP_REST_Response
|
||||
*/
|
||||
protected function get_route_response( \WP_REST_Request $request ) {
|
||||
$object = wc_get_product( (int) $request['id'] );
|
||||
|
||||
if ( ! $object || 0 === $object->get_id() ) {
|
||||
throw new RouteException( 'woocommerce_rest_product_invalid_id', __( 'Invalid product ID.', 'woocommerce' ), 404 );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $this->schema->get_item_response( $object ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* ReserveStockRouteExceptionException class.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
class RouteException extends \Exception {
|
||||
/**
|
||||
* Sanitized error code.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $error_code;
|
||||
|
||||
/**
|
||||
* Additional error data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $additional_data = [];
|
||||
|
||||
/**
|
||||
* Setup exception.
|
||||
*
|
||||
* @param string $error_code Machine-readable error code, e.g `woocommerce_invalid_product_id`.
|
||||
* @param string $message User-friendly translated error message, e.g. 'Product ID is invalid'.
|
||||
* @param int $http_status_code Proper HTTP status code to respond with, e.g. 400.
|
||||
* @param array $additional_data Extra data (key value pairs) to expose in the error response.
|
||||
*/
|
||||
public function __construct( $error_code, $message, $http_status_code = 400, $additional_data = [] ) {
|
||||
$this->error_code = $error_code;
|
||||
$this->additional_data = array_filter( (array) $additional_data );
|
||||
parent::__construct( $message, $http_status_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getErrorCode() {
|
||||
return $this->error_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns additional error data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAdditionalData() {
|
||||
return $this->additional_data;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\StoreApi\Routes;
|
||||
|
||||
/**
|
||||
* RouteInterface.
|
||||
*
|
||||
* @internal This API is used internally by Blocks--it is still in flux and may be subject to revisions.
|
||||
*/
|
||||
interface RouteInterface {
|
||||
/**
|
||||
* Get the namespace for this route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_namespace();
|
||||
|
||||
/**
|
||||
* Get the path of this REST route.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_path();
|
||||
|
||||
/**
|
||||
* Get arguments for this REST route.
|
||||
*
|
||||
* @return array An array of endpoints.
|
||||
*/
|
||||
public function get_args();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user