diff --git a/.eslintrc.js b/.eslintrc.js
index f857fd24a5..ce3ee0fdc2 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -26,6 +26,8 @@ module.exports = {
'import/core-modules': [
'webpack',
'stylelint',
+ '@woocommerce/product-editor',
+ '@woocommerce/block-templates',
'@wordpress/stylelint-config',
'@pmmmwh/react-refresh-webpack-plugin',
'react-transition-group',
diff --git a/.externalized.json b/.externalized.json
index 362d44cf08..45d8d4461e 100644
--- a/.externalized.json
+++ b/.externalized.json
@@ -1 +1 @@
-["@woocommerce/components","@woocommerce/currency","@woocommerce/customer-effort-score","@woocommerce/data","@woocommerce/date","@woocommerce/navigation","@woocommerce/number","@woocommerce/settings","@woocommerce/tracks","@wordpress/api-fetch","@wordpress/components","@wordpress/compose","@wordpress/data","@wordpress/data-controls","@wordpress/date","@wordpress/dom","@wordpress/element","@wordpress/hooks","@wordpress/html-entities","@wordpress/i18n","@wordpress/primitives","@wordpress/url","jquery","lodash","react","react-dom"]
\ No newline at end of file
+["@woocommerce/block-templates","@woocommerce/components","@woocommerce/currency","@woocommerce/customer-effort-score","@woocommerce/data","@woocommerce/date","@woocommerce/navigation","@woocommerce/number","@woocommerce/product-editor","@woocommerce/settings","@woocommerce/tracks","@wordpress/api-fetch","@wordpress/components","@wordpress/compose","@wordpress/data","@wordpress/data-controls","@wordpress/date","@wordpress/dom","@wordpress/element","@wordpress/hooks","@wordpress/html-entities","@wordpress/i18n","@wordpress/primitives","@wordpress/url","jquery","lodash","react","react-dom"]
\ No newline at end of file
diff --git a/changelog.txt b/changelog.txt
index 5cda1f9f0d..bf4fa91984 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,10 @@
*** WooCommerce Google Listings and Ads Changelog ***
+= 2.6.0 - 2024-02-27 =
+* Add - Support the new product editor (Product Block Editor).
+* Dev - Fix the compatibility issue in starting E2E test environment due to the default charset change in MariaDB v11.3.1.
+* Fix - 401 handling for connected Ads accounts.
+
= 2.5.18 - 2024-02-20 =
* Fix - Prevent product queries by IDs if no arguments are supplied.
diff --git a/google-listings-and-ads.php b/google-listings-and-ads.php
index 7f3b03893a..51f10dcba1 100644
--- a/google-listings-and-ads.php
+++ b/google-listings-and-ads.php
@@ -3,7 +3,7 @@
* Plugin Name: Google Listings and Ads
* Plugin URL: https://wordpress.org/plugins/google-listings-and-ads/
* Description: Native integration with Google that allows merchants to easily display their products across Google’s network.
- * Version: 2.5.18
+ * Version: 2.6.0
* Author: WooCommerce
* Author URI: https://woo.com/
* Text Domain: google-listings-and-ads
@@ -30,7 +30,7 @@
defined( 'ABSPATH' ) || exit;
-define( 'WC_GLA_VERSION', '2.5.18' ); // WRCS: DEFINED_VERSION.
+define( 'WC_GLA_VERSION', '2.6.0' ); // WRCS: DEFINED_VERSION.
define( 'WC_GLA_MIN_PHP_VER', '7.4' );
define( 'WC_GLA_MIN_WC_VER', '6.9' );
@@ -60,6 +60,7 @@ function () {
if ( class_exists( FeaturesUtil::class ) ) {
FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__ );
FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', __FILE__ );
+ FeaturesUtil::declare_compatibility( 'product_block_editor', __FILE__ );
}
}
);
diff --git a/js/src/blocks/README.md b/js/src/blocks/README.md
new file mode 100644
index 0000000000..2331849197
--- /dev/null
+++ b/js/src/blocks/README.md
@@ -0,0 +1,96 @@
+# Custom product blocks
+
+To make this extension compatible with the Product Block Editor, it needs to implement a few custom blocks to map all classical fields to product editor blocks.
+
+## Development
+
+Implementing custom blocks by the default development way, each block will be built as a separate script and will need to be loaded into the browser separately at runtime.
+
+Since this extension requires a few custom blocks, and considering these custom blocks will not be used individually, and in order to reduce the number of scripts loaded, here are some adjustments that differ from the default way.
+
+### Directory structure of source code
+
+```
+/js/src/blocks/ # The root directory of custom blocks
+├── product-select-field/ # A custom block
+│ ├── block.json # The metadata file of this block and also the canonical way to register this block with both PHP and JavaScript side
+│ └── edit.js # The component to work with the `edit` function when registering this block, and it's the interface for how this block is going to be rendered within the Product Block Editor
+├── another-field/ # Another custom block
+│ ├── block.json
+│ └── edit.js
+├── components/ # The shared components within custom blocks (if any)
+│ ├── index.js # The main file to export components
+│ └── label.js # A shared component
+└── index.js # The main file to import and register all custom blocks via JavaScript
+```
+
+### Directory structure of built code
+
+```
+/js/build/ # The root directory of built code
+├── product-select-field/ # A custom block
+│ └── block.json # The copied metadata file to be used to register this block via PHP
+├── another-field/ # Another custom block
+│ └── block.json
+├── blocks.js # The built script to register all custom blocks via JavaScript and to be registered and enqueued via PHP
+└── blocks.asset.php # The dependencies of blocks.js to be used when registering blocks.js via PHP
+```
+
+### Compatible with block templates and product data access
+
+The Product Block Editor requires template attributes and `usesContext` to support its specific features, e.g. conditionally hide/disable block or contextually access product data. To make the custom blocks fully compatible with these features, the uses of the following APIs are required:
+
+- JavaScript side:
+ - Use `useWooBlockProps` hook imported from `@woocommerce/block-templates` to get React props for the custom block's `
` wrapper.
+ - Use `useProductEntityProp` hook imported from `@woocommerce/product-editor` to get and set product data or metadata.
+ - Forwarding the `context.postType` to `useProductEntityProp` hook to contextually access simple or variation product.
+- PHP side:
+ - Register all custom blocks via `\Automattic\WooCommerce\Admin\Features\ProductBlockEditor\BlockRegistry::get_instance()->register_block_type_from_metadata()`.
+
+#### Derived value for initialization
+
+The "derived value" refers to the computation of a value based on another state or props in a component. At the time of starting rendering a block, the product data has already been loaded to a data store of `@wordpress/data` in Woo's Product Block Editor, so the value returned from `useProductEntityProp` can be considered as an already fetched data for directly initializing derived values, because they all eventually use the same selector `getEntityRecord` from `@wordpress/core-data` to get product data.
+
+References:
+
+- At [ProductPage](https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce-admin/client/products/product-page.tsx#L77-L79) and [ProductVariationPage](https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce-admin/client/products/product-variation-page.tsx#L83-L85) layers, they won't render the actual blocks before the product data is fetched
+- The above product data is obtained from [useProductEntityRecord](https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce-admin/client/products/hooks/use-product-entity-record.ts#L19-L23) or [useProductVariationEntityRecord](https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce-admin/client/products/hooks/use-product-variation-entity-record.ts#L16-L22) hook, and both hooks use the `getEntityRecord` selector.
+- This extension obtains product data from [useProductEntityProp](https://github.com/woocommerce/woocommerce/blob/8.6.0/packages/js/product-editor/src/hooks/use-product-entity-prop.ts#L24-L34), which uses the `useEntityProp` hook internally.
+- The [useEntityProp](https://github.com/WordPress/gutenberg/blob/wp/6.0/packages/core-data/src/entity-provider.js#L102-L133) hook also uses the `getEntityRecord` selector.
+
+### Infrastructure adjustments
+
+#### block.json and edit.js
+
+By default, it should specify the `editorScript` with the relative path of edit.js, so that the `wp-scripts build` and `wp-scripts start` provided by `@wordpress/scripts` will also parse and build each block separately, and the edit.js needs to register itself along with its block.json.
+
+With the adjusted setup:
+
+- Every block.json should **not** specify the `editorScript` to avoid building blocks separately.
+- Every edit.js should **not** do the registration.
+- All block.json and edit.js files should be imported and registered via JavaScript by the index.js file at the root directory of custom blocks.
+
+#### webpack.config.js
+
+By default, when building, the [webpack config](https://github.com/WordPress/gutenberg/tree/%40wordpress/scripts%4024.6.0/packages/scripts#default-webpack-config) imported from `@wordpress/scripts` will find all block.json files within the given source code directory [specified by `--webpack-src-dir`](https://github.com/WordPress/gutenberg/tree/%40wordpress/scripts%4024.6.0/packages/scripts#automatic-blockjson-detection-and-the-source-code-directory), and then parse and build each block as per block.json.
+
+With the adjusted setup:
+
+- Build the index.js file in the root directory of custom blocks as the blocks.js at the root directory of built code.
+- The blocks.asset.php file will be generated along with the build of the blocks.js. The `dependencies` value in the file is related to the mechanism of DEWP. See [Working with DEWP.md](../../../Working%20with%20DEWP.md) for more details.
+
+#### Registration on the PHP side
+
+By default, when each block is registered via PHP in the [ProductBlocksService](../../../src/Admin/ProductBlocksService.php) class, the `register_block_type` function will also register and enqueue the related scripts and styles such as the built edit.js.
+
+The same part is the separate registration for each block, therefore all blocks should be listed in the `CUSTOM_BLOCKS` array of the `ProductBlocksService` class.
+
+With the adjusted setup:
+
+- The scripts and styles of blocks are not specified in block.json files, so they won't be registered or enqueued via the `register_block_type` function.
+- Instead, the blocks.js script is registered and enqueued by `ProductBlocksService`.
+
+## Related documentation
+
+- [Woo - Product Editor Development](https://github.com/woocommerce/woocommerce/tree/trunk/docs/product-editor-development)
+- [WordPress - Block Editor Handbook](https://developer.wordpress.org/block-editor/)
diff --git a/js/src/blocks/index.js b/js/src/blocks/index.js
new file mode 100644
index 0000000000..dda618443d
--- /dev/null
+++ b/js/src/blocks/index.js
@@ -0,0 +1,35 @@
+/**
+ * External dependencies
+ */
+import { registerProductEditorBlockType } from '@woocommerce/product-editor';
+
+/**
+ * Internal dependencies
+ */
+import OnboardingPromptEdit from './product-onboarding-prompt/edit';
+import onboardingPromptMetadata from './product-onboarding-prompt/block.json';
+import ChannelVisibilityEdit from './product-channel-visibility/edit';
+import channelVisibilityMetadata from './product-channel-visibility/block.json';
+import DateTimeFieldEdit from './product-date-time-field/edit';
+import dateTimeFieldMetadata from './product-date-time-field/block.json';
+import SelectFieldEdit from './product-select-field/edit';
+import selectFieldMetadata from './product-select-field/block.json';
+import SelectWithTextFieldEdit from './product-select-with-text-field/edit';
+import selectWithTextFieldMetadata from './product-select-with-text-field/block.json';
+
+function registerProductEditorBlock( { name, ...metadata }, Edit ) {
+ registerProductEditorBlockType( {
+ name,
+ metadata,
+ settings: { edit: Edit },
+ } );
+}
+
+registerProductEditorBlock( onboardingPromptMetadata, OnboardingPromptEdit );
+registerProductEditorBlock( channelVisibilityMetadata, ChannelVisibilityEdit );
+registerProductEditorBlock( dateTimeFieldMetadata, DateTimeFieldEdit );
+registerProductEditorBlock( selectFieldMetadata, SelectFieldEdit );
+registerProductEditorBlock(
+ selectWithTextFieldMetadata,
+ SelectWithTextFieldEdit
+);
diff --git a/js/src/blocks/product-channel-visibility/block.json b/js/src/blocks/product-channel-visibility/block.json
new file mode 100644
index 0000000000..637acf8245
--- /dev/null
+++ b/js/src/blocks/product-channel-visibility/block.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "google-listings-and-ads/product-channel-visibility",
+ "title": "Product channel visibility",
+ "textdomain": "google-listings-and-ads",
+ "attributes": {
+ "property": {
+ "type": "string",
+ "__experimentalRole": "content"
+ },
+ "options": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "default": []
+ },
+ "valueOfSync": {
+ "type": "string"
+ },
+ "valueOfDontSync": {
+ "type": "string"
+ },
+ "statusOfSynced": {
+ "type": "string"
+ },
+ "statusOfHasErrors": {
+ "type": "string"
+ }
+ },
+ "supports": {
+ "html": false,
+ "inserter": false,
+ "lock": false
+ }
+}
diff --git a/js/src/blocks/product-channel-visibility/edit.js b/js/src/blocks/product-channel-visibility/edit.js
new file mode 100644
index 0000000000..1e02ae30cb
--- /dev/null
+++ b/js/src/blocks/product-channel-visibility/edit.js
@@ -0,0 +1,122 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useWooBlockProps } from '@woocommerce/block-templates';
+import { SelectControl, Notice } from '@wordpress/components';
+import { __experimentalUseProductEntityProp as useProductEntityProp } from '@woocommerce/product-editor';
+
+/**
+ * Internal dependencies
+ */
+import styles from './editor.module.scss';
+
+/**
+ * @typedef {import('../types.js').ProductEditorBlockContext} ProductEditorBlockContext
+ */
+
+/**
+ * @typedef {Object} ProductChannelVisibilityAttributes
+ * @property {string} property Property or metadata name in which the value is stored in the product data.
+ * @property {import('@wordpress/components').SelectControl.Option} [options=[]] The options to be shown in the select field.
+ * @property {string} valueOfSync The value of the "Sync and show" option to be used to determine the UI presentation.
+ * @property {string} valueOfDontSync The value of the "Don't Sync and show" option to be used to determine the UI presentation.
+ * @property {string} statusOfSynced The value of the "Synced" status to be used to determine the UI presentation.
+ * @property {string} statusOfHasErrors The value of the "HasErrors" status to be used to determine the UI presentation.
+ */
+
+/**
+ * Specific custom block for editing the channel visibility of the given product data.
+ *
+ * @param {Object} props React props.
+ * @param {ProductChannelVisibilityAttributes} props.attributes
+ * @param {ProductEditorBlockContext} props.context
+ */
+export default function Edit( { attributes, context } ) {
+ const {
+ valueOfSync: VALUE_SYNC,
+ valueOfDontSync: VALUE_DONT_SYNC,
+ statusOfSynced: STATUS_SYNCED,
+ statusOfHasErrors: STATUS_HAS_ERRORS,
+ } = attributes;
+
+ const blockProps = useWooBlockProps( attributes );
+ const [ value, setValue ] = useProductEntityProp( attributes.property, {
+ postType: context.postType,
+ } );
+
+ const setVisibility = ( nextVisibility ) => {
+ setValue( { ...value, channel_visibility: nextVisibility } );
+ };
+
+ const { is_visible: isVisible, sync_status: syncStatus, issues } = value;
+
+ // Force to select the "Don't Sync and show" option if the product is invisible.
+ const visibility = isVisible ? value.channel_visibility : VALUE_DONT_SYNC;
+
+ let syncStatusText = null;
+
+ if ( syncStatus === STATUS_HAS_ERRORS ) {
+ syncStatusText = __( 'Issues detected', 'google-listings-and-ads' );
+ } else if ( syncStatus ) {
+ syncStatusText = syncStatus.replace( '-', ' ' );
+ }
+
+ const help = isVisible
+ ? ''
+ : __(
+ 'This product cannot be shown on any channel because it is hidden from your store catalog. To enable this option, please change this product to be shown in the product catalog, and save the changes.',
+ 'google-listings-and-ads'
+ );
+
+ const shouldDisplayNotice =
+ isVisible &&
+ syncStatusText !== null &&
+ visibility === VALUE_SYNC &&
+ syncStatus !== STATUS_SYNCED;
+
+ const hasErrors = issues.length > 0;
+
+ return (
+
+
+ { shouldDisplayNotice && (
+
+
+
+ { __(
+ 'Google sync status',
+ 'google-listings-and-ads'
+ ) }
+
+
+ { syncStatusText }
+
+
+ { hasErrors && (
+
+
+ { __( 'Issues', 'google-listings-and-ads' ) }
+
+
+ { issues.map( ( issue, idx ) => (
+ { issue }
+ ) ) }
+
+
+ ) }
+
+ ) }
+
+ );
+}
diff --git a/js/src/blocks/product-channel-visibility/editor.module.scss b/js/src/blocks/product-channel-visibility/editor.module.scss
new file mode 100644
index 0000000000..0fc4b2a22a
--- /dev/null
+++ b/js/src/blocks/product-channel-visibility/editor.module.scss
@@ -0,0 +1,13 @@
+.notice {
+ margin: 1em 0 0;
+ padding-top: 0;
+ padding-bottom: 0;
+
+ h2 {
+ font-size: 1em;
+ }
+}
+
+.uppercaseFirstLetter::first-letter {
+ text-transform: uppercase;
+}
diff --git a/js/src/blocks/product-date-time-field/block.json b/js/src/blocks/product-date-time-field/block.json
new file mode 100644
index 0000000000..9eb1527728
--- /dev/null
+++ b/js/src/blocks/product-date-time-field/block.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "google-listings-and-ads/product-date-time-field",
+ "title": "Product date and time fields",
+ "textdomain": "google-listings-and-ads",
+ "attributes": {
+ "label": {
+ "type": "string"
+ },
+ "tooltip": {
+ "type": "string"
+ },
+ "property": {
+ "type": "string",
+ "__experimentalRole": "content"
+ }
+ },
+ "supports": {
+ "html": false,
+ "inserter": false,
+ "lock": false
+ }
+}
diff --git a/js/src/blocks/product-date-time-field/edit.js b/js/src/blocks/product-date-time-field/edit.js
new file mode 100644
index 0000000000..de0b4595c4
--- /dev/null
+++ b/js/src/blocks/product-date-time-field/edit.js
@@ -0,0 +1,126 @@
+/**
+ * External dependencies
+ */
+import { date as formatDate, getDate } from '@wordpress/date';
+import { useWooBlockProps } from '@woocommerce/block-templates';
+import { useState, useRef } from '@wordpress/element';
+import {
+ __experimentalUseProductEntityProp as useProductEntityProp,
+ __experimentalTextControl as TextControl,
+ useValidation,
+} from '@woocommerce/product-editor';
+import { Flex, FlexBlock } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import styles from './editor.module.scss';
+
+/**
+ * @typedef {import('../types.js').ProductEditorBlockContext} ProductEditorBlockContext
+ * @typedef {import('../types.js').ProductBasicAttributes} ProductBasicAttributes
+ */
+
+async function resolveValidationMessage( inputRef ) {
+ const input = inputRef.current;
+
+ if ( ! input.validity.valid ) {
+ return input.validationMessage;
+ }
+}
+
+/**
+ * Custom block for editing a given product data with date and time fields.
+ *
+ * @param {Object} props React props.
+ * @param {ProductBasicAttributes} props.attributes
+ * @param {ProductEditorBlockContext} props.context
+ */
+export default function Edit( { attributes, context } ) {
+ const { property } = attributes;
+ const blockProps = useWooBlockProps( attributes );
+ const [ value, setValue ] = useProductEntityProp( property, {
+ postType: context.postType,
+ fallbackValue: '',
+ } );
+ const dateInputRef = useRef( null );
+ const timeInputRef = useRef( null );
+
+ // Refer to the "Derived value for initialization" in README for why these `useState`
+ // can initialize directly.
+ const [ date, setDate ] = useState( () =>
+ value ? formatDate( 'Y-m-d', value ) : ''
+ );
+ const [ time, setTime ] = useState( () =>
+ value ? formatDate( 'H:i', value ) : ''
+ );
+
+ const setNextValue = ( nextDate, nextTime ) => {
+ let nextValue = '';
+
+ // It allows `nextTime` to be an empty string and fall back to '00:00:00'.
+ if ( nextDate ) {
+ // The date and time values are strings presented in the site timezone.
+ // Normalize them to date string in UTC timezone in ISO 8601 format.
+ const isoDateString = `${ nextDate }T${ nextTime || '00:00:00' }`;
+ const dateInSiteTimezone = getDate( isoDateString );
+ nextValue = formatDate( 'c', dateInSiteTimezone, 'UTC' );
+ }
+
+ setDate( nextDate );
+ setTime( nextTime );
+
+ if ( value !== nextValue ) {
+ setValue( nextValue );
+ }
+ };
+
+ const dateValidation = useValidation( `${ property }-date`, () =>
+ resolveValidationMessage( dateInputRef )
+ );
+
+ const timeValidation = useValidation( `${ property }-time`, () =>
+ resolveValidationMessage( timeInputRef )
+ );
+
+ return (
+
+
+
+
+ setNextValue( nextDate, time )
+ }
+ onBlur={ dateValidation.validate }
+ />
+
+
+ and as the same in the sibling . Also, it uses
+ // CSS to keep its space but is invisible.
+ className={ styles.invisibleLabelAndTooltip }
+ label=" "
+ tooltip=" "
+ ref={ timeInputRef }
+ type="time"
+ pattern="[0-9]{2}:[0-9]{2}"
+ value={ time }
+ error={ timeValidation.error }
+ onChange={ ( nextTime ) =>
+ setNextValue( date, nextTime )
+ }
+ onBlur={ timeValidation.validate }
+ />
+
+
+
+ );
+}
diff --git a/js/src/blocks/product-date-time-field/editor.module.scss b/js/src/blocks/product-date-time-field/editor.module.scss
new file mode 100644
index 0000000000..4d9754403e
--- /dev/null
+++ b/js/src/blocks/product-date-time-field/editor.module.scss
@@ -0,0 +1,5 @@
+.invisibleLabelAndTooltip {
+ label {
+ visibility: hidden;
+ }
+}
diff --git a/js/src/blocks/product-onboarding-prompt/block.json b/js/src/blocks/product-onboarding-prompt/block.json
new file mode 100644
index 0000000000..2da1c0721e
--- /dev/null
+++ b/js/src/blocks/product-onboarding-prompt/block.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "google-listings-and-ads/product-onboarding-prompt",
+ "title": "Product onboarding prompt",
+ "textdomain": "google-listings-and-ads",
+ "attributes": {
+ "startUrl": {
+ "type": "string",
+ "__experimentalRole": "content"
+ }
+ },
+ "supports": {
+ "html": false,
+ "inserter": false,
+ "lock": false
+ }
+}
diff --git a/js/src/blocks/product-onboarding-prompt/edit.js b/js/src/blocks/product-onboarding-prompt/edit.js
new file mode 100644
index 0000000000..15fd75f704
--- /dev/null
+++ b/js/src/blocks/product-onboarding-prompt/edit.js
@@ -0,0 +1,42 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useWooBlockProps } from '@woocommerce/block-templates';
+import { Button } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import styles from './editor.module.scss';
+
+/**
+ * @typedef {Object} ProductOnboardingPromptAttributes
+ * @property {string} startUrl The plugin start URL.
+ */
+
+/**
+ * Custom block for prompting the user to complete the onboarding.
+ *
+ * @param {Object} props React props.
+ * @param {ProductOnboardingPromptAttributes} props.attributes
+ */
+export default function Edit( { attributes } ) {
+ const blockProps = useWooBlockProps( attributes );
+
+ return (
+
+
+
+ { __(
+ 'Complete setup to get your products listed on Google for free.',
+ 'google-listings-and-ads'
+ ) }
+
+
+ { __( 'Get Started', 'google-listings-and-ads' ) }
+
+
+
+ );
+}
diff --git a/js/src/blocks/product-onboarding-prompt/editor.module.scss b/js/src/blocks/product-onboarding-prompt/editor.module.scss
new file mode 100644
index 0000000000..d09699a337
--- /dev/null
+++ b/js/src/blocks/product-onboarding-prompt/editor.module.scss
@@ -0,0 +1,8 @@
+.wrapper {
+ margin-top: $grid-unit-80;
+ text-align: center;
+}
+
+.content {
+ margin-bottom: $grid-unit-30;
+}
diff --git a/js/src/blocks/product-select-field/block.json b/js/src/blocks/product-select-field/block.json
new file mode 100644
index 0000000000..0eef098c8a
--- /dev/null
+++ b/js/src/blocks/product-select-field/block.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "google-listings-and-ads/product-select-field",
+ "title": "Product select field",
+ "textdomain": "google-listings-and-ads",
+ "attributes": {
+ "label": {
+ "type": "string"
+ },
+ "tooltip": {
+ "type": "string"
+ },
+ "property": {
+ "type": "string",
+ "__experimentalRole": "content"
+ },
+ "options": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "default": []
+ }
+ },
+ "supports": {
+ "html": false,
+ "inserter": false,
+ "lock": false
+ }
+}
diff --git a/js/src/blocks/product-select-field/edit.js b/js/src/blocks/product-select-field/edit.js
new file mode 100644
index 0000000000..1906cdb609
--- /dev/null
+++ b/js/src/blocks/product-select-field/edit.js
@@ -0,0 +1,51 @@
+/**
+ * External dependencies
+ */
+import { useWooBlockProps } from '@woocommerce/block-templates';
+import { SelectControl } from '@wordpress/components';
+import {
+ __experimentalUseProductEntityProp as useProductEntityProp,
+ __experimentalLabel as Label,
+} from '@woocommerce/product-editor';
+
+/**
+ * @typedef {import('../types.js').ProductEditorBlockContext} ProductEditorBlockContext
+ * @typedef {import('../types.js').ProductBasicAttributes} ProductBasicAttributes
+ */
+
+/**
+ * @typedef {Object} SpecificAttributes
+ * @property {import('@wordpress/components').SelectControl.Option} [options=[]] The options to be shown in the select field.
+ *
+ * @typedef {ProductBasicAttributes & SpecificAttributes} ProductSelectFieldAttributes
+ */
+
+/**
+ * Custom block for editing a given product data with a select field.
+ *
+ * @param {Object} props React props.
+ * @param {ProductSelectFieldAttributes} props.attributes
+ * @param {ProductEditorBlockContext} props.context
+ */
+export default function Edit( { attributes, context } ) {
+ const blockProps = useWooBlockProps( attributes );
+ const [ value, setValue ] = useProductEntityProp( attributes.property, {
+ postType: context.postType,
+ } );
+
+ return (
+
+
+ }
+ options={ attributes.options }
+ value={ value }
+ onChange={ setValue }
+ />
+
+ );
+}
diff --git a/js/src/blocks/product-select-with-text-field/block.json b/js/src/blocks/product-select-with-text-field/block.json
new file mode 100644
index 0000000000..a9942b118c
--- /dev/null
+++ b/js/src/blocks/product-select-with-text-field/block.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "google-listings-and-ads/product-select-with-text-field",
+ "title": "Product select with text field",
+ "textdomain": "google-listings-and-ads",
+ "attributes": {
+ "label": {
+ "type": "string"
+ },
+ "tooltip": {
+ "type": "string"
+ },
+ "property": {
+ "type": "string",
+ "__experimentalRole": "content"
+ },
+ "options": {
+ "type": "array",
+ "items": {
+ "type": "object"
+ },
+ "default": []
+ },
+ "customInputValue": {
+ "type": "string"
+ }
+ },
+ "supports": {
+ "html": false,
+ "inserter": false,
+ "lock": false
+ }
+}
diff --git a/js/src/blocks/product-select-with-text-field/edit.js b/js/src/blocks/product-select-with-text-field/edit.js
new file mode 100644
index 0000000000..4440fb76b5
--- /dev/null
+++ b/js/src/blocks/product-select-with-text-field/edit.js
@@ -0,0 +1,99 @@
+/**
+ * External dependencies
+ */
+import { useWooBlockProps } from '@woocommerce/block-templates';
+import { SelectControl } from '@wordpress/components';
+import { useState } from '@wordpress/element';
+import {
+ __experimentalUseProductEntityProp as useProductEntityProp,
+ __experimentalTextControl as TextControl,
+ __experimentalLabel as Label,
+} from '@woocommerce/product-editor';
+
+/**
+ * @typedef {import('../types.js').ProductEditorBlockContext} ProductEditorBlockContext
+ * @typedef {import('../types.js').ProductBasicAttributes} ProductBasicAttributes
+ */
+
+/**
+ * @typedef {Object} SpecificAttributes
+ * @property {string} customInputValue The value to be used in the selection option that shows the text field.
+ * @property {import('@wordpress/components').SelectControl.Option} [options=[]] The options to be shown in the select field.
+ *
+ * @typedef {ProductBasicAttributes & SpecificAttributes} ProductSelectWithTextFieldAttributes
+ */
+
+const FALLBACK_VALUE = '';
+
+/**
+ * Custom block for editing a given product data with a select field or a text field.
+ * The text field is used to enter a custom value and is shown when selecting the option
+ * that has the same value as the given `customInputValue`.
+ *
+ * @param {Object} props React props.
+ * @param {ProductSelectWithTextFieldAttributes} props.attributes
+ * @param {ProductEditorBlockContext} props.context
+ */
+export default function Edit( { attributes, context } ) {
+ const { options, customInputValue } = attributes;
+
+ const blockProps = useWooBlockProps( attributes );
+ const [ value, setValue ] = useProductEntityProp( attributes.property, {
+ postType: context.postType,
+ fallbackValue: FALLBACK_VALUE,
+ } );
+
+ // Refer to the "Derived value for initialization" in README for why this `useState`
+ // can initialize directly.
+ const [ optionValue, setOptionValue ] = useState( () => {
+ // Empty string is a valid option value.
+ const initValue = value ?? FALLBACK_VALUE;
+ const selectedOption = options.find(
+ ( option ) => option.value === initValue
+ );
+
+ return selectedOption?.value ?? customInputValue;
+ } );
+
+ const isSelectedCustomInput = optionValue === customInputValue;
+
+ const [ text, setText ] = useState( isSelectedCustomInput ? value : '' );
+
+ const handleSelectionChange = ( nextOptionValue ) => {
+ setOptionValue( nextOptionValue );
+
+ if ( nextOptionValue === customInputValue ) {
+ setValue( text );
+ } else {
+ setValue( nextOptionValue );
+ }
+ };
+
+ const handleTextChange = ( nextText ) => {
+ setText( nextText );
+ setValue( nextText );
+ };
+
+ return (
+
+
+ }
+ options={ options }
+ value={ optionValue }
+ onChange={ handleSelectionChange }
+ />
+ { isSelectedCustomInput && (
+
+ ) }
+
+ );
+}
diff --git a/js/src/blocks/types.js b/js/src/blocks/types.js
new file mode 100644
index 0000000000..e317c4d81c
--- /dev/null
+++ b/js/src/blocks/types.js
@@ -0,0 +1,14 @@
+/**
+ * @typedef {Object} ProductEditorBlockContext
+ * @property {string} postType The current post type ('product' or 'product_variation') got from the context provider.
+ */
+
+/**
+ * @typedef {Object} ProductBasicAttributes
+ * @property {string} property Property or metadata name in which the value is stored in the product data.
+ * @property {string} [label] Label that appears on top of the field.
+ * @property {string} [tooltip] Tooltip next to the label with additional information.
+ */
+
+// This export is required for JSDoc in other files to import the type definitions from this file.
+export default {};
diff --git a/package-lock.json b/package-lock.json
index bec75fb297..db4f4fce00 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "google-listings-and-ads",
- "version": "2.5.18",
+ "version": "2.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "google-listings-and-ads",
- "version": "2.5.17",
+ "version": "2.5.18",
"license": "GPL-3.0-or-later",
"dependencies": {
"@woocommerce/components": "^10.3.0",
@@ -48,9 +48,9 @@
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
- "@woocommerce/dependency-extraction-webpack-plugin": "^2.0.0",
+ "@woocommerce/dependency-extraction-webpack-plugin": "^2.3.0",
"@woocommerce/eslint-plugin": "^1.2.0",
- "@wordpress/env": "^8.4.0",
+ "@wordpress/env": "^9.4.0",
"@wordpress/jest-preset-default": "^11.9.0",
"@wordpress/prettier-config": "2.18.1",
"@wordpress/scripts": "^24.6.0",
@@ -6533,12 +6533,16 @@
}
},
"node_modules/@woocommerce/dependency-extraction-webpack-plugin": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.2.0.tgz",
- "integrity": "sha512-0wDY3EIUwWrPm0KrWvt1cf2SZDSX7CzBXvv4TyCqWOPuVPvC/ajyY8kD1HTFI80q6/RHoxWf3BYCmhuBzPbe9A==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.3.0.tgz",
+ "integrity": "sha512-dfa5bBZFRKcjFpqTEXyuUKC16n85RlKQ4EamdAAqUVQedv4DMRCj2jWUcId7pN2mOFGgAp+heGgb9i6fwiZk2Q==",
"dev": true,
"dependencies": {
"@wordpress/dependency-extraction-webpack-plugin": "^3.3.0"
+ },
+ "engines": {
+ "node": "^16.14.1",
+ "pnpm": "^8.6.7"
}
},
"node_modules/@woocommerce/dependency-extraction-webpack-plugin/node_modules/@wordpress/dependency-extraction-webpack-plugin": {
@@ -7226,14 +7230,14 @@
}
},
"node_modules/@wordpress/env": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-8.4.0.tgz",
- "integrity": "sha512-3DnTW/WanvQpWqagCIMQYtSBYj9wXLQQqGgmKl8gVJ4MfxV3K5A9zE25Rv6iogdr7ydLcPSbmNJx6mYgQRaSog==",
+ "version": "9.4.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.4.0.tgz",
+ "integrity": "sha512-flF+WLAuf8j97OjFkJU7/l9bzvI7GEUm1MXYdo16kSqxBvOTwVkk2yf5s9nxREJ3gPYahgNYf8cA7uT9Rj2eDQ==",
"dev": true,
"dependencies": {
"chalk": "^4.0.0",
"copy-dir": "^1.3.0",
- "docker-compose": "^0.22.2",
+ "docker-compose": "^0.24.3",
"extract-zip": "^1.6.7",
"got": "^11.8.5",
"inquirer": "^7.1.0",
@@ -13475,14 +13479,26 @@
}
},
"node_modules/docker-compose": {
- "version": "0.22.2",
- "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.22.2.tgz",
- "integrity": "sha512-iXWb5+LiYmylIMFXvGTYsjI1F+Xyx78Jm/uj1dxwwZLbWkUdH6yOXY5Nr3RjbYX15EgbGJCq78d29CmWQQQMPg==",
+ "version": "0.24.6",
+ "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.6.tgz",
+ "integrity": "sha512-VidlUyNzXMaVsuM79sjSvwC4nfojkP2VneL+Zfs538M2XFnffZDhx6veqnz/evCNIYGyz5O+1fgL6+g0NLWTBA==",
"dev": true,
+ "dependencies": {
+ "yaml": "^2.2.2"
+ },
"engines": {
"node": ">= 6.0.0"
}
},
+ "node_modules/docker-compose/node_modules/yaml": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+ "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -33300,9 +33316,9 @@
}
},
"@woocommerce/dependency-extraction-webpack-plugin": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.2.0.tgz",
- "integrity": "sha512-0wDY3EIUwWrPm0KrWvt1cf2SZDSX7CzBXvv4TyCqWOPuVPvC/ajyY8kD1HTFI80q6/RHoxWf3BYCmhuBzPbe9A==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.3.0.tgz",
+ "integrity": "sha512-dfa5bBZFRKcjFpqTEXyuUKC16n85RlKQ4EamdAAqUVQedv4DMRCj2jWUcId7pN2mOFGgAp+heGgb9i6fwiZk2Q==",
"dev": true,
"requires": {
"@wordpress/dependency-extraction-webpack-plugin": "^3.3.0"
@@ -33861,14 +33877,14 @@
}
},
"@wordpress/env": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-8.4.0.tgz",
- "integrity": "sha512-3DnTW/WanvQpWqagCIMQYtSBYj9wXLQQqGgmKl8gVJ4MfxV3K5A9zE25Rv6iogdr7ydLcPSbmNJx6mYgQRaSog==",
+ "version": "9.4.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-9.4.0.tgz",
+ "integrity": "sha512-flF+WLAuf8j97OjFkJU7/l9bzvI7GEUm1MXYdo16kSqxBvOTwVkk2yf5s9nxREJ3gPYahgNYf8cA7uT9Rj2eDQ==",
"dev": true,
"requires": {
"chalk": "^4.0.0",
"copy-dir": "^1.3.0",
- "docker-compose": "^0.22.2",
+ "docker-compose": "^0.24.3",
"extract-zip": "^1.6.7",
"got": "^11.8.5",
"inquirer": "^7.1.0",
@@ -38551,10 +38567,21 @@
}
},
"docker-compose": {
- "version": "0.22.2",
- "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.22.2.tgz",
- "integrity": "sha512-iXWb5+LiYmylIMFXvGTYsjI1F+Xyx78Jm/uj1dxwwZLbWkUdH6yOXY5Nr3RjbYX15EgbGJCq78d29CmWQQQMPg==",
- "dev": true
+ "version": "0.24.6",
+ "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.6.tgz",
+ "integrity": "sha512-VidlUyNzXMaVsuM79sjSvwC4nfojkP2VneL+Zfs538M2XFnffZDhx6veqnz/evCNIYGyz5O+1fgL6+g0NLWTBA==",
+ "dev": true,
+ "requires": {
+ "yaml": "^2.2.2"
+ },
+ "dependencies": {
+ "yaml": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
+ "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
+ "dev": true
+ }
+ }
},
"doctrine": {
"version": "3.0.0",
diff --git a/package.json b/package.json
index 07e6e13cee..868c3cd0ac 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "google-listings-and-ads",
"title": "Google Listings and Ads",
- "version": "2.5.18",
+ "version": "2.6.0",
"description": "google-listings-and-ads",
"author": "Automattic",
"license": "GPL-3.0-or-later",
@@ -57,9 +57,9 @@
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
- "@woocommerce/dependency-extraction-webpack-plugin": "^2.0.0",
+ "@woocommerce/dependency-extraction-webpack-plugin": "^2.3.0",
"@woocommerce/eslint-plugin": "^1.2.0",
- "@wordpress/env": "^8.4.0",
+ "@wordpress/env": "^9.4.0",
"@wordpress/jest-preset-default": "^11.9.0",
"@wordpress/prettier-config": "2.18.1",
"@wordpress/scripts": "^24.6.0",
@@ -89,11 +89,11 @@
"archive": "composer archive --file=$npm_package_name --format=zip",
"postarchive": "rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name",
"prebuild": "composer install",
- "build": "NODE_ENV=production wp-scripts build && npm run i18n",
+ "build": "NODE_ENV=production wp-scripts build --webpack-src-dir=js/src/blocks && npm run i18n",
"postbuild": "npm run archive",
"check-engines": "wp-scripts check-engines",
"check-licenses": "wp-scripts check-licenses",
- "dev": "NODE_ENV=development wp-scripts build",
+ "dev": "NODE_ENV=development wp-scripts build --webpack-src-dir=js/src/blocks",
"doc:tracking": "woocommerce-grow-jsdoc ./js/src",
"format": "wp-scripts format",
"i18n": "WP_CLI_PHP_ARGS='-d memory_limit=2048M' ./vendor/bin/wp i18n make-pot ./ languages/$npm_package_name.pot --slug=$npm_package_name --domain=$npm_package_name --exclude=bin,data,js/src,node_modules,tests,vendor",
@@ -108,7 +108,7 @@
"outdated:dewp": "npm outdated --color=always `cat .externalized.json | sed 's/[][\",]/ /g'` | grep -E --color=never \"Depended by|google-listings-and-ads$\" || true",
"outdated:nondewp": "npm outdated --color=always | grep --color=never --invert -E \"^(.\\[31m|.\\[33m)?(`cat .externalized.json | sed 's/[][\"]//g'| sed 's/,/|/g'`)\"",
"packages-update": "wp-scripts packages-update",
- "start": "wp-scripts start",
+ "start": "wp-scripts start --webpack-src-dir=js/src/blocks",
"start:hot": "npm run dev -- --config ./webpack-development.config.js && npm start -- --hot --allowed-hosts all",
"test:e2e": "npx playwright test --config=tests/e2e/config/playwright.config.js",
"test:e2e-dev": "npx playwright test --config=tests/e2e/config/playwright.config.js --debug",
diff --git a/readme.txt b/readme.txt
index d5330c6d4f..d8279c0379 100644
--- a/readme.txt
+++ b/readme.txt
@@ -5,7 +5,7 @@ Requires at least: 5.9
Tested up to: 6.4
Requires PHP: 7.4
Requires PHP Architecture: 64 Bits
-Stable tag: 2.5.18
+Stable tag: 2.6.0
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
@@ -111,6 +111,11 @@ Yes, you can run both at the same time, and we recommend it! In the US, advertis
== Changelog ==
+= 2.6.0 - 2024-02-27 =
+* Add - Support the new product editor (Product Block Editor).
+* Dev - Fix the compatibility issue in starting E2E test environment due to the default charset change in MariaDB v11.3.1.
+* Fix - 401 handling for connected Ads accounts.
+
= 2.5.18 - 2024-02-20 =
* Fix - Prevent product queries by IDs if no arguments are supplied.
@@ -121,13 +126,4 @@ Yes, you can run both at the same time, and we recommend it! In the US, advertis
* Fix - Prevent notifications from sending request to Google API when disconnected.
* Tweak - WC 8.6 compatibility.
-= 2.5.16 - 2024-01-30 =
-* Add - Include connected accounts in tracks from the backend.
-* Add - Include plugin version, Google Merchant Center account ID, and Google Ads account ID in all frontend tracking events.
-* Add - Send the related tracking event with the account ID to be connected when connecting to an existing Google Merchant Center or Google Ads account.
-* Add - Tracking for completed events.
-* Dev - Generate coverage report with xdebug.
-* Fix - Context not tracked in Create Campaign FAQs.
-* Fix - WordPress 6.4 Compatibility: Set an appropriate width for the content in the Popover component.
-
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/google-listings-and-ads/trunk/changelog.txt).
diff --git a/src/Admin/Input/Checkbox.php b/src/Admin/Input/Checkbox.php
deleted file mode 100644
index c1a0df1307..0000000000
--- a/src/Admin/Input/Checkbox.php
+++ /dev/null
@@ -1,20 +0,0 @@
-type = $type;
+ public function __construct( string $type, string $block_name ) {
+ $this->type = $type;
+ $this->block_name = $block_name;
parent::__construct();
}
@@ -159,4 +175,60 @@ public function get_view_id(): string {
return sprintf( 'gla_%s', $this->get_name() );
}
+
+ /**
+ * Return the name of a generic product block in WooCommerce core or a custom block in this extension.
+ *
+ * @return string
+ */
+ public function get_block_name(): string {
+ return $this->block_name;
+ }
+
+ /**
+ * Add or update a block attribute used for block config.
+ *
+ * @param string $key The attribute key defined in the corresponding block.json
+ * @param mixed $value The attribute value defined in the corresponding block.json
+ *
+ * @return InputInterface
+ */
+ public function set_block_attribute( string $key, $value ): InputInterface {
+ $this->block_attributes[ $key ] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Return the attributes of block config used for the input's view within the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_block_attributes(): array {
+ $meta_key = $this->prefix_meta_key( $this->get_id() );
+
+ return array_merge(
+ [
+ 'property' => "meta_data.{$meta_key}",
+ 'label' => $this->get_label(),
+ 'tooltip' => $this->get_description(),
+ ],
+ $this->block_attributes
+ );
+ }
+
+ /**
+ * Return the config used for the input's block within the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_block_config(): array {
+ $id = $this->get_id();
+
+ return [
+ 'id' => "google-listings-and-ads-product-attributes-{$id}",
+ 'blockName' => $this->get_block_name(),
+ 'attributes' => $this->get_block_attributes(),
+ ];
+ }
}
diff --git a/src/Admin/Input/InputInterface.php b/src/Admin/Input/InputInterface.php
index 98c67192f1..db580be968 100644
--- a/src/Admin/Input/InputInterface.php
+++ b/src/Admin/Input/InputInterface.php
@@ -71,4 +71,35 @@ public function set_value( $value ): InputInterface;
* @return string
*/
public function get_view_id(): string;
+
+ /**
+ * Return the name of a generic product block in WooCommerce core or a custom block in this extension.
+ *
+ * @return string
+ */
+ public function get_block_name(): string;
+
+ /**
+ * Add or update a block attribute used for block config.
+ *
+ * @param string $key The attribute key defined in the corresponding block.json
+ * @param mixed $value The attribute value defined in the corresponding block.json
+ *
+ * @return InputInterface
+ */
+ public function set_block_attribute( string $key, $value ): InputInterface;
+
+ /**
+ * Return the attributes of block config used for the input's view within the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_block_attributes(): array;
+
+ /**
+ * Return the block config used for the input's view within the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_block_config(): array;
}
diff --git a/src/Admin/Input/Integer.php b/src/Admin/Input/Integer.php
index 169044da4b..c40dc78625 100644
--- a/src/Admin/Input/Integer.php
+++ b/src/Admin/Input/Integer.php
@@ -15,6 +15,11 @@ class Integer extends Input {
* Integer constructor.
*/
public function __construct() {
- parent::__construct( 'integer' );
+ // Ideally, it should use the 'woocommerce/product-number-field' block
+ // but the block doesn't support integer validation. Therefore, it uses
+ // the text field block to work around it.
+ parent::__construct( 'integer', 'woocommerce/product-text-field' );
+
+ $this->set_block_attribute( 'type', [ 'value' => 'number' ] );
}
}
diff --git a/src/Admin/Input/Select.php b/src/Admin/Input/Select.php
index ae421a9deb..f2fcdf8c47 100644
--- a/src/Admin/Input/Select.php
+++ b/src/Admin/Input/Select.php
@@ -20,7 +20,7 @@ class Select extends Input {
* Select constructor.
*/
public function __construct() {
- parent::__construct( 'select' );
+ parent::__construct( 'select', 'google-listings-and-ads/product-select-field' );
}
/**
@@ -55,4 +55,24 @@ public function get_view_data(): array {
return $view_data;
}
+
+ /**
+ * Return the attributes of block config used for the input's view within the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_block_attributes(): array {
+ $options = [];
+
+ foreach ( $this->get_options() as $key => $value ) {
+ $options[] = [
+ 'label' => $value,
+ 'value' => $key,
+ ];
+ }
+
+ $this->set_block_attribute( 'options', $options );
+
+ return parent::get_block_attributes();
+ }
}
diff --git a/src/Admin/Input/SelectWithTextInput.php b/src/Admin/Input/SelectWithTextInput.php
index 622d977dbc..952b6baad4 100644
--- a/src/Admin/Input/SelectWithTextInput.php
+++ b/src/Admin/Input/SelectWithTextInput.php
@@ -28,7 +28,7 @@ public function __construct() {
->set_name( self::CUSTOM_INPUT_KEY );
$this->add( $custom_input );
- parent::__construct( 'select-with-text-input' );
+ parent::__construct( 'select-with-text-input', 'google-listings-and-ads/product-select-with-text-field' );
}
/**
@@ -85,15 +85,6 @@ protected function get_custom_input(): Text {
return $this->children[ self::CUSTOM_INPUT_KEY ];
}
- /**
- * Whether the set value is a custom value (i.e. not from the list of specified options).
- *
- * @return bool
- */
- protected function is_custom_value(): bool {
- return ! empty( $this->get_value() ) && ! is_array( $this->get_value() ) && ! isset( $this->get_options()[ $this->get_value() ] );
- }
-
/**
* Return the data used for the input's view.
*
@@ -145,4 +136,30 @@ public function set_data( $data ): void {
$this->data = $select_value;
}
}
+
+ /**
+ * Return the attributes of block config used for the input's view within the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_block_attributes(): array {
+ $options = [];
+
+ foreach ( $this->get_options() as $key => $value ) {
+ $options[] = [
+ 'label' => $value,
+ 'value' => $key,
+ ];
+ }
+
+ $options[] = [
+ 'label' => __( 'Enter a custom value', 'google-listings-and-ads' ),
+ 'value' => self::CUSTOM_INPUT_KEY,
+ ];
+
+ $this->set_block_attribute( 'options', $options );
+ $this->set_block_attribute( 'customInputValue', self::CUSTOM_INPUT_KEY );
+
+ return parent::get_block_attributes();
+ }
}
diff --git a/src/Admin/Input/Text.php b/src/Admin/Input/Text.php
index 5a5c9eddd0..a1fd41c952 100644
--- a/src/Admin/Input/Text.php
+++ b/src/Admin/Input/Text.php
@@ -15,6 +15,6 @@ class Text extends Input {
* Text constructor.
*/
public function __construct() {
- parent::__construct( 'text' );
+ parent::__construct( 'text', 'woocommerce/product-text-field' );
}
}
diff --git a/src/Admin/Product/Attributes/AttributesForm.php b/src/Admin/Product/Attributes/AttributesForm.php
index 0da6a9e055..e816b7071d 100644
--- a/src/Admin/Product/Attributes/AttributesForm.php
+++ b/src/Admin/Product/Attributes/AttributesForm.php
@@ -32,7 +32,7 @@ class AttributesForm extends Form {
/**
* AttributesForm constructor.
*
- * @param string[] $attribute_types
+ * @param string[] $attribute_types The names of the attribute classes extending AttributeInterface.
* @param array $data
*/
public function __construct( array $attribute_types, array $data = [] ) {
@@ -57,23 +57,11 @@ public function get_view_data(): array {
continue;
}
- $attribute_id = $index;
- $attribute_type = $this->attribute_types[ $index ];
- $applicable_types = call_user_func( [ $attribute_type, 'get_applicable_product_types' ] );
+ $attribute_type = $this->attribute_types[ $index ];
+ $attribute_product_types = self::get_attribute_product_types( $attribute_type );
- /**
- * This filter is documented in AttributeManager::map_attribute_types
- *
- * @see AttributeManager::map_attribute_types
- */
- $applicable_types = apply_filters( "woocommerce_gla_attribute_applicable_product_types_{$attribute_id}", $applicable_types, $attribute_type );
-
- /**
- * Filters the list of product types to hide the attribute for.
- */
- $hidden_types = apply_filters( "woocommerce_gla_attribute_hidden_product_types_{$attribute_id}", [] );
-
- $visible_types = array_diff( $applicable_types, $hidden_types );
+ $hidden_types = $attribute_product_types['hidden'];
+ $visible_types = $attribute_product_types['visible'];
$input['gla_wrapper_class'] = $input['gla_wrapper_class'] ?? '';
@@ -91,13 +79,44 @@ public function get_view_data(): array {
return $view_data;
}
+ /**
+ * Get the hidden and visible types of an attribute's applicable product types.
+ *
+ * @param string $attribute_type The name of an attribute class extending AttributeInterface.
+ *
+ * @return array
+ */
+ public static function get_attribute_product_types( string $attribute_type ): array {
+ $attribute_id = call_user_func( [ $attribute_type, 'get_id' ] );
+ $applicable_product_types = call_user_func( [ $attribute_type, 'get_applicable_product_types' ] );
+
+ /**
+ * This filter is documented in AttributeManager::map_attribute_types
+ *
+ * @see AttributeManager::map_attribute_types
+ */
+ $applicable_product_types = apply_filters( "woocommerce_gla_attribute_applicable_product_types_{$attribute_id}", $applicable_product_types, $attribute_type );
+
+ /**
+ * Filters the list of product types to hide the attribute for.
+ */
+ $hidden_product_types = apply_filters( "woocommerce_gla_attribute_hidden_product_types_{$attribute_id}", [] );
+
+ $visible_product_types = array_diff( $applicable_product_types, $hidden_product_types );
+
+ return [
+ 'hidden' => $hidden_product_types,
+ 'visible' => $visible_product_types,
+ ];
+ }
+
/**
* @param InputInterface $input
* @param AttributeInterface $attribute
*
* @return InputInterface
*/
- protected function init_input( InputInterface $input, AttributeInterface $attribute ) {
+ public static function init_input( InputInterface $input, AttributeInterface $attribute ) {
$input->set_id( $attribute::get_id() )
->set_name( $attribute::get_id() );
@@ -113,7 +132,7 @@ protected function init_input( InputInterface $input, AttributeInterface $attrib
$new_input->set_label( $input->get_label() )
->set_description( $input->get_description() );
- return $this->init_input( $new_input, $attribute );
+ return self::init_input( $new_input, $attribute );
}
// add a 'default' value option
@@ -128,8 +147,8 @@ protected function init_input( InputInterface $input, AttributeInterface $attrib
/**
* Add an attribute to the form
*
- * @param string $attribute_type An attribute class extending AttributeInterface
- * @param string|null $input_type An input class extending InputInterface to use for attribute input.
+ * @param string $attribute_type The name of an attribute class extending AttributeInterface.
+ * @param string|null $input_type The name of an input class extending InputInterface to use for attribute input.
*
* @return AttributesForm
*
@@ -146,7 +165,7 @@ public function add_attribute( string $attribute_type, ?string $input_type = nul
$this->validate_interface( $input_type, InputInterface::class );
- $attribute_input = $this->init_input( new $input_type(), new $attribute_type() );
+ $attribute_input = self::init_input( new $input_type(), new $attribute_type() );
$this->add( $attribute_input );
$attribute_id = call_user_func( [ $attribute_type, 'get_id' ] );
@@ -158,7 +177,7 @@ public function add_attribute( string $attribute_type, ?string $input_type = nul
/**
* Remove an attribute from the form
*
- * @param string $attribute_type An attribute class extending AttributeInterface
+ * @param string $attribute_type The name of an attribute class extending AttributeInterface.
*
* @return AttributesForm
*
@@ -178,8 +197,8 @@ public function remove_attribute( string $attribute_type ): AttributesForm {
/**
* Sets the input type for the given attribute.
*
- * @param string $attribute_type
- * @param string $input_type
+ * @param string $attribute_type The name of an attribute class extending AttributeInterface.
+ * @param string $input_type The name of an input class extending InputInterface to use for attribute input.
*
* @return $this
*
@@ -195,7 +214,7 @@ public function set_attribute_input( string $attribute_type, string $input_type
$attribute_id = call_user_func( [ $attribute_type, 'get_id' ] );
if ( $this->has( $attribute_id ) ) {
- $this->children[ $attribute_id ] = $this->init_input( new $input_type(), new $attribute_type() );
+ $this->children[ $attribute_id ] = self::init_input( new $input_type(), new $attribute_type() );
}
return $this;
diff --git a/src/Admin/Product/Attributes/AttributesTab.php b/src/Admin/Product/Attributes/AttributesTab.php
index a2f3b1fcc0..85f12c5888 100644
--- a/src/Admin/Product/Attributes/AttributesTab.php
+++ b/src/Admin/Product/Attributes/AttributesTab.php
@@ -23,6 +23,7 @@
class AttributesTab implements Service, Registerable, Conditional {
use AdminConditional;
+ use AttributesTrait;
/**
* @var Admin
@@ -165,15 +166,6 @@ protected function get_form( WC_Product $product ): AttributesForm {
return $form;
}
- /**
- * Return an array of WooCommerce product types that the Google Listings and Ads tab can be displayed for.
- *
- * @return array of WooCommerce product types (e.g. 'simple', 'variable', etc.)
- */
- protected function get_applicable_product_types(): array {
- return apply_filters( 'woocommerce_gla_attributes_tab_applicable_product_types', [ 'simple', 'variable' ] );
- }
-
/**
* @param WC_Product $product
* @param array $data
diff --git a/src/Admin/Product/Attributes/AttributesTrait.php b/src/Admin/Product/Attributes/AttributesTrait.php
new file mode 100644
index 0000000000..4d80abe30c
--- /dev/null
+++ b/src/Admin/Product/Attributes/AttributesTrait.php
@@ -0,0 +1,20 @@
+set_label( __( 'Multipack', 'google-listings-and-ads' ) );
$this->set_description( __( 'The number of identical products in a multipack. Use this attribute to indicate that you\'ve grouped multiple identical products for sale as one item.', 'google-listings-and-ads' ) );
+ $this->set_block_attribute( 'min', [ 'value' => 0 ] );
}
}
diff --git a/src/Admin/Product/ChannelVisibilityBlock.php b/src/Admin/Product/ChannelVisibilityBlock.php
new file mode 100644
index 0000000000..2ee0c112e7
--- /dev/null
+++ b/src/Admin/Product/ChannelVisibilityBlock.php
@@ -0,0 +1,153 @@
+product_helper = $product_helper;
+ $this->merchant_center = $merchant_center;
+ }
+
+ /**
+ * Register hooks for querying and updating product via REST APIs.
+ */
+ public function register(): void {
+ if ( ! $this->merchant_center->is_setup_complete() ) {
+ return;
+ }
+
+ // https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-products-v2-controller.php#L182-L192
+ add_filter( 'woocommerce_rest_prepare_product_object', [ $this, 'prepare_data' ], 10, 2 );
+
+ // https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-crud-controller.php#L200-L207
+ // https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-crud-controller.php#L247-L254
+ add_action( 'woocommerce_rest_insert_product_object', [ $this, 'update_data' ], 10, 2 );
+ }
+
+ /**
+ * Get channel visibility data from the given product and add it to the given response.
+ *
+ * @param Response $response Response to be added channel visibility data.
+ * @param WC_Product|WC_Data $product WooCommerce product to get data.
+ *
+ * @return Response
+ */
+ public function prepare_data( Response $response, WC_Data $product ): Response {
+ if ( ! $product instanceof WC_Product ) {
+ return $response;
+ }
+
+ $response->data[ self::PROPERTY ] = [
+ 'is_visible' => $product->is_visible(),
+ 'channel_visibility' => $this->product_helper->get_channel_visibility( $product ),
+ 'sync_status' => $this->product_helper->get_sync_status( $product ),
+ 'issues' => $this->product_helper->get_validation_errors( $product ),
+ ];
+
+ return $response;
+ }
+
+ /**
+ * Get channel visibility data from the given request and update it to the given product.
+ *
+ * @param WC_Product|WC_Data $product WooCommerce product to be updated.
+ * @param Request $request Response to get the channel visibility data.
+ */
+ public function update_data( WC_Data $product, Request $request ): void {
+ if ( ! $product instanceof WC_Product || ! in_array( $product->get_type(), $this->get_visible_product_types(), true ) ) {
+ return;
+ }
+
+ $params = $request->get_params();
+
+ if ( ! isset( $params[ self::PROPERTY ] ) ) {
+ return;
+ }
+
+ $channel_visibility = $params[ self::PROPERTY ]['channel_visibility'];
+
+ if ( $channel_visibility !== $this->product_helper->get_channel_visibility( $product ) ) {
+ $this->product_helper->update_channel_visibility( $product, $channel_visibility );
+ }
+ }
+
+ /**
+ * Return the visible product types to control the hidden condition of the channel visibility block
+ * in the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_visible_product_types(): array {
+ return array_diff( ProductSyncer::get_supported_product_types(), [ 'variation' ] );
+ }
+
+ /**
+ * Return the config used for the input's block within the Product Block Editor.
+ *
+ * @return array
+ */
+ public function get_block_config(): array {
+ $options = [];
+
+ foreach ( ChannelVisibility::get_value_options() as $key => $value ) {
+ $options[] = [
+ 'label' => $value,
+ 'value' => $key,
+ ];
+ }
+
+ $attributes = [
+ 'property' => self::PROPERTY,
+ 'options' => $options,
+ 'valueOfSync' => ChannelVisibility::SYNC_AND_SHOW,
+ 'valueOfDontSync' => ChannelVisibility::DONT_SYNC_AND_SHOW,
+ 'statusOfSynced' => SyncStatus::SYNCED,
+ 'statusOfHasErrors' => SyncStatus::HAS_ERRORS,
+ ];
+
+ return [
+ 'id' => 'google-listings-and-ads-product-channel-visibility',
+ 'blockName' => 'google-listings-and-ads/product-channel-visibility',
+ 'attributes' => $attributes,
+ ];
+ }
+}
diff --git a/src/Admin/ProductBlocksService.php b/src/Admin/ProductBlocksService.php
new file mode 100644
index 0000000000..7286ce006d
--- /dev/null
+++ b/src/Admin/ProductBlocksService.php
@@ -0,0 +1,344 @@
+assets_handler = $assets_handler;
+ $this->attribute_manager = $attribute_manager;
+ $this->merchant_center = $merchant_center;
+ $this->channel_visibility_block = $channel_visibility_block;
+ }
+
+ /**
+ * Return whether this service is needed to be registered.
+ *
+ * @return bool Whether this service is needed to be registered.
+ */
+ public static function is_needed(): bool {
+ // compatibility-code "WC >= 8.6" -- The Block Template API used requires at least WooCommerce 8.6
+ return version_compare( WC_VERSION, '8.6', '>=' );
+ }
+
+ /**
+ * Register a service.
+ */
+ public function register(): void {
+ if ( PageController::is_admin_page() ) {
+ add_action( 'init', [ $this, 'hook_init' ] );
+ }
+
+ // https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/AbstractProductFormTemplate.php#L19
+ $template_area = 'product-form';
+
+ // https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/SimpleProductTemplate.php#L19
+ // https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce/src/Internal/Features/ProductBlockEditor/ProductTemplates/ProductVariationTemplate.php#L19
+ $block_id = 'general';
+
+ add_action(
+ "woocommerce_block_template_area_{$template_area}_after_add_block_{$block_id}",
+ [ $this, 'hook_block_template' ]
+ );
+ }
+
+ /**
+ * Action hanlder for the 'init' hook.
+ */
+ public function hook_init(): void {
+ $build_path = "{$this->get_root_dir()}/js/build";
+ $uri = 'js/build/blocks';
+
+ $this->register_custom_blocks( BlockRegistry::get_instance(), $build_path, $uri, self::CUSTOM_BLOCKS );
+ }
+
+ /**
+ * Action hanlder for the "woocommerce_block_template_area_{$template_area}_after_add_block_{$block_id}" hook.
+ *
+ * @param BlockInterface $block The block just added to get its root template to add this extension's group and blocks.
+ */
+ public function hook_block_template( BlockInterface $block ): void {
+ /** @var Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\ProductFormTemplateInterface */
+ $template = $block->get_root_template();
+
+ $is_variation_template = $this->is_variation_template( $block );
+
+ // Please note that the simple, variable, grouped, and external product types
+ // use the same product block template 'simple-product'. Their dynamic hidden
+ // conditions are added below.
+ if ( 'simple-product' !== $template->get_id() && ! $is_variation_template ) {
+ return;
+ }
+
+ /** @var Automattic\WooCommerce\Admin\Features\ProductBlockEditor\ProductTemplates\GroupInterface */
+ $group = $template->add_group(
+ [
+ 'id' => 'google-listings-and-ads-group',
+ 'order' => 100,
+ 'attributes' => [
+ 'title' => __( 'Google Listings & Ads', 'google-listings-and-ads' ),
+ ],
+ ]
+ );
+
+ $visible_product_types = ProductSyncer::get_supported_product_types();
+
+ if ( $is_variation_template ) {
+ // The property of `editedProduct.type` doesn't exist in the variation product.
+ // The condition returned from `get_hide_condition` won't work, so it uses 'true' directly.
+ if ( ! in_array( 'variation', $visible_product_types, true ) ) {
+ $group->add_hide_condition( 'true' );
+ }
+ } else {
+ $group->add_hide_condition( $this->get_hide_condition( $visible_product_types ) );
+ }
+
+ if ( ! $this->merchant_center->is_setup_complete() ) {
+ $group->add_block(
+ [
+ 'id' => 'google-listings-and-ads-product-onboarding-prompt',
+ 'blockName' => 'google-listings-and-ads/product-onboarding-prompt',
+ 'attributes' => [
+ 'startUrl' => $this->get_start_url(),
+ ],
+ ]
+ );
+
+ return;
+ }
+
+ /** @var SectionInterface */
+ $channel_visibility_section = $group->add_section(
+ [
+ 'id' => 'google-listings-and-ads-channel-visibility-section',
+ 'order' => 1,
+ 'attributes' => [
+ 'title' => __( 'Channel visibility', 'google-listings-and-ads' ),
+ ],
+ ]
+ );
+
+ if ( ! $is_variation_template ) {
+ $this->add_channel_visibility_block( $channel_visibility_section );
+ }
+
+ // Add the hidden condition to the channel visibility section because it only has one block.
+ $visible_product_types = $this->channel_visibility_block->get_visible_product_types();
+ $channel_visibility_section->add_hide_condition( $this->get_hide_condition( $visible_product_types ) );
+
+ /** @var SectionInterface */
+ $product_attributes_section = $group->add_section(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-section',
+ 'order' => 2,
+ 'attributes' => [
+ 'title' => __( 'Product attributes', 'google-listings-and-ads' ),
+ ],
+ ]
+ );
+
+ $this->add_product_attribute_blocks( $product_attributes_section );
+ }
+
+ /**
+ * Register the custom blocks and their assets.
+ *
+ * @param BlockRegistry $block_registry BlockRegistry instance getting from Woo Core for registering custom blocks.
+ * @param string $build_path The absolute path to the build directory of the assets.
+ * @param string $uri The script URI of the custom blocks.
+ * @param string[] $custom_blocks The directory names of each custom block under the build path.
+ */
+ public function register_custom_blocks( BlockRegistry $block_registry, string $build_path, string $uri, array $custom_blocks ): void {
+ foreach ( $custom_blocks as $custom_block ) {
+ $block_json_file = "{$build_path}/{$custom_block}/block.json";
+
+ if ( ! file_exists( $block_json_file ) ) {
+ continue;
+ }
+
+ $block_registry->register_block_type_from_metadata( $block_json_file );
+ }
+
+ $assets[] = new AdminScriptWithBuiltDependenciesAsset(
+ 'google-listings-and-ads-product-blocks',
+ $uri,
+ "{$build_path}/blocks.asset.php",
+ new BuiltScriptDependencyArray(
+ [
+ 'dependencies' => [],
+ 'version' => (string) filemtime( "{$build_path}/blocks.js" ),
+ ]
+ )
+ );
+
+ $assets[] = new AdminStyleAsset(
+ 'google-listings-and-ads-product-blocks-css',
+ $uri,
+ [],
+ (string) filemtime( "{$build_path}/blocks.css" )
+ );
+
+ $this->assets_handler->register_many( $assets );
+ $this->assets_handler->enqueue_many( $assets );
+ }
+
+ /**
+ * Add the channel visibility block to the given section block.
+ *
+ * @param SectionInterface $section The section block to add the channel visibility block
+ */
+ private function add_channel_visibility_block( SectionInterface $section ): void {
+ $section->add_block( $this->channel_visibility_block->get_block_config() );
+ }
+
+ /**
+ * Add product attribute blocks to the given section block.
+ *
+ * @param SectionInterface $section The section block to add product attribute blocks
+ */
+ private function add_product_attribute_blocks( SectionInterface $section ): void {
+ $is_variation_template = $this->is_variation_template( $section );
+
+ $product_types = $is_variation_template ? [ 'variation' ] : $this->get_applicable_product_types();
+ $attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( $product_types );
+
+ foreach ( $attribute_types as $attribute_type ) {
+ $input_type = call_user_func( [ $attribute_type, 'get_input_type' ] );
+ $input = AttributesForm::init_input( new $input_type(), new $attribute_type() );
+
+ if ( $is_variation_template ) {
+ // When editing a variation, its product type on the frontend side won't be changed dynamically.
+ // In addition, the property of `editedProduct.type` doesn't exist in the variation product.
+ // Therefore, instead of using the ProductTemplates API `add_hide_condition` to conditionally
+ // hide attributes, it doesn't add invisible attribute blocks from the beginning.
+ if ( $this->is_visible_for_variation( $attribute_type ) ) {
+ $section->add_block( $input->get_block_config() );
+ }
+ } else {
+ $visible_product_types = AttributesForm::get_attribute_product_types( $attribute_type )['visible'];
+
+ // When editing a simple, variable, grouped, or external product, its product type on the
+ // frontend side can be changed dynamically. So, it needs to use the ProductTemplates API
+ // `add_hide_condition` to conditionally hide attributes.
+ /** @var BlockInterface */
+ $block = $section->add_block( $input->get_block_config() );
+ $block->add_hide_condition( $this->get_hide_condition( $visible_product_types ) );
+ }
+ }
+ }
+
+ /**
+ * Determine if the product block template of the given block is the variation template.
+ *
+ * @param BlockInterface $block The block to be checked
+ *
+ * @return boolean
+ */
+ private function is_variation_template( BlockInterface $block ): bool {
+ return 'product-variation' === $block->get_root_template()->get_id();
+ }
+
+ /**
+ * Determine if the given attribute is visible for variation product after applying related filters.
+ *
+ * @param string $attribute_type An attribute class extending AttributeInterface
+ *
+ * @return bool
+ */
+ private function is_visible_for_variation( string $attribute_type ): bool {
+ $attribute_product_types = AttributesForm::get_attribute_product_types( $attribute_type );
+
+ return in_array( 'variation', $attribute_product_types['visible'], true );
+ }
+
+ /**
+ * Get the expression of the hide condition to a block based on the visible product types.
+ * e.g. "editedProduct.type !== 'simple' && ! editedProduct.parent_id > 0"
+ *
+ * The hide condition is a JavaScript-like expression that will be evaluated on the client to determine if the block should be hidden.
+ * See [@woocommerce/expression-evaluation](https://github.com/woocommerce/woocommerce/blob/trunk/packages/js/expression-evaluation/README.md) for more details.
+ *
+ * @param array $visible_product_types The visible product types to be converted to a hidden condition
+ *
+ * @return string
+ */
+ public function get_hide_condition( array $visible_product_types ): string {
+ $conditions = array_map(
+ function ( $type ) {
+ return "editedProduct.type !== '{$type}'";
+ },
+ $visible_product_types
+ );
+
+ return implode( ' && ', $conditions ) ?: 'true';
+ }
+}
diff --git a/src/Assets/BaseAsset.php b/src/Assets/BaseAsset.php
index d4e371396e..9838652c3f 100644
--- a/src/Assets/BaseAsset.php
+++ b/src/Assets/BaseAsset.php
@@ -130,6 +130,10 @@ public function get_uri(): string {
* @return bool
*/
public function can_enqueue(): bool {
+ if ( is_null( $this->enqueue_condition_callback ) ) {
+ return true;
+ }
+
return (bool) call_user_func( $this->enqueue_condition_callback, $this );
}
diff --git a/src/Hooks/README.md b/src/Hooks/README.md
index 6c824becda..acfe826c00 100644
--- a/src/Hooks/README.md
+++ b/src/Hooks/README.md
@@ -8,7 +8,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [BulkEditInitializer.php#L36](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/BulkEdit/BulkEditInitializer.php#L36)
+- [BulkEditInitializer.php#L36](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/BulkEdit/BulkEditInitializer.php#L36)
## woocommerce_admin_disabled
@@ -16,7 +16,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCAdminValidator.php#L38](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Internal/Requirements/WCAdminValidator.php#L38)
+- [WCAdminValidator.php#L38](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Internal/Requirements/WCAdminValidator.php#L38)
## woocommerce_gla_ads_billing_setup_status
@@ -24,8 +24,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Ads.php#L112](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Ads.php#L112)
-- [Ads.php#L121](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Ads.php#L121)
+- [Ads.php#L112](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Ads.php#L112)
+- [Ads.php#L121](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Ads.php#L121)
## woocommerce_gla_ads_client_exception
@@ -33,24 +33,24 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AdsAssetGroupAsset.php#L135](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsAssetGroupAsset.php#L135)
-- [AdsAssetGroupAsset.php#L201](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsAssetGroupAsset.php#L201)
-- [Ads.php#L73](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Ads.php#L73)
-- [Ads.php#L117](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Ads.php#L117)
-- [Ads.php#L172](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Ads.php#L172)
-- [Ads.php#L214](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Ads.php#L214)
-- [Ads.php#L298](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Ads.php#L298)
-- [AdsCampaign.php#L140](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsCampaign.php#L140)
-- [AdsCampaign.php#L183](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsCampaign.php#L183)
-- [AdsCampaign.php#L246](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsCampaign.php#L246)
-- [AdsCampaign.php#L301](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsCampaign.php#L301)
-- [AdsCampaign.php#L338](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsCampaign.php#L338)
-- [AdsConversionAction.php#L97](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsConversionAction.php#L97)
-- [AdsConversionAction.php#L143](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsConversionAction.php#L143)
-- [AdsAssetGroup.php#L113](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsAssetGroup.php#L113)
-- [AdsAssetGroup.php#L304](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsAssetGroup.php#L304)
-- [AdsAssetGroup.php#L368](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsAssetGroup.php#L368)
-- [AdsReport.php#L105](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsReport.php#L105)
+- [AdsAssetGroupAsset.php#L135](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsAssetGroupAsset.php#L135)
+- [AdsAssetGroupAsset.php#L201](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsAssetGroupAsset.php#L201)
+- [Ads.php#L73](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Ads.php#L73)
+- [Ads.php#L117](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Ads.php#L117)
+- [Ads.php#L172](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Ads.php#L172)
+- [Ads.php#L214](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Ads.php#L214)
+- [Ads.php#L298](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Ads.php#L298)
+- [AdsCampaign.php#L140](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsCampaign.php#L140)
+- [AdsCampaign.php#L183](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsCampaign.php#L183)
+- [AdsCampaign.php#L246](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsCampaign.php#L246)
+- [AdsCampaign.php#L301](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsCampaign.php#L301)
+- [AdsCampaign.php#L338](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsCampaign.php#L338)
+- [AdsConversionAction.php#L97](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsConversionAction.php#L97)
+- [AdsConversionAction.php#L143](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsConversionAction.php#L143)
+- [AdsAssetGroup.php#L113](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsAssetGroup.php#L113)
+- [AdsAssetGroup.php#L304](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsAssetGroup.php#L304)
+- [AdsAssetGroup.php#L368](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsAssetGroup.php#L368)
+- [AdsReport.php#L105](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsReport.php#L105)
## woocommerce_gla_ads_setup_completed
@@ -58,7 +58,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [SetupCompleteController.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/Ads/SetupCompleteController.php#L66)
+- [SetupCompleteController.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/Ads/SetupCompleteController.php#L66)
## woocommerce_gla_attribute_applicable_product_types_
@@ -66,8 +66,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AttributeManager.php#L295](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/Attributes/AttributeManager.php#L295)
-- [AttributesForm.php#L69](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/Product/Attributes/AttributesForm.php#L69)
+- [AttributeManager.php#L295](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/Attributes/AttributeManager.php#L295)
+- [AttributesForm.php#L98](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/Product/Attributes/AttributesForm.php#L98)
## woocommerce_gla_attribute_hidden_product_types_
@@ -75,7 +75,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AttributesForm.php#L74](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/Product/Attributes/AttributesForm.php#L74)
+- [AttributesForm.php#L103](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/Product/Attributes/AttributesForm.php#L103)
## woocommerce_gla_attribute_mapping_sources
@@ -83,7 +83,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [IsFieldTrait.php#L31](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L31)
+- [IsFieldTrait.php#L31](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L31)
## woocommerce_gla_attribute_mapping_sources_custom_attributes
@@ -91,7 +91,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [IsFieldTrait.php#L125](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L125)
+- [IsFieldTrait.php#L125](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L125)
## woocommerce_gla_attribute_mapping_sources_global_attributes
@@ -99,7 +99,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [IsFieldTrait.php#L64](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L64)
+- [IsFieldTrait.php#L64](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L64)
## woocommerce_gla_attribute_mapping_sources_product_fields
@@ -107,7 +107,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [IsFieldTrait.php#L115](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L115)
+- [IsFieldTrait.php#L115](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L115)
## woocommerce_gla_attribute_mapping_sources_taxonomies
@@ -115,7 +115,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [IsFieldTrait.php#L65](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L65)
+- [IsFieldTrait.php#L65](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/AttributeMapping/Traits/IsFieldTrait.php#L65)
## woocommerce_gla_attributes_tab_applicable_product_types
@@ -123,7 +123,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AttributesTab.php#L174](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/Product/Attributes/AttributesTab.php#L174)
+- [AttributesTrait.php#L18](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/Product/Attributes/AttributesTrait.php#L18)
## woocommerce_gla_batch_deleted_products
@@ -131,7 +131,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L229](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L229)
+- [ProductSyncer.php#L229](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L229)
## woocommerce_gla_batch_retry_delete_products
@@ -139,7 +139,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L343](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L343)
+- [ProductSyncer.php#L343](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L343)
## woocommerce_gla_batch_retry_update_products
@@ -147,7 +147,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L287](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L287)
+- [ProductSyncer.php#L287](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L287)
## woocommerce_gla_batch_updated_products
@@ -155,7 +155,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L143](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L143)
+- [ProductSyncer.php#L143](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L143)
## woocommerce_gla_batched_job_size
@@ -163,8 +163,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [UpdateSyncableProductsCount.php#L74](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/UpdateSyncableProductsCount.php#L74)
-- [AbstractBatchedActionSchedulerJob.php#L104](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/AbstractBatchedActionSchedulerJob.php#L104)
+- [UpdateSyncableProductsCount.php#L74](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/UpdateSyncableProductsCount.php#L74)
+- [AbstractBatchedActionSchedulerJob.php#L104](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/AbstractBatchedActionSchedulerJob.php#L104)
## woocommerce_gla_bulk_update_coupon
@@ -172,7 +172,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponBulkEdit.php#L133](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/BulkEdit/CouponBulkEdit.php#L133)
+- [CouponBulkEdit.php#L133](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/BulkEdit/CouponBulkEdit.php#L133)
## woocommerce_gla_conversion_action_name
@@ -180,7 +180,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AdsConversionAction.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/AdsConversionAction.php#L66)
+- [AdsConversionAction.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/AdsConversionAction.php#L66)
## woocommerce_gla_coupon_destinations
@@ -188,7 +188,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCCouponAdapter.php#L391](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/WCCouponAdapter.php#L391)
+- [WCCouponAdapter.php#L391](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/WCCouponAdapter.php#L391)
## woocommerce_gla_coupons_delete_retry_on_failure
@@ -196,7 +196,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L438](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L438)
+- [CouponSyncer.php#L438](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L438)
## woocommerce_gla_coupons_update_retry_on_failure
@@ -204,7 +204,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L400](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L400)
+- [CouponSyncer.php#L400](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L400)
## woocommerce_gla_custom_merchant_issues
@@ -212,7 +212,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [MerchantStatuses.php#L435](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantStatuses.php#L435)
+- [MerchantStatuses.php#L435](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantStatuses.php#L435)
## woocommerce_gla_debug_message
@@ -220,38 +220,38 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L205](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L205)
-- [ProductSyncer.php#L149](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L149)
-- [ProductSyncer.php#L159](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L159)
-- [ProductSyncer.php#L235](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L235)
-- [ProductSyncer.php#L245](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L245)
-- [ProductHelper.php#L458](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductHelper.php#L458)
-- [ProductHelper.php#L490](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductHelper.php#L490)
-- [BatchProductHelper.php#L208](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/BatchProductHelper.php#L208)
-- [BatchProductHelper.php#L231](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/BatchProductHelper.php#L231)
-- [SyncerHooks.php#L197](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/SyncerHooks.php#L197)
-- [ProductRepository.php#L309](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductRepository.php#L309)
-- [ProductMetaQueryHelper.php#L92](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/DB/ProductMetaQueryHelper.php#L92)
-- [ProductMetaQueryHelper.php#L123](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/DB/ProductMetaQueryHelper.php#L123)
-- [IssuesController.php#L96](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/IssuesController.php#L96)
-- [ActionSchedulerJobMonitor.php#L117](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/ActionSchedulerJobMonitor.php#L117)
-- [ActionSchedulerJobMonitor.php#L126](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/ActionSchedulerJobMonitor.php#L126)
-- [CleanupSyncedProducts.php#L74](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/CleanupSyncedProducts.php#L74)
-- [MerchantStatuses.php#L334](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantStatuses.php#L334)
-- [MerchantStatuses.php#L357](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantStatuses.php#L357)
-- [MerchantCenterService.php#L305](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantCenterService.php#L305)
-- [CouponHelper.php#L257](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponHelper.php#L257)
-- [CouponHelper.php#L294](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponHelper.php#L294)
-- [CouponSyncer.php#L103](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L103)
-- [CouponSyncer.php#L116](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L116)
-- [CouponSyncer.php#L141](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L141)
-- [CouponSyncer.php#L155](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L155)
-- [CouponSyncer.php#L172](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L172)
-- [CouponSyncer.php#L195](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L195)
-- [CouponSyncer.php#L260](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L260)
-- [CouponSyncer.php#L309](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L309)
-- [CouponSyncer.php#L328](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L328)
-- [SyncerHooks.php#L178](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/SyncerHooks.php#L178)
+- [WCProductAdapter.php#L205](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L205)
+- [ProductSyncer.php#L149](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L149)
+- [ProductSyncer.php#L159](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L159)
+- [ProductSyncer.php#L235](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L235)
+- [ProductSyncer.php#L245](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L245)
+- [ProductHelper.php#L482](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductHelper.php#L482)
+- [ProductHelper.php#L514](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductHelper.php#L514)
+- [BatchProductHelper.php#L208](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/BatchProductHelper.php#L208)
+- [BatchProductHelper.php#L231](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/BatchProductHelper.php#L231)
+- [SyncerHooks.php#L197](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/SyncerHooks.php#L197)
+- [ProductRepository.php#L309](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductRepository.php#L309)
+- [ProductMetaQueryHelper.php#L92](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/DB/ProductMetaQueryHelper.php#L92)
+- [ProductMetaQueryHelper.php#L123](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/DB/ProductMetaQueryHelper.php#L123)
+- [IssuesController.php#L96](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/IssuesController.php#L96)
+- [ActionSchedulerJobMonitor.php#L117](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/ActionSchedulerJobMonitor.php#L117)
+- [ActionSchedulerJobMonitor.php#L126](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/ActionSchedulerJobMonitor.php#L126)
+- [CleanupSyncedProducts.php#L74](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/CleanupSyncedProducts.php#L74)
+- [MerchantStatuses.php#L334](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantStatuses.php#L334)
+- [MerchantStatuses.php#L357](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantStatuses.php#L357)
+- [MerchantCenterService.php#L305](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantCenterService.php#L305)
+- [CouponHelper.php#L257](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponHelper.php#L257)
+- [CouponHelper.php#L294](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponHelper.php#L294)
+- [CouponSyncer.php#L103](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L103)
+- [CouponSyncer.php#L116](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L116)
+- [CouponSyncer.php#L141](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L141)
+- [CouponSyncer.php#L155](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L155)
+- [CouponSyncer.php#L172](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L172)
+- [CouponSyncer.php#L195](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L195)
+- [CouponSyncer.php#L260](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L260)
+- [CouponSyncer.php#L309](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L309)
+- [CouponSyncer.php#L328](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L328)
+- [SyncerHooks.php#L178](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/SyncerHooks.php#L178)
## woocommerce_gla_deleted_promotions
@@ -259,7 +259,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L322](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L322)
+- [CouponSyncer.php#L322](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L322)
## woocommerce_gla_dimension_unit
@@ -267,7 +267,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L430](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L430)
+- [WCProductAdapter.php#L430](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L430)
## woocommerce_gla_disable_gtag_tracking
@@ -275,7 +275,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [GlobalSiteTag.php#L464](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Google/GlobalSiteTag.php#L464)
+- [GlobalSiteTag.php#L464](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Google/GlobalSiteTag.php#L464)
## woocommerce_gla_enable_connection_test
@@ -283,7 +283,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ConnectionTest.php#L87](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/ConnectionTest.php#L87)
+- [ConnectionTest.php#L87](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/ConnectionTest.php#L87)
## woocommerce_gla_enable_debug_logging
@@ -291,7 +291,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [DebugLogger.php#L33](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Logging/DebugLogger.php#L33)
+- [DebugLogger.php#L33](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Logging/DebugLogger.php#L33)
## woocommerce_gla_enable_mcm
@@ -299,7 +299,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [GLAChannel.php#L75](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MultichannelMarketing/GLAChannel.php#L75)
+- [GLAChannel.php#L75](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MultichannelMarketing/GLAChannel.php#L75)
## woocommerce_gla_enable_reports
@@ -307,7 +307,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Admin.php#L271](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/Admin.php#L271)
+- [Admin.php#L271](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/Admin.php#L271)
## woocommerce_gla_error
@@ -315,23 +315,23 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L290](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L290)
-- [ProductSyncer.php#L313](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L313)
-- [ProductSyncer.php#L346](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L346)
-- [ProductSyncer.php#L361](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L361)
-- [ProductHelper.php#L350](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductHelper.php#L350)
-- [ProductHelper.php#L566](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductHelper.php#L566)
-- [ProductMetaHandler.php#L173](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductMetaHandler.php#L173)
-- [BatchProductHelper.php#L248](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/BatchProductHelper.php#L248)
-- [AttributeManager.php#L269](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/Attributes/AttributeManager.php#L269)
-- [PHPView.php#L136](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/View/PHPView.php#L136)
-- [PHPView.php#L164](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/View/PHPView.php#L164)
-- [PHPView.php#L208](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/View/PHPView.php#L208)
-- [ProductMetaQueryHelper.php#L139](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/DB/ProductMetaQueryHelper.php#L139)
-- [CouponSyncer.php#L410](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L410)
-- [CouponSyncer.php#L448](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L448)
-- [CouponSyncer.php#L466](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L466)
-- [CouponMetaHandler.php#L220](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponMetaHandler.php#L220)
+- [ProductSyncer.php#L290](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L290)
+- [ProductSyncer.php#L313](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L313)
+- [ProductSyncer.php#L346](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L346)
+- [ProductSyncer.php#L361](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L361)
+- [ProductHelper.php#L374](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductHelper.php#L374)
+- [ProductHelper.php#L590](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductHelper.php#L590)
+- [ProductMetaHandler.php#L173](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductMetaHandler.php#L173)
+- [BatchProductHelper.php#L248](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/BatchProductHelper.php#L248)
+- [AttributeManager.php#L269](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/Attributes/AttributeManager.php#L269)
+- [PHPView.php#L136](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/View/PHPView.php#L136)
+- [PHPView.php#L164](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/View/PHPView.php#L164)
+- [PHPView.php#L208](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/View/PHPView.php#L208)
+- [ProductMetaQueryHelper.php#L139](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/DB/ProductMetaQueryHelper.php#L139)
+- [CouponSyncer.php#L410](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L410)
+- [CouponSyncer.php#L448](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L448)
+- [CouponSyncer.php#L466](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L466)
+- [CouponMetaHandler.php#L220](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponMetaHandler.php#L220)
## woocommerce_gla_exception
@@ -339,27 +339,29 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ScriptWithBuiltDependenciesAsset.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Assets/ScriptWithBuiltDependenciesAsset.php#L66)
-- [GoogleServiceProvider.php#L232](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Internal/DependencyManagement/GoogleServiceProvider.php#L232)
-- [ProductSyncer.php#L134](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L134)
-- [ProductSyncer.php#L220](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L220)
-- [PHPView.php#L87](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/View/PHPView.php#L87)
-- [WooCommercePreOrders.php#L111](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Integration/WooCommercePreOrders.php#L111)
-- [WooCommercePreOrders.php#L131](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Integration/WooCommercePreOrders.php#L131)
-- [ProductVisibilityController.php#L193](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/ProductVisibilityController.php#L193)
-- [SettingsSyncController.php#L96](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/SettingsSyncController.php#L96)
-- [ContactInformationController.php#L242](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/ContactInformationController.php#L242)
-- [Connection.php#L95](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Connection.php#L95)
-- [PluginUpdate.php#L75](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/Update/PluginUpdate.php#L75)
-- [DateTime.php#L44](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/Input/DateTime.php#L44)
-- [DateTime.php#L80](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/Input/DateTime.php#L80)
-- [ChannelVisibilityMetaBox.php#L176](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/MetaBox/ChannelVisibilityMetaBox.php#L176)
-- [CouponChannelVisibilityMetaBox.php#L197](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/MetaBox/CouponChannelVisibilityMetaBox.php#L197)
-- [ClearProductStatsCache.php#L61](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Event/ClearProductStatsCache.php#L61)
-- [NoteInitializer.php#L74](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Notes/NoteInitializer.php#L74)
-- [NoteInitializer.php#L116](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Notes/NoteInitializer.php#L116)
-- [CouponSyncer.php#L203](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L203)
-- [CouponSyncer.php#L293](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L293)
+- [ScriptWithBuiltDependenciesAsset.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Assets/ScriptWithBuiltDependenciesAsset.php#L66)
+- [GoogleServiceProvider.php#L234](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Internal/DependencyManagement/GoogleServiceProvider.php#L234)
+- [GoogleServiceProvider.php#L244](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Internal/DependencyManagement/GoogleServiceProvider.php#L244)
+- [ProductSyncer.php#L134](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L134)
+- [ProductSyncer.php#L220](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L220)
+- [ProductHelper.php#L256](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductHelper.php#L256)
+- [PHPView.php#L87](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/View/PHPView.php#L87)
+- [WooCommercePreOrders.php#L111](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Integration/WooCommercePreOrders.php#L111)
+- [WooCommercePreOrders.php#L131](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Integration/WooCommercePreOrders.php#L131)
+- [ProductVisibilityController.php#L193](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/ProductVisibilityController.php#L193)
+- [SettingsSyncController.php#L96](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/SettingsSyncController.php#L96)
+- [ContactInformationController.php#L242](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/ContactInformationController.php#L242)
+- [Connection.php#L95](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Connection.php#L95)
+- [PluginUpdate.php#L75](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/Update/PluginUpdate.php#L75)
+- [DateTime.php#L44](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/Input/DateTime.php#L44)
+- [DateTime.php#L80](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/Input/DateTime.php#L80)
+- [ChannelVisibilityMetaBox.php#L176](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/MetaBox/ChannelVisibilityMetaBox.php#L176)
+- [CouponChannelVisibilityMetaBox.php#L197](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/MetaBox/CouponChannelVisibilityMetaBox.php#L197)
+- [ClearProductStatsCache.php#L61](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Event/ClearProductStatsCache.php#L61)
+- [NoteInitializer.php#L74](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Notes/NoteInitializer.php#L74)
+- [NoteInitializer.php#L116](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Notes/NoteInitializer.php#L116)
+- [CouponSyncer.php#L203](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L203)
+- [CouponSyncer.php#L293](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L293)
## woocommerce_gla_force_run_install
@@ -367,7 +369,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Installer.php#L82](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Installer.php#L82)
+- [Installer.php#L82](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Installer.php#L82)
## woocommerce_gla_get_google_product_offer_id
@@ -375,7 +377,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L283](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L283)
+- [WCProductAdapter.php#L283](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L283)
## woocommerce_gla_get_sync_ready_products_filter
@@ -383,7 +385,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductFilter.php#L61](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductFilter.php#L61)
+- [ProductFilter.php#L61](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductFilter.php#L61)
## woocommerce_gla_get_sync_ready_products_pre_filter
@@ -391,7 +393,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductFilter.php#L47](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductFilter.php#L47)
+- [ProductFilter.php#L47](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductFilter.php#L47)
## woocommerce_gla_get_wc_product_id
@@ -399,7 +401,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductHelper.php#L277](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductHelper.php#L277)
+- [ProductHelper.php#L301](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductHelper.php#L301)
## woocommerce_gla_guzzle_client_exception
@@ -407,20 +409,20 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [GoogleServiceProvider.php#L256](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Internal/DependencyManagement/GoogleServiceProvider.php#L256)
-- [Connection.php#L70](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Connection.php#L70)
-- [Connection.php#L91](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Connection.php#L91)
-- [Connection.php#L126](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Connection.php#L126)
-- [Middleware.php#L80](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L80)
-- [Middleware.php#L178](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L178)
-- [Middleware.php#L228](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L228)
-- [Middleware.php#L273](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L273)
-- [Middleware.php#L344](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L344)
-- [Middleware.php#L394](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L394)
-- [Middleware.php#L418](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L418)
-- [Middleware.php#L452](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L452)
-- [Middleware.php#L552](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L552)
-- [Middleware.php#L611](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L611)
+- [GoogleServiceProvider.php#L263](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Internal/DependencyManagement/GoogleServiceProvider.php#L263)
+- [Connection.php#L70](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Connection.php#L70)
+- [Connection.php#L91](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Connection.php#L91)
+- [Connection.php#L126](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Connection.php#L126)
+- [Middleware.php#L80](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L80)
+- [Middleware.php#L178](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L178)
+- [Middleware.php#L228](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L228)
+- [Middleware.php#L273](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L273)
+- [Middleware.php#L344](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L344)
+- [Middleware.php#L394](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L394)
+- [Middleware.php#L418](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L418)
+- [Middleware.php#L452](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L452)
+- [Middleware.php#L552](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L552)
+- [Middleware.php#L611](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L611)
## woocommerce_gla_guzzle_invalid_response
@@ -428,15 +430,15 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Connection.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Connection.php#L66)
-- [Connection.php#L121](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Connection.php#L121)
-- [Middleware.php#L159](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L159)
-- [Middleware.php#L223](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L223)
-- [Middleware.php#L267](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L267)
-- [Middleware.php#L339](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L339)
-- [Middleware.php#L389](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L389)
-- [Middleware.php#L548](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L548)
-- [Middleware.php#L599](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L599)
+- [Connection.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Connection.php#L66)
+- [Connection.php#L121](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Connection.php#L121)
+- [Middleware.php#L159](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L159)
+- [Middleware.php#L223](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L223)
+- [Middleware.php#L267](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L267)
+- [Middleware.php#L339](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L339)
+- [Middleware.php#L389](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L389)
+- [Middleware.php#L548](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L548)
+- [Middleware.php#L599](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L599)
## woocommerce_gla_handle_shipping_method_to_rates
@@ -444,7 +446,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ZoneMethodsParser.php#L106](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Shipping/ZoneMethodsParser.php#L106)
+- [ZoneMethodsParser.php#L106](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Shipping/ZoneMethodsParser.php#L106)
## woocommerce_gla_hidden_coupon_types
@@ -452,7 +454,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L379](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L379)
+- [CouponSyncer.php#L379](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L379)
## woocommerce_gla_job_failure_rate_threshold
@@ -460,7 +462,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ActionSchedulerJobMonitor.php#L186](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/ActionSchedulerJobMonitor.php#L186)
+- [ActionSchedulerJobMonitor.php#L186](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/ActionSchedulerJobMonitor.php#L186)
## woocommerce_gla_job_failure_timeframe
@@ -468,7 +470,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ActionSchedulerJobMonitor.php#L195](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Jobs/ActionSchedulerJobMonitor.php#L195)
+- [ActionSchedulerJobMonitor.php#L195](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Jobs/ActionSchedulerJobMonitor.php#L195)
## woocommerce_gla_mapping_rules_change
@@ -476,9 +478,9 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AttributeMappingRulesController.php#L143](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/AttributeMapping/AttributeMappingRulesController.php#L143)
-- [AttributeMappingRulesController.php#L166](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/AttributeMapping/AttributeMappingRulesController.php#L166)
-- [AttributeMappingRulesController.php#L188](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/AttributeMapping/AttributeMappingRulesController.php#L188)
+- [AttributeMappingRulesController.php#L143](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/AttributeMapping/AttributeMappingRulesController.php#L143)
+- [AttributeMappingRulesController.php#L166](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/AttributeMapping/AttributeMappingRulesController.php#L166)
+- [AttributeMappingRulesController.php#L188](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/AttributeMapping/AttributeMappingRulesController.php#L188)
## woocommerce_gla_mc_account_review_lifetime
@@ -486,7 +488,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [RequestReviewStatuses.php#L146](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Google/RequestReviewStatuses.php#L146)
+- [RequestReviewStatuses.php#L146](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Google/RequestReviewStatuses.php#L146)
## woocommerce_gla_mc_client_exception
@@ -494,14 +496,14 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Merchant.php#L92](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L92)
-- [Merchant.php#L140](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L140)
-- [Merchant.php#L172](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L172)
-- [Merchant.php#L191](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L191)
-- [Merchant.php#L247](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L247)
-- [Merchant.php#L292](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L292)
-- [Merchant.php#L354](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L354)
-- [MerchantReport.php#L95](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/MerchantReport.php#L95)
+- [Merchant.php#L92](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L92)
+- [Merchant.php#L140](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L140)
+- [Merchant.php#L172](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L172)
+- [Merchant.php#L191](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L191)
+- [Merchant.php#L247](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L247)
+- [Merchant.php#L292](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L292)
+- [Merchant.php#L354](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L354)
+- [MerchantReport.php#L95](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/MerchantReport.php#L95)
## woocommerce_gla_mc_settings_sync
@@ -509,7 +511,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [SettingsSyncController.php#L69](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/SettingsSyncController.php#L69)
+- [SettingsSyncController.php#L69](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/SettingsSyncController.php#L69)
## woocommerce_gla_mc_status_lifetime
@@ -517,7 +519,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [MerchantStatuses.php#L778](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantStatuses.php#L778)
+- [MerchantStatuses.php#L778](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantStatuses.php#L778)
## woocommerce_gla_merchant_issue_override
@@ -525,7 +527,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [IssuesController.php#L86](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/IssuesController.php#L86)
+- [IssuesController.php#L86](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/IssuesController.php#L86)
## woocommerce_gla_merchant_status_google_ids_chunk
@@ -533,7 +535,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [MerchantStatuses.php#L191](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantStatuses.php#L191)
+- [MerchantStatuses.php#L191](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantStatuses.php#L191)
## woocommerce_gla_merchant_status_presync_issues_chunk
@@ -541,7 +543,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [MerchantStatuses.php#L531](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantStatuses.php#L531)
+- [MerchantStatuses.php#L531](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantStatuses.php#L531)
## woocommerce_gla_options_deleted_
@@ -549,7 +551,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Options.php#L103](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Options/Options.php#L103)
+- [Options.php#L103](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Options/Options.php#L103)
## woocommerce_gla_options_updated_
@@ -557,8 +559,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Options.php#L65](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Options/Options.php#L65)
-- [Options.php#L85](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Options/Options.php#L85)
+- [Options.php#L65](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Options/Options.php#L65)
+- [Options.php#L85](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Options/Options.php#L85)
## woocommerce_gla_prepared_response_->GET_ROUTE_NAME
@@ -566,7 +568,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [BaseController.php#L160](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/BaseController.php#L160)
+- [BaseController.php#L160](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/BaseController.php#L160)
## woocommerce_gla_product_attribute_types
@@ -574,7 +576,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AttributeManager.php#L243](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/Attributes/AttributeManager.php#L243)
+- [AttributeManager.php#L243](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/Attributes/AttributeManager.php#L243)
## woocommerce_gla_product_attribute_value_
@@ -582,8 +584,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L909](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L909)
-- [WCProductAdapter.php#L960](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L960)
+- [WCProductAdapter.php#L909](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L909)
+- [WCProductAdapter.php#L960](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L960)
## woocommerce_gla_product_attribute_value_description
@@ -591,7 +593,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L351](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L351)
+- [WCProductAdapter.php#L351](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L351)
## woocommerce_gla_product_attribute_value_options_::get_id
@@ -599,7 +601,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AttributesForm.php#L108](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Admin/Product/Attributes/AttributesForm.php#L108)
+- [AttributesForm.php#L127](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Admin/Product/Attributes/AttributesForm.php#L127)
## woocommerce_gla_product_attribute_value_price
@@ -607,7 +609,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L633](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L633)
+- [WCProductAdapter.php#L633](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L633)
## woocommerce_gla_product_attribute_value_sale_price
@@ -615,7 +617,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L685](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L685)
+- [WCProductAdapter.php#L685](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L685)
## woocommerce_gla_product_attribute_values
@@ -623,7 +625,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L166](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L166)
+- [WCProductAdapter.php#L166](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L166)
## woocommerce_gla_product_description_apply_shortcodes
@@ -631,7 +633,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L320](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L320)
+- [WCProductAdapter.php#L320](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L320)
## woocommerce_gla_product_property_value_is_virtual
@@ -639,7 +641,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L775](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L775)
+- [WCProductAdapter.php#L775](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L775)
## woocommerce_gla_product_query_args
@@ -647,7 +649,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductRepository.php#L365](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductRepository.php#L365)
+- [ProductRepository.php#L365](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductRepository.php#L365)
## woocommerce_gla_products_delete_retry_on_failure
@@ -655,7 +657,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L342](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L342)
+- [ProductSyncer.php#L342](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L342)
## woocommerce_gla_products_update_retry_on_failure
@@ -663,7 +665,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L286](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L286)
+- [ProductSyncer.php#L286](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L286)
## woocommerce_gla_ready_for_syncing
@@ -671,7 +673,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [MerchantCenterService.php#L118](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/MerchantCenterService.php#L118)
+- [MerchantCenterService.php#L118](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/MerchantCenterService.php#L118)
## woocommerce_gla_request_review_failure
@@ -679,9 +681,9 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [RequestReviewController.php#L110](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/RequestReviewController.php#L110)
-- [RequestReviewController.php#L122](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/RequestReviewController.php#L122)
-- [Middleware.php#L592](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L592)
+- [RequestReviewController.php#L110](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/RequestReviewController.php#L110)
+- [RequestReviewController.php#L122](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/RequestReviewController.php#L122)
+- [Middleware.php#L592](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L592)
## woocommerce_gla_request_review_response
@@ -689,7 +691,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Middleware.php#L545](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L545)
+- [Middleware.php#L545](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L545)
## woocommerce_gla_retry_delete_coupons
@@ -697,7 +699,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L443](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L443)
+- [CouponSyncer.php#L443](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L443)
## woocommerce_gla_retry_update_coupons
@@ -705,7 +707,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L405](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L405)
+- [CouponSyncer.php#L405](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L405)
## woocommerce_gla_site_claim_failure
@@ -713,10 +715,10 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Middleware.php#L268](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L268)
-- [Middleware.php#L274](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L274)
-- [Merchant.php#L93](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L93)
-- [AccountService.php#L365](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/AccountService.php#L365)
+- [Middleware.php#L268](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L268)
+- [Middleware.php#L274](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L274)
+- [Merchant.php#L93](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L93)
+- [AccountService.php#L365](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/AccountService.php#L365)
## woocommerce_gla_site_claim_overwrite_required
@@ -724,7 +726,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AccountService.php#L360](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/AccountService.php#L360)
+- [AccountService.php#L360](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/AccountService.php#L360)
## woocommerce_gla_site_claim_success
@@ -732,8 +734,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [Middleware.php#L263](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Middleware.php#L263)
-- [Merchant.php#L90](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/Merchant.php#L90)
+- [Middleware.php#L263](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Middleware.php#L263)
+- [Merchant.php#L90](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/Merchant.php#L90)
## woocommerce_gla_site_url
@@ -741,7 +743,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [PluginHelper.php#L189](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/PluginHelper.php#L189)
+- [PluginHelper.php#L189](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/PluginHelper.php#L189)
## woocommerce_gla_site_verify_failure
@@ -749,9 +751,9 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [SiteVerification.php#L58](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/SiteVerification.php#L58)
-- [SiteVerification.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/SiteVerification.php#L66)
-- [SiteVerification.php#L87](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/SiteVerification.php#L87)
+- [SiteVerification.php#L58](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/SiteVerification.php#L58)
+- [SiteVerification.php#L66](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/SiteVerification.php#L66)
+- [SiteVerification.php#L87](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/SiteVerification.php#L87)
## woocommerce_gla_site_verify_success
@@ -759,7 +761,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [SiteVerification.php#L85](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/SiteVerification.php#L85)
+- [SiteVerification.php#L85](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/SiteVerification.php#L85)
## woocommerce_gla_supported_coupon_types
@@ -767,7 +769,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L366](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L366)
+- [CouponSyncer.php#L366](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L366)
## woocommerce_gla_supported_product_types
@@ -775,7 +777,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductSyncer.php#L264](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductSyncer.php#L264)
+- [ProductSyncer.php#L264](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductSyncer.php#L264)
## woocommerce_gla_sv_client_exception
@@ -783,8 +785,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [SiteVerification.php#L120](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/SiteVerification.php#L120)
-- [SiteVerification.php#L162](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Google/SiteVerification.php#L162)
+- [SiteVerification.php#L120](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/SiteVerification.php#L120)
+- [SiteVerification.php#L162](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Google/SiteVerification.php#L162)
## woocommerce_gla_tax_excluded
@@ -792,7 +794,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L594](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L594)
+- [WCProductAdapter.php#L594](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L594)
## woocommerce_gla_track_event
@@ -800,11 +802,11 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [SetupCompleteController.php#L75](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/Ads/SetupCompleteController.php#L75)
-- [CampaignController.php#L151](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/Ads/CampaignController.php#L151)
-- [CampaignController.php#L229](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/Ads/CampaignController.php#L229)
-- [CampaignController.php#L267](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/Ads/CampaignController.php#L267)
-- [SettingsSyncController.php#L83](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/API/Site/Controllers/MerchantCenter/SettingsSyncController.php#L83)
+- [SetupCompleteController.php#L75](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/Ads/SetupCompleteController.php#L75)
+- [CampaignController.php#L151](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/Ads/CampaignController.php#L151)
+- [CampaignController.php#L229](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/Ads/CampaignController.php#L229)
+- [CampaignController.php#L267](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/Ads/CampaignController.php#L267)
+- [SettingsSyncController.php#L83](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/API/Site/Controllers/MerchantCenter/SettingsSyncController.php#L83)
## woocommerce_gla_updated_coupon
@@ -812,7 +814,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [CouponSyncer.php#L169](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Coupon/CouponSyncer.php#L169)
+- [CouponSyncer.php#L169](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Coupon/CouponSyncer.php#L169)
## woocommerce_gla_url_switch_required
@@ -820,7 +822,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AccountService.php#L445](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/AccountService.php#L445)
+- [AccountService.php#L445](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/AccountService.php#L445)
## woocommerce_gla_url_switch_success
@@ -828,7 +830,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [AccountService.php#L468](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/MerchantCenter/AccountService.php#L468)
+- [AccountService.php#L468](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/MerchantCenter/AccountService.php#L468)
## woocommerce_gla_use_short_description
@@ -836,7 +838,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L297](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L297)
+- [WCProductAdapter.php#L297](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L297)
## woocommerce_gla_wcs_url
@@ -844,8 +846,8 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [PluginHelper.php#L174](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/PluginHelper.php#L174)
-- [PluginHelper.php#L178](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/PluginHelper.php#L178)
+- [PluginHelper.php#L174](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/PluginHelper.php#L174)
+- [PluginHelper.php#L178](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/PluginHelper.php#L178)
## woocommerce_gla_weight_unit
@@ -853,7 +855,7 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [WCProductAdapter.php#L431](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/WCProductAdapter.php#L431)
+- [WCProductAdapter.php#L431](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/WCProductAdapter.php#L431)
## woocommerce_hide_invisible_variations
@@ -861,5 +863,5 @@ A list of hooks, e.g. `actions` and `filters`, that are defined or used in this
**Used in**:
-- [ProductHelper.php#L365](https://github.com/woocommerce/google-listings-and-ads/blob/08665c12ac59f20e4862c879e50e88a251cd33d6/src/Product/ProductHelper.php#L365)
+- [ProductHelper.php#L389](https://github.com/woocommerce/google-listings-and-ads/blob/161a91c04f81e349b1504eafb1ec9526a92ccf53/src/Product/ProductHelper.php#L389)
diff --git a/src/Internal/DependencyManagement/CoreServiceProvider.php b/src/Internal/DependencyManagement/CoreServiceProvider.php
index 07005962fd..d34f9942f1 100644
--- a/src/Internal/DependencyManagement/CoreServiceProvider.php
+++ b/src/Internal/DependencyManagement/CoreServiceProvider.php
@@ -14,6 +14,8 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\MetaBox\MetaBoxInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\AttributesTab;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\VariationsAttributes;
+use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\ChannelVisibilityBlock;
+use Automattic\WooCommerce\GoogleListingsAndAds\Admin\ProductBlocksService;
use Automattic\WooCommerce\GoogleListingsAndAds\Ads\AccountService as AdsAccountService;
use Automattic\WooCommerce\GoogleListingsAndAds\Ads\AdsAwareInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Ads\AdsService;
@@ -322,6 +324,10 @@ function ( ...$arguments ) {
$this->conditionally_share_with_tags( AttributesTab::class, Admin::class, AttributeManager::class, MerchantCenterService::class );
$this->conditionally_share_with_tags( VariationsAttributes::class, Admin::class, AttributeManager::class, MerchantCenterService::class );
+ // Product Block Editor
+ $this->share_with_tags( ChannelVisibilityBlock::class, ProductHelper::class, MerchantCenterService::class );
+ $this->conditionally_share_with_tags( ProductBlocksService::class, AssetsHandlerInterface::class, ChannelVisibilityBlock::class, AttributeManager::class, MerchantCenterService::class );
+
$this->share_with_tags( MerchantAccountState::class );
$this->share_with_tags( MerchantStatuses::class );
$this->share_with_tags( PhoneVerification::class, Merchant::class, WP::class, ISOUtility::class );
diff --git a/src/Internal/DependencyManagement/GoogleServiceProvider.php b/src/Internal/DependencyManagement/GoogleServiceProvider.php
index e916c862ca..6efca7b665 100644
--- a/src/Internal/DependencyManagement/GoogleServiceProvider.php
+++ b/src/Internal/DependencyManagement/GoogleServiceProvider.php
@@ -134,12 +134,12 @@ protected function register_guzzle() {
$handler_stack = HandlerStack::create();
$handler_stack->remove( 'http_errors' );
$handler_stack->push( $this->error_handler(), 'http_errors' );
- $handler_stack->push( $this->add_auth_header() );
+ $handler_stack->push( $this->add_auth_header(), 'auth_header' );
$handler_stack->push( $this->add_plugin_version_header(), 'plugin_version_header' );
// Override endpoint URL if we are using http locally.
if ( 0 === strpos( $this->get_connect_server_url_root()->getValue(), 'http://' ) ) {
- $handler_stack->push( $this->override_http_url() );
+ $handler_stack->push( $this->override_http_url(), 'override_http_url' );
}
return new GuzzleClient( [ 'handler' => $handler_stack ] );
@@ -228,17 +228,24 @@ function ( ResponseInterface $response ) use ( $request ) {
* @throws AccountReconnect When an account must be reconnected.
*/
protected function handle_unauthorized_error( RequestInterface $request, ResponseInterface $response ) {
- // Log original exception before throwing reconnect exception.
- do_action( 'woocommerce_gla_exception', RequestException::create( $request, $response ), __METHOD__ );
-
$auth_header = $response->getHeader( 'www-authenticate' )[0] ?? '';
if ( 0 === strpos( $auth_header, 'X_JP_Auth' ) ) {
+ // Log original exception before throwing reconnect exception.
+ do_action( 'woocommerce_gla_exception', RequestException::create( $request, $response ), __METHOD__ );
+
$this->set_jetpack_connected( false );
throw AccountReconnect::jetpack_disconnected();
}
- $this->set_google_disconnected();
- throw AccountReconnect::google_disconnected();
+ // Exclude listing customers as it will handle it's own unauthorized errors.
+ $path = $request->getUri()->getPath();
+ if ( false === strpos( $path, 'customers:listAccessibleCustomers' ) ) {
+ // Log original exception before throwing reconnect exception.
+ do_action( 'woocommerce_gla_exception', RequestException::create( $request, $response ), __METHOD__ );
+
+ $this->set_google_disconnected();
+ throw AccountReconnect::google_disconnected();
+ }
}
/**
@@ -271,7 +278,7 @@ protected function add_auth_header(): callable {
*
* @return callable
*/
- public function add_plugin_version_header(): callable {
+ protected function add_plugin_version_header(): callable {
return function ( callable $handler ) {
return function ( RequestInterface $request, array $options ) use ( $handler ) {
$request = $request->withHeader( 'x-client-name', $this->get_client_name() )
diff --git a/src/Product/ProductHelper.php b/src/Product/ProductHelper.php
index 79ebeeaddc..f2ee8ad6e7 100644
--- a/src/Product/ProductHelper.php
+++ b/src/Product/ProductHelper.php
@@ -236,6 +236,30 @@ protected function update_empty_visibility( WC_Product $product ): void {
}
}
+ /**
+ * Update a product's channel visibility.
+ *
+ * @param WC_Product $product
+ * @param string $visibility
+ */
+ public function update_channel_visibility( WC_Product $product, string $visibility ): void {
+ try {
+ $product = $this->maybe_swap_for_parent( $product );
+ } catch ( InvalidValue $exception ) {
+ // The error has been logged within the call of maybe_swap_for_parent
+ return;
+ }
+
+ try {
+ $visibility = ChannelVisibility::cast( $visibility )->get();
+ } catch ( InvalidValue $exception ) {
+ do_action( 'woocommerce_gla_exception', $exception, __METHOD__ );
+ return;
+ }
+
+ $this->meta_handler->update_visibility( $product, $visibility );
+ }
+
/**
* @param WC_Product $product
*
diff --git a/src/Value/ChannelVisibility.php b/src/Value/ChannelVisibility.php
index 3e6f12c0b0..0b50475ba6 100644
--- a/src/Value/ChannelVisibility.php
+++ b/src/Value/ChannelVisibility.php
@@ -64,6 +64,18 @@ public static function cast( $value ): ChannelVisibility {
return new self( $value );
}
+ /**
+ * Return an array of the values with option labels.
+ *
+ * @return array
+ */
+ public static function get_value_options(): array {
+ return [
+ self::SYNC_AND_SHOW => __( 'Sync and show', 'google-listings-and-ads' ),
+ self::DONT_SYNC_AND_SHOW => __( 'Don\'t Sync and show', 'google-listings-and-ads' ),
+ ];
+ }
+
/**
* @return string
*/
diff --git a/tests/Unit/API/ClientTest.php b/tests/Unit/API/ClientTest.php
index 66c71a679b..ecf35ddc1b 100644
--- a/tests/Unit/API/ClientTest.php
+++ b/tests/Unit/API/ClientTest.php
@@ -3,11 +3,23 @@
namespace Automattic\WooCommerce\GoogleListingsAndAds\Tests\Unit\API;
-use Automattic\WooCommerce\GoogleListingsAndAds\Tests\Framework\ContainerAwareUnitTest;
-use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Client;
+use Automattic\Jetpack\Connection\Manager;
+use Automattic\Jetpack\Connection\Tokens;
+use Automattic\WooCommerce\GoogleListingsAndAds\Exception\AccountReconnect;
+use Automattic\WooCommerce\GoogleListingsAndAds\Google\Ads\GoogleAdsClient;
use Automattic\WooCommerce\GoogleListingsAndAds\Internal\DependencyManagement\GoogleServiceProvider;
+use Automattic\WooCommerce\GoogleListingsAndAds\Notes\ReconnectWordPress;
+use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
+use Automattic\WooCommerce\GoogleListingsAndAds\Tests\Framework\UnitTest;
+use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Client;
+use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Exception\RequestException;
+use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Handler\MockHandler;
+use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\HandlerStack;
use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Psr7\Request;
+use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\GuzzleHttp\Psr7\Response;
+use Automattic\WooCommerce\GoogleListingsAndAds\Vendor\League\Container\Container;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
+use ReflectionMethod;
defined( 'ABSPATH' ) || exit;
@@ -16,17 +28,205 @@
*
* @package Automattic\WooCommerce\GoogleListingsAndAds\Tests\Unit\API
*/
-class ClientTest extends ContainerAwareUnitTest {
+class ClientTest extends UnitTest {
use PluginHelper;
+ /** @var MockObject|Manager $manager */
+ protected $manager;
+
+ /** @var MockObject|ReconnectWordPress $note */
+ protected $note;
+
+ /** @var MockObject|OptionsInterface $options */
+ protected $options;
+
/**
- * Confirm that the client handler stack includes the `plugin_version_header`
- *
- * @return void
+ * @var Container $container
+ */
+ protected $container;
+
+ /**
+ * @var Provider $provider
+ */
+ protected $provider;
+
+ /**
+ * Runs before each test is executed.
+ */
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->manager = $this->createMock( Manager::class );
+ $this->note = $this->createMock( ReconnectWordPress::class );
+ $this->options = $this->createMock( OptionsInterface::class );
+
+ $this->container = new Container();
+ $this->container->share( Manager::class, $this->manager );
+ $this->container->share( ReconnectWordPress::class, $this->note );
+ $this->container->share( OptionsInterface::class, $this->options );
+
+ $this->provider = new GoogleServiceProvider();
+ $this->provider->setLeagueContainer( $this->container );
+ }
+
+ /**
+ * Confirm that the client handler stack includes the following handlers:
+ * - `http_errors`
+ * - `auth_header`
+ * - `plugin_version_header`
+ */
+ public function test_handlers_in_stack(): void {
+ // Get string representation of the handler stack (fetches handlers from main container).
+ $handlers = (string) woogle_get_container()->get( Client::class )->getConfig( 'handler' );
+
+ $this->assertStringContainsString( 'http_errors', $handlers );
+ $this->assertStringContainsString( 'auth_header', $handlers );
+ $this->assertStringContainsString( 'plugin_version_header', $handlers );
+
+ // By default we should not have a http URL.
+ $this->assertStringNotContainsString( 'override_http_url', $handlers );
+ }
+
+ /**
+ * Confirm that service classes are available from the container after registering.
+ */
+ public function test_registering_provided_services() {
+ $this->provider->register();
+
+ $this->assertNotNull( $this->container->get( GoogleAdsClient::class ) );
+ }
+
+ /**
+ * Confirm that the error handler does not intervene for regular responses.
+ */
+ public function test_error_handler_regular_response() {
+ $mocked_responses = [
+ new Response( 200, [], 'response' ),
+ ];
+
+ $client = $this->mock_client_with_handler( 'error_handler', $mocked_responses );
+ $response = $client->request( 'GET', 'https://testing.local' );
+
+ $this->assertEquals( 200, $response->getStatusCode() );
+ $this->assertEquals( 'response', $response->getBody() );
+ }
+
+ /**
+ * Confirm that the error handler throws an error to reconnect Jetpack when the header is not included.
+ */
+ public function test_error_handler_reconnect_jetpack() {
+ $mocked_responses = [
+ new Response( 401, [ 'www-authenticate' => 'X_JP_Auth' ], 'error' ),
+ ];
+
+ // Set Jetpack as previously connected to trigger change note.
+ $this->options->expects( $this->once() )->method( 'get' )->with( OptionsInterface::JETPACK_CONNECTED )->willReturn( true );
+
+ // Expect Jetpack to be marked as disconnected.
+ $this->options->expects( $this->once() )->method( 'update' )->with( OptionsInterface::JETPACK_CONNECTED, false );
+
+ // Expect ReconnectWordPress note to be triggered.
+ $this->note->expects( $this->once() )->method( 'get_entry' );
+
+ $this->expectException( AccountReconnect::class );
+ $this->expectExceptionMessage( AccountReconnect::jetpack_disconnected()->getMessage() );
+
+ $client = $this->mock_client_with_handler( 'error_handler', $mocked_responses );
+ $response = $client->request( 'GET', 'https://testing.local' );
+ }
+
+ /**
+ * Confirm that the error handler throws an error to reconnect Google with a permission denied status.
+ */
+ public function test_error_handler_reconnect_google() {
+ $mocked_responses = [
+ new Response( 401, [], 'error' ),
+ ];
+
+ // Expect Google to be marked as disconnected.
+ $this->options->expects( $this->once() )->method( 'update' )->with( OptionsInterface::GOOGLE_CONNECTED, false );
+
+ $this->expectException( AccountReconnect::class );
+ $this->expectExceptionMessage( AccountReconnect::google_disconnected()->getMessage() );
+
+ $client = $this->mock_client_with_handler( 'error_handler', $mocked_responses );
+ $response = $client->request( 'GET', 'https://testing.local' );
+ }
+
+ /**
+ * Confirm that a request to listAccessibleCustomers does not return a redirect error.
+ */
+ public function test_error_handler_list_accessible_customers() {
+ $mocked_responses = [
+ new Response( 401, [], 'error' ),
+ ];
+
+ $this->expectException( RequestException::class );
+ $this->expectExceptionMessage( 'error' );
+
+ $client = $this->mock_client_with_handler( 'error_handler', $mocked_responses );
+ $response = $client->request( 'GET', 'https://testing.local/google/google-ads/customers:listAccessibleCustomers' );
+ }
+
+ /**
+ * Confirm that the error handler throws a generic error when the status code is higher than 400 except a 401.
*/
- public function test_plugin_version_header_in_handler_stack(): void {
- // Get string representation of the handler stack and confirm if `plugin_version_header` is contained within it.
- $this->assertStringContainsString( 'plugin_version_header', (string) $this->container->get( Client::class )->getConfig( 'handler' ) );
+ public function test_error_handler_generic_error_response() {
+ $mocked_responses = [
+ new Response( 404, [], 'not found' ),
+ ];
+
+ $this->expectException( RequestException::class );
+ $this->expectExceptionMessage( 'not found' );
+
+ $client = $this->mock_client_with_handler( 'error_handler', $mocked_responses );
+ $response = $client->request( 'GET', 'https://testing.local' );
+ }
+
+ /**
+ * Confirm that an auth header is added to the request.
+ */
+ public function test_add_auth_header() {
+ $request = new Request( 'GET', 'https://testing.local' );
+
+ // Mock JetPack tokens.
+ $tokens = $this->createMock( Tokens::class );
+ $tokens->method( 'get_access_token' )->willReturn(
+ (object) [
+ 'secret' => 'secret.token',
+ 'external_user_id' => 123,
+ ]
+ );
+ $this->manager->expects( $this->once() )->method( 'get_tokens' )->willReturn( $tokens );
+
+ // Set Jetpack as previously disconnected to trigger removal of note.
+ $this->options->expects( $this->once() )->method( 'get' )->with( OptionsInterface::JETPACK_CONNECTED )->willReturn( false );
+
+ // Expect ReconnectWordPress note to be removed.
+ $this->note->expects( $this->once() )->method( 'delete' );
+
+ $this->invoke_handler( 'add_auth_header' )(
+ function ( $request, $options ) {
+ $this->assertStringStartsWith( 'X_JP_Auth token=', $request->getHeader( 'Authorization' )[0] );
+ }
+ )( $request, [] );
+ }
+
+ /**
+ * Confirm that an auth header fails when no token is available.
+ */
+ public function test_add_auth_header_no_token() {
+ $request = new Request( 'GET', 'https://testing.local' );
+
+ // Mock empty JetPack tokens.
+ $this->manager->expects( $this->once() )->method( 'get_tokens' )->willReturn( new Tokens() );
+
+ $this->expectException( AccountReconnect::class );
+ $this->expectExceptionMessage( AccountReconnect::jetpack_disconnected()->getMessage() );
+
+ $this->invoke_handler( 'add_auth_header' )(
+ function ( $request, $options ) {}
+ )( $request, [] );
}
/**
@@ -35,14 +235,41 @@ public function test_plugin_version_header_in_handler_stack(): void {
* @return void
*/
public function test_plugin_version_headers(): void {
- $service = new GoogleServiceProvider();
$request = new Request( 'GET', 'https://testing.local' );
- $service->add_plugin_version_header()(
+ $this->invoke_handler( 'add_plugin_version_header' )(
function ( $request, $options ) {
$this->assertEquals( $this->get_client_name(), $request->getHeader( 'x-client-name' )[0] );
$this->assertEquals( $this->get_version(), $request->getHeader( 'x-client-version' )[0] );
}
)( $request, [] );
}
+
+ /**
+ * Calls a handler function through ReflectionMethod to allow testing protected handlers.
+ *
+ * @param string $handler_function
+ * @return callable Handler callback.
+ */
+ protected function invoke_handler( string $handler_function ) {
+ $handler = new ReflectionMethod( GoogleServiceProvider::class, $handler_function );
+ $handler->setAccessible( true );
+
+ return $handler->invoke( $this->provider );
+ }
+
+ /**
+ * Returns a mock client with an individual handler attached to the stack.
+ *
+ * @param string $handler_function Handler function name to include in stack.
+ * @param array $mocked_responses List of responses to return.
+ *
+ * @return Client Mock client.
+ */
+ protected function mock_client_with_handler( string $handler_function, array $mocked_responses ) {
+ $mock = new MockHandler( $mocked_responses );
+ $handlers = HandlerStack::create( $mock );
+ $handlers->push( $this->invoke_handler( $handler_function ) );
+ return new Client( [ 'handler' => $handlers ] );
+ }
}
diff --git a/tests/Unit/API/Site/Controllers/Ads/AccountControllerTest.php b/tests/Unit/API/Site/Controllers/Ads/AccountControllerTest.php
index 7326d3dca2..3246e74710 100644
--- a/tests/Unit/API/Site/Controllers/Ads/AccountControllerTest.php
+++ b/tests/Unit/API/Site/Controllers/Ads/AccountControllerTest.php
@@ -4,6 +4,7 @@
use Automattic\WooCommerce\GoogleListingsAndAds\Ads\AccountService;
use Automattic\WooCommerce\GoogleListingsAndAds\API\Site\Controllers\Ads\AccountController;
+use Automattic\WooCommerce\GoogleListingsAndAds\Exception\AccountReconnect;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ExceptionWithResponseData;
use Automattic\WooCommerce\GoogleListingsAndAds\Tests\Framework\RESTControllerUnitTest;
use Exception;
@@ -214,4 +215,18 @@ public function test_get_billing_status() {
$this->assertEquals( self::TEST_BILLING_STATUS_DATA, $response->get_data() );
$this->assertEquals( 200, $response->get_status() );
}
+
+ /**
+ * Test a Google disconnected error since it's a dependency for a connected Ads account.
+ */
+ public function test_connected_with_google_disconnected() {
+ $this->account->expects( $this->once() )
+ ->method( 'get_accounts' )
+ ->willThrowException( AccountReconnect::google_disconnected() );
+
+ $response = $this->do_request( self::ROUTE_ACCOUNTS, 'GET' );
+ $this->assertEquals( 'GOOGLE_DISCONNECTED', $response->get_data()['code'] );
+ $this->assertEquals( 401, $response->get_data()['status'] );
+ $this->assertEquals( 401, $response->get_status() );
+ }
}
diff --git a/tests/Unit/Admin/Input/InputCollectionTest.php b/tests/Unit/Admin/Input/InputCollectionTest.php
new file mode 100644
index 0000000000..5184fbaa5e
--- /dev/null
+++ b/tests/Unit/Admin/Input/InputCollectionTest.php
@@ -0,0 +1,322 @@
+set_id( 'test-boolean-select' );
+
+ $this->assertEquals( 'select', $input->get_type() );
+ $this->assertEquals( 'google-listings-and-ads/product-select-field', $input->get_block_name() );
+
+ // BooleanSelect doesn't reflect the call of set_options method
+ $input->set_options( [] );
+
+ $this->assertEquals(
+ [
+ '' => 'Default',
+ 'yes' => 'Yes',
+ 'no' => 'No',
+ ],
+ $input->get_options()
+ );
+
+ $this->assertEquals(
+ [
+ [
+ 'label' => 'Default',
+ 'value' => '',
+ ],
+ [
+ 'label' => 'Yes',
+ 'value' => 'yes',
+ ],
+ [
+ 'label' => 'No',
+ 'value' => 'no',
+ ],
+ ],
+ $input->get_block_attributes()['options']
+ );
+
+ // Null by default
+ $this->assertNull( $input->get_view_data()['value'] );
+
+ // Convert to a 'yes' or 'no' if the data is the bool type
+ $input->set_data( true );
+ $this->assertEquals( 'yes', $input->get_view_data()['value'] );
+
+ $input->set_data( false );
+ $this->assertEquals( 'no', $input->get_view_data()['value'] );
+ }
+
+ public function test_date_time() {
+ $input = new DateTime();
+
+ $this->assertEquals( 'datetime', $input->get_type() );
+ $this->assertEquals( 'google-listings-and-ads/product-date-time-field', $input->get_block_name() );
+
+ // Null by default
+ $view_data = $input->get_view_data();
+
+ $this->assertNull( $view_data['value'] );
+ $this->assertArrayNotHasKey( 'date', $view_data );
+ $this->assertArrayNotHasKey( 'time', $view_data );
+
+ // Set date and time data with a string
+ $input->set_data( '2023-09-04T08:42:00+00:00' );
+ $view_data = $input->get_view_data();
+
+ $this->assertEquals( '2023-09-04 08:42:00', $view_data['value'] );
+ $this->assertEquals( '2023-09-04', $view_data['date'] );
+ $this->assertEquals( '08:42', $view_data['time'] );
+
+ // Set date and time data with a key-value array
+ $input->set_data(
+ [
+ 'date' => '2024-01-02',
+ 'time' => '19:27:56',
+ ]
+ );
+ $view_data = $input->get_view_data();
+
+ $this->assertEquals( '2024-01-02 19:27:56', $view_data['value'] );
+ $this->assertEquals( '2024-01-02', $view_data['date'] );
+ $this->assertEquals( '19:27', $view_data['time'] );
+ }
+
+ public function test_integer() {
+ $input = new Integer();
+ $input->set_id( 'test-integer' );
+
+ $this->assertEquals( 'integer', $input->get_type() );
+ $this->assertEquals( 'woocommerce/product-text-field', $input->get_block_name() );
+ $this->assertEquals( [ 'value' => 'number' ], $input->get_block_attributes()['type'] );
+ }
+
+ public function test_select() {
+ $input = new Select();
+ $input->set_id( 'test-select' );
+
+ $this->assertEquals( 'select', $input->get_type() );
+ $this->assertEquals( 'google-listings-and-ads/product-select-field', $input->get_block_name() );
+ $this->assertEquals( 'select short', $input->get_view_data()['class'] );
+
+ // Empty options by default
+ $this->assertEquals( [], $input->get_options() );
+ $this->assertEquals( [], $input->get_view_data()['options'] );
+ $this->assertEquals( [], $input->get_block_attributes()['options'] );
+
+ // Set and get options
+ $options = [
+ 'foo' => 'bar',
+ 'hi' => 'hello',
+ ];
+ $input->set_options( $options );
+
+ $this->assertEquals( $options, $input->get_options() );
+ $this->assertEquals( $options, $input->get_view_data()['options'] );
+ $this->assertEquals(
+ [
+ [
+ 'label' => 'bar',
+ 'value' => 'foo',
+ ],
+ [
+ 'label' => 'hello',
+ 'value' => 'hi',
+ ],
+ ],
+ $input->get_block_attributes()['options']
+ );
+ }
+
+ public function test_select_with_text_input() {
+ $input = new SelectWithTextInput();
+ $input->set_name( 'name' );
+ $input->set_id( 'test-select-with-text-input' );
+
+ $this->assertEquals( 'select-with-text-input', $input->get_type() );
+ $this->assertEquals( 'google-listings-and-ads/product-select-with-text-field', $input->get_block_name() );
+
+ // Default view data
+ $this->assertEquals(
+ [
+ 'id' => 'gla_name',
+ 'type' => 'select-with-text-input',
+ 'label' => null,
+ 'description' => null,
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_name',
+ 'is_root' => true,
+ 'children' => [
+ '_gla_select' => [
+ 'id' => 'gla_name__gla_select',
+ 'type' => 'select',
+ 'label' => null,
+ 'description' => null,
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_name[_gla_select]',
+ 'is_root' => false,
+ 'children' => [],
+ 'class' => 'select short',
+ 'options' => [
+ '_gla_custom_value' => 'Enter a custom value',
+ ],
+ ],
+ '_gla_custom_value' => [
+ 'id' => 'gla_name__gla_custom_value',
+ 'type' => 'text',
+ 'label' => 'Enter your value',
+ 'description' => null,
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_name[_gla_custom_value]',
+ 'is_root' => false,
+ 'children' => [],
+ 'wrapper_class' => 'custom-input',
+ ],
+ ],
+ 'gla_wrapper_class' => ' select-with-text-input',
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals( '_gla_custom_value', $input->get_block_attributes()['customInputValue'] );
+ $this->assertEquals(
+ [
+ [
+ 'label' => 'Enter a custom value',
+ 'value' => '_gla_custom_value',
+ ],
+ ],
+ $input->get_block_attributes()['options']
+ );
+
+ // After calling setters
+ $input
+ ->set_label( 'Hi label' )
+ ->set_description( 'Hello description' )
+ ->set_options(
+ [
+ '' => 'Default name',
+ 'use_admin_name' => 'Use admin name',
+ ]
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_name',
+ 'type' => 'select-with-text-input',
+ 'label' => 'Hi label',
+ 'description' => 'Hello description',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_name',
+ 'is_root' => true,
+ 'children' => [
+ '_gla_select' => [
+ 'id' => 'gla_name__gla_select',
+ 'type' => 'select',
+ 'label' => 'Hi label',
+ 'description' => 'Hello description',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_name[_gla_select]',
+ 'is_root' => false,
+ 'children' => [],
+ 'class' => 'select short',
+ 'options' => [
+ '_gla_custom_value' => 'Enter a custom value',
+ '' => 'Default name',
+ 'use_admin_name' => 'Use admin name',
+ ],
+ ],
+ '_gla_custom_value' => [
+ 'id' => 'gla_name__gla_custom_value',
+ 'type' => 'text',
+ 'label' => 'Enter your value',
+ 'description' => null,
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_name[_gla_custom_value]',
+ 'is_root' => false,
+ 'children' => [],
+ 'wrapper_class' => 'custom-input',
+ ],
+ ],
+ 'gla_wrapper_class' => ' select-with-text-input',
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ [
+ 'label' => 'Default name',
+ 'value' => '',
+ ],
+ [
+ 'label' => 'Use admin name',
+ 'value' => 'use_admin_name',
+ ],
+ [
+ 'label' => 'Enter a custom value',
+ 'value' => '_gla_custom_value',
+ ],
+ ],
+ $input->get_block_attributes()['options']
+ );
+
+ // Selected an option other than value from the custom text input
+ $input->set_data(
+ [
+ '_gla_select' => 'use_admin_name',
+ '_gla_custom_value' => null,
+ ]
+ );
+
+ $this->assertEquals( 'use_admin_name', $input->get_view_data()['value'] );
+ $this->assertEquals( 'use_admin_name', $input->get_view_data()['children']['_gla_select']['value'] );
+ $this->assertNull( $input->get_view_data()['children']['_gla_custom_value']['value'] );
+
+ // Selected the _gla_custom_value option and entered a value via the custom text input
+ $input->set_data(
+ [
+ '_gla_select' => 'mismatching any option',
+ '_gla_custom_value' => 'Say my name!',
+ ]
+ );
+
+ $this->assertEquals( 'Say my name!', $input->get_view_data()['value'] );
+ $this->assertEquals( '_gla_custom_value', $input->get_view_data()['children']['_gla_select']['value'] );
+ $this->assertEquals( 'Say my name!', $input->get_view_data()['children']['_gla_custom_value']['value'] );
+ }
+
+
+ public function test_text() {
+ $input = new Text();
+
+ $this->assertEquals( 'text', $input->get_type() );
+ $this->assertEquals( 'woocommerce/product-text-field', $input->get_block_name() );
+ }
+}
diff --git a/tests/Unit/Admin/Input/InputTest.php b/tests/Unit/Admin/Input/InputTest.php
new file mode 100644
index 0000000000..1277477410
--- /dev/null
+++ b/tests/Unit/Admin/Input/InputTest.php
@@ -0,0 +1,187 @@
+input = new Input( 'text', 'woocommerce/product-text-field' );
+ }
+
+ public function test_constructor_and_init_values() {
+ $input = new Input( 'integer', 'woocommerce/product-number-field' );
+
+ $this->assertEquals( 'integer', $input->get_type() );
+ $this->assertEquals( 'woocommerce/product-number-field', $input->get_block_name() );
+ }
+
+ public function test_set_id_get_id() {
+ $this->assertNull( $this->input->get_id() );
+ $this->input->set_id( 'color' );
+ $this->assertEquals( 'color', $this->input->get_id() );
+ }
+
+ public function test_set_label_get_label() {
+ $this->assertNull( $this->input->get_label() );
+ $this->input->set_label( 'Color' );
+ $this->assertEquals( 'Color', $this->input->get_label() );
+ }
+
+ public function test_set_description_get_description() {
+ $this->assertNull( $this->input->get_description() );
+ $this->input->set_description( 'Color of the product' );
+ $this->assertEquals( 'Color of the product', $this->input->get_description() );
+ }
+
+ public function test_set_value_get_value() {
+ $this->assertNull( $this->input->get_value() );
+ $this->input->set_value( 'black' );
+ $this->assertEquals( 'black', $this->input->get_value() );
+ }
+
+ public function test_get_view_id() {
+ $this->input->set_name( 'leaf' );
+ $this->assertEquals( 'gla_leaf', $this->input->get_view_id() );
+
+ // An input can have a Form parent
+ $this->input->set_id( 'leaf' );
+ $form = new Form();
+ $form->add( $this->input );
+ $this->assertEquals( 'gla__leaf', $this->input->get_view_id() );
+
+ // The depth of the tree structure of Form and Input can be > 1. Depth starts from 0
+ $nested_input = new Input( 'text', '' );
+ $nested_input->set_id( 'mid-node' );
+ $form->remove( 'leaf' );
+ $form->add( $nested_input );
+ $nested_input->add( $this->input );
+ $this->assertEquals( 'gla__mid-node_leaf', $this->input->get_view_id() );
+ }
+
+ public function test_get_view_data() {
+ $form = new Form();
+ $leaf = new Input( 'select', '' );
+
+ $this->input
+ ->set_id( 'color' )
+ ->set_label( 'Color' )
+ ->set_value( 'black' )
+ ->set_description( 'Color of the product' )
+ ->set_name( 'attr_color' );
+
+ $leaf
+ ->set_id( 'leaf' )
+ ->set_name( 'attr_leaf' );
+
+ $form->add( $this->input );
+ $this->input->add( $leaf );
+
+ $this->assertEquals(
+ [
+ // Input
+ 'id' => 'gla__color',
+ 'type' => 'text',
+ 'label' => 'Color',
+ 'value' => 'black',
+ 'description' => 'Color of the product',
+ 'desc_tip' => true,
+ // Form
+ 'name' => 'gla_[attr_color]',
+ 'is_root' => false,
+ 'children' => [
+ 'attr_leaf' => [
+ // Input
+ 'id' => 'gla__color_leaf',
+ 'type' => 'select',
+ 'label' => null,
+ 'value' => null,
+ 'description' => null,
+ 'desc_tip' => true,
+ // Form
+ 'name' => 'gla_[attr_color][attr_leaf]',
+ 'is_root' => false,
+ 'children' => [],
+ ],
+ ],
+ ],
+ $this->input->get_view_data()
+ );
+ }
+
+ public function test_set_block_attribute_get_block_attributes() {
+ $this->input->set_id( 'color' );
+
+ $this->assertEquals(
+ [
+ 'property' => 'meta_data._wc_gla_color',
+ 'label' => null,
+ 'tooltip' => null,
+ ],
+ $this->input->get_block_attributes()
+ );
+
+ $this->input->set_label( 'Color' );
+ $this->input->set_description( 'Color of the product' );
+
+ $this->assertEquals(
+ [
+ 'property' => 'meta_data._wc_gla_color',
+ 'label' => 'Color',
+ 'tooltip' => 'Color of the product',
+ ],
+ $this->input->get_block_attributes()
+ );
+
+ $this->input->set_block_attribute( 'required', true );
+ $this->input->set_block_attribute( 'min', 10 );
+ $this->input->set_block_attribute( 'placeholder', 'Enter a color' );
+
+ $this->assertEquals(
+ [
+ 'property' => 'meta_data._wc_gla_color',
+ 'label' => 'Color',
+ 'tooltip' => 'Color of the product',
+ 'required' => true,
+ 'min' => 10,
+ 'placeholder' => 'Enter a color',
+ ],
+ $this->input->get_block_attributes()
+ );
+ }
+
+ public function test_get_block_config() {
+ $this->input->set_id( 'size' );
+ $this->input->set_label( 'Size' );
+ $this->input->set_description( 'Size of the product' );
+ $this->input->set_block_attribute( 'maxLength', 120 );
+ $this->input->set_block_attribute( 'help', 'Hello!' );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-size',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_size',
+ 'label' => 'Size',
+ 'tooltip' => 'Size of the product',
+ 'maxLength' => 120,
+ 'help' => 'Hello!',
+ ],
+ ],
+ $this->input->get_block_config()
+ );
+ }
+}
diff --git a/tests/Unit/Admin/Product/Attributes/AttributesFormTest.php b/tests/Unit/Admin/Product/Attributes/AttributesFormTest.php
new file mode 100644
index 0000000000..3005f45d11
--- /dev/null
+++ b/tests/Unit/Admin/Product/Attributes/AttributesFormTest.php
@@ -0,0 +1,208 @@
+attribute_manager = $this->container->get( AttributeManager::class );
+ }
+
+ public function test_add_attribute_remove_attribute() {
+ $attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( [ 'simple' ] );
+
+ $form = new AttributesForm( $attribute_types );
+
+ $this->assertNotEmpty( $form->get_children() );
+ $this->assertEquals( count( $attribute_types ), count( $form->get_children() ) );
+
+ foreach ( $attribute_types as $attribute_type ) {
+ $attribute = new $attribute_type();
+ $this->assertArrayHasKey( $attribute->get_id(), $form->get_children() );
+ }
+
+ $mpn_attribute_type = $attribute_types['mpn'];
+ $mpn_attribute = new $mpn_attribute_type();
+ $form->remove_attribute( $mpn_attribute_type );
+ $this->assertArrayNotHasKey( $mpn_attribute->get_id(), $form->get_children() );
+
+ $form->add_attribute( $mpn_attribute_type );
+ $this->assertArrayHasKey( $mpn_attribute->get_id(), $form->get_children() );
+ }
+
+ public function test_set_attribute_input() {
+ $attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( [ 'simple' ] );
+
+ $mpn_attribute_type = $attribute_types['mpn'];
+ $mpn_input_type = call_user_func( [ $mpn_attribute_type, 'get_input_type' ] );
+
+ $gender_attribute_type = $attribute_types['gender'];
+ $gender_input_type = call_user_func( [ $gender_attribute_type, 'get_input_type' ] );
+
+ $form = new AttributesForm( [] );
+
+ // It won't set attribute input if the given attribute has not yet been set
+ $this->assertCount( 0, $form->get_children() );
+ $form->set_attribute_input( $mpn_attribute_type, $mpn_input_type );
+ $this->assertCount( 0, $form->get_children() );
+
+ // Add MPN as the existing attribute to be set with another input
+ $form->add_attribute( $mpn_attribute_type );
+ $this->assertCount( 1, $form->get_children() );
+ $this->assertInstanceOf( $mpn_input_type, $form->get_children()['mpn'] );
+
+ // Set MPN's input to GenderInput
+ $form->set_attribute_input( $mpn_attribute_type, $gender_input_type );
+ $this->assertCount( 1, $form->get_children() );
+ $this->assertInstanceOf( $gender_input_type, $form->get_children()['mpn'] );
+ }
+
+ public function test_init_input_initialization() {
+ $attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( [ 'simple' ] );
+
+ $gender_attribute_type = $attribute_types['gender'];
+ $gender_input_type = call_user_func( [ $gender_attribute_type, 'get_input_type' ] );
+
+ $gender_input = new $gender_input_type();
+
+ $this->assertNull( $gender_input->get_id() );
+ $this->assertEquals( '', $gender_input->get_name() );
+ $this->assertEquals( [], $gender_input->get_options() );
+
+ $gender_input = AttributesForm::init_input( $gender_input, new $gender_attribute_type() );
+
+ $this->assertEquals( 'gender', $gender_input->get_id() );
+ $this->assertEquals( 'gender', $gender_input->get_name() );
+ $this->assertEquals(
+ [
+ '' => 'Default',
+ 'male' => 'Male',
+ 'female' => 'Female',
+ 'unisex' => 'Unisex',
+ ],
+ $gender_input->get_options()
+ );
+ }
+
+
+ public function test_init_input_transformation() {
+ $attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( [ 'simple' ] );
+
+ $mpn_attribute_type = $attribute_types['mpn'];
+ $mpn_input_type = call_user_func( [ $mpn_attribute_type, 'get_input_type' ] );
+
+ $mpn_input = AttributesForm::init_input( new $mpn_input_type(), new $mpn_attribute_type() );
+
+ $this->assertInstanceOf( Text::class, $mpn_input );
+
+ // Test the input transform an instance from the non-selectable class to SelectWithTextInput
+ // when it gets any option from filter
+ add_filter(
+ 'woocommerce_gla_product_attribute_value_options_mpn',
+ function ( array $value_options ) {
+ $value_options['from_integration'] = 'From integration value';
+ return $value_options;
+ }
+ );
+
+ $transformed_mpn_input = AttributesForm::init_input( new $mpn_input_type(), new $mpn_attribute_type() );
+
+ $this->assertInstanceOf( SelectWithTextInput::class, $transformed_mpn_input );
+ $this->assertEquals( $mpn_input->get_id(), $transformed_mpn_input->get_id() );
+ $this->assertEquals( $mpn_input->get_name(), $transformed_mpn_input->get_name() );
+ $this->assertEquals( $mpn_input->get_label(), $transformed_mpn_input->get_label() );
+ $this->assertEquals( $mpn_input->get_description(), $transformed_mpn_input->get_description() );
+ }
+
+ public function test_get_attribute_product_types() {
+ $attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( [ 'simple' ] );
+ $bundle_attribute_type = $attribute_types['isBundle'];
+
+ $this->assertEquals(
+ [
+ 'visible' => [ 'simple', 'variation' ],
+ 'hidden' => [],
+ ],
+ AttributesForm::get_attribute_product_types( $bundle_attribute_type )
+ );
+
+ add_filter(
+ 'woocommerce_gla_attribute_applicable_product_types_isBundle',
+ function ( array $applicable_types ) {
+ $applicable_types[] = 'bundle';
+ return $applicable_types;
+ }
+ );
+
+ add_filter(
+ 'woocommerce_gla_attribute_hidden_product_types_isBundle',
+ function ( array $applicable_types ) {
+ $applicable_types[] = 'simple';
+ return $applicable_types;
+ }
+ );
+
+ $this->assertEquals(
+ [
+ 'visible' => [
+ '1' => 'variation',
+ '2' => 'bundle',
+ ],
+ 'hidden' => [ 'simple' ],
+ ],
+ AttributesForm::get_attribute_product_types( $bundle_attribute_type )
+ );
+ }
+
+ public function test_get_view_data() {
+ add_filter(
+ 'woocommerce_gla_attribute_hidden_product_types_isBundle',
+ function ( array $applicable_types ) {
+ $applicable_types[] = 'simple';
+ return $applicable_types;
+ }
+ );
+
+ $attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( [ 'simple' ] );
+
+ $form = new AttributesForm( $attribute_types );
+ $form->set_name( 'attributes' );
+
+ $view_data = $form->get_view_data();
+
+ $this->assertEquals( 'gla_attributes', $view_data['name'] );
+ $this->assertTrue( $view_data['is_root'] );
+ $this->assertNotEmpty( $view_data['children'] );
+ $this->assertEquals( count( $attribute_types ), count( $view_data['children'] ) );
+
+ foreach ( $view_data['children'] as $id => $child_view_data ) {
+ $attribute_type = $attribute_types[ $id ];
+ $attribute_product_types = AttributesForm::get_attribute_product_types( $attribute_type );
+ $gla_wrapper_class = $child_view_data['gla_wrapper_class'];
+
+ foreach ( $attribute_product_types['visible'] as $visible_type ) {
+ $this->assertMatchesRegularExpression( "/(^| )show_if_{$visible_type}( |$)/", $gla_wrapper_class );
+ }
+
+ foreach ( $attribute_product_types['hidden'] as $hidden_type ) {
+ $this->assertMatchesRegularExpression( "/(^| )hide_if_{$hidden_type}( |$)/", $gla_wrapper_class );
+ }
+ }
+ }
+}
diff --git a/tests/Unit/Admin/Product/Attributes/Input/AttributeInputCollectionTest.php b/tests/Unit/Admin/Product/Attributes/Input/AttributeInputCollectionTest.php
new file mode 100644
index 0000000000..5176dc8c96
--- /dev/null
+++ b/tests/Unit/Admin/Product/Attributes/Input/AttributeInputCollectionTest.php
@@ -0,0 +1,643 @@
+set_id( Adult::get_id() )
+ ->set_name( Adult::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_adult',
+ 'type' => 'select',
+ 'label' => 'Adult content',
+ 'description' => 'Whether the product contains nudity or sexually suggestive content',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'options' => [
+ '' => 'Default',
+ 'yes' => 'Yes',
+ 'no' => 'No',
+ ],
+ 'class' => 'select short',
+ 'name' => 'gla_adult',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-adult',
+ 'blockName' => 'google-listings-and-ads/product-select-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_adult',
+ 'label' => 'Adult content',
+ 'tooltip' => 'Whether the product contains nudity or sexually suggestive content',
+ 'options' => $input->get_block_attributes()['options'],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_age_group_input() {
+ $input = new AgeGroupInput();
+ $input
+ ->set_id( AgeGroup::get_id() )
+ ->set_name( AgeGroup::get_id() )
+ ->set_options( AgeGroup::get_value_options() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_ageGroup',
+ 'type' => 'select',
+ 'label' => 'Age Group',
+ 'description' => 'Target age group of the item.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'options' => AgeGroup::get_value_options(),
+ 'class' => 'select short',
+ 'name' => 'gla_ageGroup',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-ageGroup',
+ 'blockName' => 'google-listings-and-ads/product-select-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_ageGroup',
+ 'label' => 'Age Group',
+ 'tooltip' => 'Target age group of the item.',
+ 'options' => $input->get_block_attributes()['options'],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_availability_date_input() {
+ $input = new AvailabilityDateInput();
+ $input
+ ->set_id( AvailabilityDate::get_id() )
+ ->set_name( AvailabilityDate::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_availabilityDate',
+ 'type' => 'datetime',
+ 'label' => 'Availability Date',
+ 'description' => 'The date a preordered or backordered product becomes available for delivery. Required if product availability is preorder or backorder',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_availabilityDate',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-availabilityDate',
+ 'blockName' => 'google-listings-and-ads/product-date-time-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_availabilityDate',
+ 'label' => 'Availability Date',
+ 'tooltip' => 'The date a preordered or backordered product becomes available for delivery. Required if product availability is preorder or backorder',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_brand_input() {
+ $input = new BrandInput();
+ $input
+ ->set_id( Brand::get_id() )
+ ->set_name( Brand::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_brand',
+ 'type' => 'text',
+ 'label' => 'Brand',
+ 'description' => 'Brand of the product.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_brand',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-brand',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_brand',
+ 'label' => 'Brand',
+ 'tooltip' => 'Brand of the product.',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_color_input() {
+ $input = new ColorInput();
+ $input
+ ->set_id( Color::get_id() )
+ ->set_name( Color::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_color',
+ 'type' => 'text',
+ 'label' => 'Color',
+ 'description' => 'Color of the product.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_color',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-color',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_color',
+ 'label' => 'Color',
+ 'tooltip' => 'Color of the product.',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_condition_input() {
+ $input = new ConditionInput();
+ $input
+ ->set_id( Condition::get_id() )
+ ->set_name( Condition::get_id() )
+ ->set_options( Condition::get_value_options() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_condition',
+ 'type' => 'select',
+ 'label' => 'Condition',
+ 'description' => 'Condition or state of the item.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'options' => Condition::get_value_options(),
+ 'class' => 'select short',
+ 'name' => 'gla_condition',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-condition',
+ 'blockName' => 'google-listings-and-ads/product-select-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_condition',
+ 'label' => 'Condition',
+ 'tooltip' => 'Condition or state of the item.',
+ 'options' => $input->get_block_attributes()['options'],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_gender_input() {
+ $input = new GenderInput();
+ $input
+ ->set_id( Gender::get_id() )
+ ->set_name( Gender::get_id() )
+ ->set_options( Gender::get_value_options() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_gender',
+ 'type' => 'select',
+ 'label' => 'Gender',
+ 'description' => 'The gender for which your product is intended.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'options' => Gender::get_value_options(),
+ 'class' => 'select short',
+ 'name' => 'gla_gender',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-gender',
+ 'blockName' => 'google-listings-and-ads/product-select-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_gender',
+ 'label' => 'Gender',
+ 'tooltip' => 'The gender for which your product is intended.',
+ 'options' => $input->get_block_attributes()['options'],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_gtin_input() {
+ $input = new GTINInput();
+ $input
+ ->set_id( GTIN::get_id() )
+ ->set_name( GTIN::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_gtin',
+ 'type' => 'text',
+ 'label' => 'Global Trade Item Number (GTIN)',
+ 'description' => 'Global Trade Item Number (GTIN) for your item. These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books)',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_gtin',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-gtin',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_gtin',
+ 'label' => 'Global Trade Item Number (GTIN)',
+ 'tooltip' => 'Global Trade Item Number (GTIN) for your item. These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books)',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_is_bundle_input() {
+ $input = new IsBundleInput();
+ $input
+ ->set_id( IsBundle::get_id() )
+ ->set_name( IsBundle::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_isBundle',
+ 'type' => 'select',
+ 'label' => 'Is Bundle?',
+ 'description' => 'Whether the item is a bundle of products. A bundle is a custom grouping of different products sold by a merchant for a single price.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'options' => [
+ '' => 'Default',
+ 'yes' => 'Yes',
+ 'no' => 'No',
+ ],
+ 'class' => 'select short',
+ 'name' => 'gla_isBundle',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-isBundle',
+ 'blockName' => 'google-listings-and-ads/product-select-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_isBundle',
+ 'label' => 'Is Bundle?',
+ 'tooltip' => 'Whether the item is a bundle of products. A bundle is a custom grouping of different products sold by a merchant for a single price.',
+ 'options' => $input->get_block_attributes()['options'],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_material_input() {
+ $input = new MaterialInput();
+ $input
+ ->set_id( Material::get_id() )
+ ->set_name( Material::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_material',
+ 'type' => 'text',
+ 'label' => 'Material',
+ 'description' => 'The material of which the item is made.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_material',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-material',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_material',
+ 'label' => 'Material',
+ 'tooltip' => 'The material of which the item is made.',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_mpn_input() {
+ $input = new MPNInput();
+ $input
+ ->set_id( MPN::get_id() )
+ ->set_name( MPN::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_mpn',
+ 'type' => 'text',
+ 'label' => 'Manufacturer Part Number (MPN)',
+ 'description' => 'This code uniquely identifies the product to its manufacturer.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_mpn',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-mpn',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_mpn',
+ 'label' => 'Manufacturer Part Number (MPN)',
+ 'tooltip' => 'This code uniquely identifies the product to its manufacturer.',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_multipack_input() {
+ $input = new MultipackInput();
+ $input
+ ->set_id( Multipack::get_id() )
+ ->set_name( Multipack::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_multipack',
+ 'type' => 'integer',
+ 'label' => 'Multipack',
+ 'description' => 'The number of identical products in a multipack. Use this attribute to indicate that you\'ve grouped multiple identical products for sale as one item.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_multipack',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-multipack',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_multipack',
+ 'label' => 'Multipack',
+ 'tooltip' => 'The number of identical products in a multipack. Use this attribute to indicate that you\'ve grouped multiple identical products for sale as one item.',
+ 'type' => [ 'value' => 'number' ],
+ 'min' => [ 'value' => 0 ],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_pattern_input() {
+ $input = new PatternInput();
+ $input
+ ->set_id( Pattern::get_id() )
+ ->set_name( Pattern::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_pattern',
+ 'type' => 'text',
+ 'label' => 'Pattern',
+ 'description' => 'The item\'s pattern (e.g. polka dots).',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_pattern',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-pattern',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_pattern',
+ 'label' => 'Pattern',
+ 'tooltip' => 'The item\'s pattern (e.g. polka dots).',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_size_input() {
+ $input = new SizeInput();
+ $input
+ ->set_id( Size::get_id() )
+ ->set_name( Size::get_id() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_size',
+ 'type' => 'text',
+ 'label' => 'Size',
+ 'description' => 'Size of the product.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'name' => 'gla_size',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-size',
+ 'blockName' => 'woocommerce/product-text-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_size',
+ 'label' => 'Size',
+ 'tooltip' => 'Size of the product.',
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_size_system_input() {
+ $input = new SizeSystemInput();
+ $input
+ ->set_id( SizeSystem::get_id() )
+ ->set_name( SizeSystem::get_id() )
+ ->set_options( SizeSystem::get_value_options() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_sizeSystem',
+ 'type' => 'select',
+ 'label' => 'Size system',
+ 'description' => 'System in which the size is specified. Recommended for apparel items.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'options' => SizeSystem::get_value_options(),
+ 'class' => 'select short',
+ 'name' => 'gla_sizeSystem',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-sizeSystem',
+ 'blockName' => 'google-listings-and-ads/product-select-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_sizeSystem',
+ 'label' => 'Size system',
+ 'tooltip' => 'System in which the size is specified. Recommended for apparel items.',
+ 'options' => $input->get_block_attributes()['options'],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+
+ public function test_size_type_input() {
+ $input = new SizeTypeInput();
+ $input
+ ->set_id( SizeType::get_id() )
+ ->set_name( SizeType::get_id() )
+ ->set_options( SizeType::get_value_options() );
+
+ $this->assertEquals(
+ [
+ 'id' => 'gla_sizeType',
+ 'type' => 'select',
+ 'label' => 'Size type',
+ 'description' => 'The cut of the item. Recommended for apparel items.',
+ 'desc_tip' => true,
+ 'value' => null,
+ 'options' => SizeType::get_value_options(),
+ 'class' => 'select short',
+ 'name' => 'gla_sizeType',
+ 'is_root' => true,
+ 'children' => [],
+ ],
+ $input->get_view_data()
+ );
+
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-sizeType',
+ 'blockName' => 'google-listings-and-ads/product-select-field',
+ 'attributes' => [
+ 'property' => 'meta_data._wc_gla_sizeType',
+ 'label' => 'Size type',
+ 'tooltip' => 'The cut of the item. Recommended for apparel items.',
+ 'options' => $input->get_block_attributes()['options'],
+ ],
+ ],
+ $input->get_block_config()
+ );
+ }
+}
diff --git a/tests/Unit/Admin/Product/ChannelVisibilityBlockTest.php b/tests/Unit/Admin/Product/ChannelVisibilityBlockTest.php
new file mode 100644
index 0000000000..855d5d7dd5
--- /dev/null
+++ b/tests/Unit/Admin/Product/ChannelVisibilityBlockTest.php
@@ -0,0 +1,240 @@
+product_helper = $this->createStub( ProductHelper::class );
+ $this->merchant_center = $this->createStub( MerchantCenterService::class );
+
+ $this->channel_visibility_block = new ChannelVisibilityBlock( $this->product_helper, $this->merchant_center );
+ }
+
+ public function test_register() {
+ $this->merchant_center->method( 'is_setup_complete' )->willReturn( true );
+
+ $this->channel_visibility_block->register();
+
+ $this->assertEquals(
+ 10,
+ has_filter(
+ 'woocommerce_rest_prepare_product_object',
+ [ $this->channel_visibility_block, 'prepare_data' ]
+ )
+ );
+
+ $this->assertEquals(
+ 10,
+ has_action(
+ 'woocommerce_rest_insert_product_object',
+ [ $this->channel_visibility_block, 'update_data' ]
+ )
+ );
+ }
+
+ public function test_register_merchant_center_setup_is_not_complete() {
+ $this->merchant_center->method( 'is_setup_complete' )->willReturn( false );
+
+ $this->channel_visibility_block->register();
+
+ $this->assertFalse(
+ has_filter(
+ 'woocommerce_rest_prepare_product_object',
+ [ $this->channel_visibility_block, 'prepare_data' ]
+ )
+ );
+
+ $this->assertFalse(
+ has_action(
+ 'woocommerce_rest_insert_product_object',
+ [ $this->channel_visibility_block, 'update_data' ]
+ )
+ );
+ }
+
+ public function test_prepare_data() {
+ $product = $this->generate_simple_product_mock();
+ $product->method( 'is_visible' )->willReturn( true );
+
+ $issues = [ 'Problem #1', 'Problem #2' ];
+ $this->product_helper->method( 'get_channel_visibility' )->willReturn( 'sync-and-show' );
+ $this->product_helper->method( 'get_sync_status' )->willReturn( 'has-errors' );
+ $this->product_helper->method( 'get_validation_errors' )->willReturn( $issues );
+
+ $response = $this->channel_visibility_block->prepare_data( new Response(), $product );
+
+ $this->assertInstanceOf( Response::class, $response );
+ $this->assertEquals(
+ [
+ 'is_visible' => true,
+ 'channel_visibility' => 'sync-and-show',
+ 'sync_status' => 'has-errors',
+ 'issues' => $issues,
+ ],
+ $response->data[ ChannelVisibilityBlock::PROPERTY ]
+ );
+ }
+
+ public function test_prepare_data_incoming_unexpected_wc_data() {
+ $product = $this->createMock( WC_Order_Item_Product::class );
+ $response = $this->channel_visibility_block->prepare_data( new Response( [] ), $product );
+
+ $this->assertInstanceOf( Response::class, $response );
+ $this->assertArrayNotHasKey( ChannelVisibilityBlock::PROPERTY, $response->data );
+ $this->product_helper->expects( $this->exactly( 0 ) )->method( 'get_channel_visibility' );
+ }
+
+ public function test_update_data() {
+ $product = $this->generate_simple_product_mock();
+ $request = new Request( 'POST' );
+ $data = [ 'channel_visibility' => 'dont-sync-and-show' ];
+
+ $request->set_param( ChannelVisibilityBlock::PROPERTY, $data );
+
+ $this->product_helper
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'update_channel_visibility' )
+ ->with( $product, $data['channel_visibility'] );
+
+ $this->channel_visibility_block->update_data( $product, $request );
+ }
+
+ public function test_update_data_skip_update_if_same_value() {
+ $product = $this->generate_simple_product_mock();
+ $request = new Request( 'POST' );
+ $data = [ 'channel_visibility' => 'sync-and-show' ];
+
+ $request->set_param( ChannelVisibilityBlock::PROPERTY, $data );
+
+ $this->product_helper
+ ->method( 'get_channel_visibility' )
+ ->willReturn( $data['channel_visibility'] );
+
+ $this->product_helper
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'update_channel_visibility' );
+
+ $this->channel_visibility_block->update_data( $product, $request );
+ }
+
+ public function test_update_data_incoming_unexpected_wc_data() {
+ $product = $this->createMock( WC_Order_Item_Product::class );
+ $request = $this->createMock( Request::class );
+
+ $request
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'get_params' );
+
+ $this->product_helper
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'update_channel_visibility' );
+
+ $this->channel_visibility_block->update_data( $product, $request );
+ }
+
+ public function test_update_data_unsupported_product_type() {
+ $product = $this->generate_variation_product_mock();
+ $request = $this->createMock( Request::class );
+
+ $request
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'get_params' );
+
+ $this->product_helper
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'update_channel_visibility' );
+
+ $this->channel_visibility_block->update_data( $product, $request );
+ }
+
+ public function test_update_data_unrelated_request() {
+ $product = $this->generate_simple_product_mock();
+ $request = $this->createMock( Request::class );
+
+ $request
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'get_params' );
+
+ $this->product_helper
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'update_channel_visibility' );
+
+ $this->channel_visibility_block->update_data( $product, $request );
+ }
+
+ public function test_get_visible_product_types() {
+ $this->assertEqualsCanonicalizing(
+ [ 'simple', 'variable' ],
+ $this->channel_visibility_block->get_visible_product_types()
+ );
+
+ add_filter(
+ 'woocommerce_gla_supported_product_types',
+ function ( array $product_types ) {
+ $product_types[] = 'bundle';
+ return $product_types;
+ }
+ );
+
+ $this->assertEqualsCanonicalizing(
+ [ 'simple', 'variable', 'bundle' ],
+ $this->channel_visibility_block->get_visible_product_types()
+ );
+ }
+
+ public function test_get_block_config() {
+ $this->assertEquals(
+ [
+ 'id' => 'google-listings-and-ads-product-channel-visibility',
+ 'blockName' => 'google-listings-and-ads/product-channel-visibility',
+ 'attributes' => [
+ 'property' => ChannelVisibilityBlock::PROPERTY,
+ 'options' => [
+ [
+ 'label' => 'Sync and show',
+ 'value' => 'sync-and-show',
+ ],
+ [
+ 'label' => "Don't Sync and show",
+ 'value' => 'dont-sync-and-show',
+ ],
+ ],
+ 'valueOfSync' => 'sync-and-show',
+ 'valueOfDontSync' => 'dont-sync-and-show',
+ 'statusOfSynced' => 'synced',
+ 'statusOfHasErrors' => 'has-errors',
+ ],
+ ],
+ $this->channel_visibility_block->get_block_config()
+ );
+ }
+}
diff --git a/tests/Unit/Admin/ProductBlocksServiceTest.php b/tests/Unit/Admin/ProductBlocksServiceTest.php
new file mode 100644
index 0000000000..f1f5e9e3be
--- /dev/null
+++ b/tests/Unit/Admin/ProductBlocksServiceTest.php
@@ -0,0 +1,484 @@
+= 8.6" -- The Block Template API used requires at least WooCommerce 8.6
+ if ( ! ProductBlocksService::is_needed() ) {
+ $this->markTestSkipped( 'This test suite requires WooCommerce version >= 8.6' );
+ }
+
+ parent::setUp();
+
+ $this->assets_handler = $this->createMock( AssetsHandlerInterface::class );
+ $this->channel_visibility_block = $this->container->get( ChannelVisibilityBlock::class );
+ $this->attribute_manager = $this->container->get( AttributeManager::class );
+ $this->merchant_center = $this->createStub( MerchantCenterService::class );
+
+ $this->simple_anchor_group = $this->createMock( BlockInterface::class );
+ $this->variation_anchor_group = $this->createMock( BlockInterface::class );
+ $this->mismatching_group = $this->createMock( BlockInterface::class );
+
+ $this->product_blocks_service = new ProductBlocksService( $this->assets_handler, $this->channel_visibility_block, $this->attribute_manager, $this->merchant_center );
+
+ // Set up stubs and mocks
+ $this->is_mc_setup_complete = true;
+ $this->merchant_center
+ ->method( 'is_setup_complete' )
+ ->willReturnCallback(
+ function () {
+ return $this->is_mc_setup_complete;
+ }
+ );
+
+ // Ref: https://github.com/woocommerce/woocommerce/blob/8.6.0/plugins/woocommerce/src/Admin/PageController.php#L555-L562
+ $_GET['page'] = PageController::PAGE_ROOT;
+
+ $this->simple = $this->setUpBlockMock( $this->simple_anchor_group, 'simple-product' );
+ $this->variation = $this->setUpBlockMock( $this->variation_anchor_group, 'product-variation' );
+ $this->setUpBlockMock( $this->mismatching_group, 'mismatching-template' );
+ }
+
+ private function setUpBlockMock( MockObject $anchor_group, string $template_id ) {
+ $template = $this->createMock( ProductFormTemplateInterface::class );
+ $group = $this->createMock( GroupInterface::class );
+
+ $visibility_section = $this->createMock( SectionInterface::class );
+ $attributes_section = $this->createMock( SectionInterface::class );
+ $visibility_block = $this->createMock( BlockInterface::class );
+ $attributes_block = $this->createMock( BlockInterface::class );
+
+ $template->method( 'get_id' )->willReturn( $template_id );
+ $template->method( 'add_group' )->willReturn( $group );
+
+ $visibility_section->method( 'get_root_template' )->willReturn( $template );
+ $visibility_section->method( 'add_block' )->willReturn( $visibility_block );
+
+ $attributes_section->method( 'get_root_template' )->willReturn( $template );
+ $attributes_section->method( 'add_block' )->willReturn( $attributes_block );
+
+ $anchor_group->method( 'get_root_template' )->willReturn( $template );
+
+ $group
+ ->method( 'add_section' )
+ ->willReturnCallback(
+ function ( array $config ) use ( $visibility_section, $attributes_section ) {
+ if ( 'google-listings-and-ads-channel-visibility-section' === $config['id'] ) {
+ return $visibility_section;
+ }
+
+ if ( 'google-listings-and-ads-product-attributes-section' === $config['id'] ) {
+ return $attributes_section;
+ }
+ }
+ );
+
+ return [
+ 'template' => $template,
+ 'group' => $group,
+ 'visibility_section' => $visibility_section,
+ 'attributes_section' => $attributes_section,
+ 'visibility_block' => $visibility_block,
+ 'attributes_block' => $attributes_block,
+ ];
+ }
+
+ public function test_get_applicable_product_types() {
+ $this->assertEquals( [ 'simple', 'variable' ], $this->get_applicable_product_types() );
+
+ add_filter(
+ 'woocommerce_gla_attributes_tab_applicable_product_types',
+ function ( array $product_types ) {
+ $product_types[] = 'bundle';
+ return $product_types;
+ }
+ );
+
+ $this->assertEquals( [ 'simple', 'variable', 'bundle' ], $this->get_applicable_product_types() );
+ }
+
+ public function test_get_hide_condition() {
+ $this->assertEquals(
+ "editedProduct.type !== 'simple' && editedProduct.type !== 'variable' && editedProduct.type !== 'variation'",
+ $this->product_blocks_service->get_hide_condition( Adult::get_applicable_product_types() )
+ );
+
+ $this->assertEquals(
+ "editedProduct.type !== 'simple' && editedProduct.type !== 'variable'",
+ $this->product_blocks_service->get_hide_condition( Brand::get_applicable_product_types() )
+ );
+
+ $this->assertEquals(
+ "editedProduct.type !== 'simple' && editedProduct.type !== 'variation'",
+ $this->product_blocks_service->get_hide_condition( Gender::get_applicable_product_types() )
+ );
+
+ // Hide all product types
+ $this->assertEquals( 'true', $this->product_blocks_service->get_hide_condition( [] ) );
+ }
+
+ public function test_register() {
+ $this->assertFalse( has_filter( 'init', [ $this->product_blocks_service, 'hook_init' ] ) );
+ $this->assertFalse( has_filter( self::GENERAL_GROUP_HOOK, [ $this->product_blocks_service, 'hook_block_template' ] ) );
+
+ $this->product_blocks_service->register();
+
+ $this->assertEquals( 10, has_filter( 'init', [ $this->product_blocks_service, 'hook_init' ] ) );
+ $this->assertEquals( 10, has_filter( self::GENERAL_GROUP_HOOK, [ $this->product_blocks_service, 'hook_block_template' ] ) );
+ }
+
+ public function test_register_is_not_admin_page() {
+ unset( $_GET['page'] );
+
+ $this->product_blocks_service->register();
+
+ $this->assertFalse( has_filter( 'init', [ $this->product_blocks_service, 'hook_init' ] ) );
+ }
+
+ public function test_hook_block_template() {
+ $this->simple_anchor_group->get_root_template()
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_group' );
+
+ $this->variation_anchor_group->get_root_template()
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_group' );
+
+ $this->simple['template']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_group' )->with(
+ [
+ 'id' => 'google-listings-and-ads-group',
+ 'order' => 100,
+ 'attributes' => [
+ 'title' => 'Google Listings & Ads',
+ ],
+ ]
+ );
+
+ $this->simple['group']
+ ->expects( $this->exactly( 2 ) )
+ ->method( 'add_section' )
+ ->withConsecutive(
+ [
+ [
+ 'id' => 'google-listings-and-ads-channel-visibility-section',
+ 'order' => 1,
+ 'attributes' => [
+ 'title' => __( 'Channel visibility', 'google-listings-and-ads' ),
+ ],
+ ],
+ ],
+ [
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-section',
+ 'order' => 2,
+ 'attributes' => [
+ 'title' => 'Product attributes',
+ ],
+ ],
+ ]
+ );
+
+ $this->variation['template']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_group' )->with(
+ [
+ 'id' => 'google-listings-and-ads-group',
+ 'order' => 100,
+ 'attributes' => [
+ 'title' => 'Google Listings & Ads',
+ ],
+ ]
+ );
+
+ $this->variation['group']
+ ->expects( $this->exactly( 2 ) )
+ ->method( 'add_section' )
+ ->withConsecutive(
+ [
+ [
+ 'id' => 'google-listings-and-ads-channel-visibility-section',
+ 'order' => 1,
+ 'attributes' => [
+ 'title' => __( 'Channel visibility', 'google-listings-and-ads' ),
+ ],
+ ],
+ ],
+ [
+ [
+ 'id' => 'google-listings-and-ads-product-attributes-section',
+ 'order' => 2,
+ 'attributes' => [
+ 'title' => 'Product attributes',
+ ],
+ ],
+ ]
+ );
+
+ $this->product_blocks_service->hook_block_template( $this->simple_anchor_group );
+ $this->product_blocks_service->hook_block_template( $this->variation_anchor_group );
+ }
+
+ public function test_hook_block_template_group_hidden_condition() {
+ $this->simple['group']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_hide_condition' )
+ ->with( "editedProduct.type !== 'simple' && editedProduct.type !== 'variable' && editedProduct.type !== 'variation'" );
+
+ $this->variation['group']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_hide_condition' );
+
+ $this->product_blocks_service->hook_block_template( $this->simple_anchor_group );
+ $this->product_blocks_service->hook_block_template( $this->variation_anchor_group );
+ }
+
+ public function test_hook_block_template_group_hidden_condition_with_applying_filter() {
+ $this->simple['group']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_hide_condition' )
+ ->with( "editedProduct.type !== 'simple'" );
+
+ $this->variation['group']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_hide_condition' )
+ ->with( 'true' );
+
+ add_filter(
+ 'woocommerce_gla_supported_product_types',
+ function () {
+ return [ 'simple' ];
+ }
+ );
+
+ $this->product_blocks_service->hook_block_template( $this->simple_anchor_group );
+ $this->product_blocks_service->hook_block_template( $this->variation_anchor_group );
+ }
+
+ public function test_hook_block_template_merchant_center_setup_is_not_complete() {
+ $this->is_mc_setup_complete = false;
+
+ $this->simple_anchor_group->get_root_template()
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_group' );
+
+ $this->simple['group']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_block' )
+ ->with(
+ [
+ 'id' => 'google-listings-and-ads-product-onboarding-prompt',
+ 'blockName' => 'google-listings-and-ads/product-onboarding-prompt',
+ 'attributes' => [ 'startUrl' => 'http://example.org/wp-admin/admin.php?page=wc-admin&path=/google/start' ],
+ ]
+ );
+
+ $this->variation_anchor_group->get_root_template()
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_group' );
+
+ $this->variation['group']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_block' )
+ ->with(
+ [
+ 'id' => 'google-listings-and-ads-product-onboarding-prompt',
+ 'blockName' => 'google-listings-and-ads/product-onboarding-prompt',
+ 'attributes' => [ 'startUrl' => 'http://example.org/wp-admin/admin.php?page=wc-admin&path=/google/start' ],
+ ]
+ );
+
+ $this->product_blocks_service->hook_block_template( $this->simple_anchor_group );
+ $this->product_blocks_service->hook_block_template( $this->variation_anchor_group );
+ }
+
+ public function test_hook_block_template_not_add_group_or_section() {
+ $this->simple['template']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_group' );
+
+ $this->simple['group']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_section' );
+
+ $this->variation['template']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_group' );
+
+ $this->variation['group']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_section' );
+
+ // Here it intentionally calls with a mismatched template for each
+ $this->product_blocks_service->hook_block_template( $this->mismatching_group );
+ $this->product_blocks_service->hook_block_template( $this->mismatching_group );
+ }
+
+ /**
+ * Tests that assert the block configs passed to `add_block` are covered by
+ * `ChannelVisibilityBlockTest`.
+ */
+ public function test_hook_block_template_add_channel_visibility_blocks() {
+ $this->simple['visibility_section']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_block' );
+
+ $this->simple['visibility_section']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_hide_condition' )
+ ->with( "editedProduct.type !== 'simple' && editedProduct.type !== 'variable'" );
+
+ $this->simple['visibility_block']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_hide_condition' );
+
+ $this->variation['visibility_section']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_block' );
+
+ $this->variation['visibility_section']
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'add_hide_condition' )
+ ->with( "editedProduct.type !== 'simple' && editedProduct.type !== 'variable'" );
+
+ $this->variation['visibility_block']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_hide_condition' );
+
+ $this->product_blocks_service->hook_block_template( $this->simple_anchor_group );
+ $this->product_blocks_service->hook_block_template( $this->variation_anchor_group );
+ }
+
+ /**
+ * Tests that assert the block configs passed to `add_block` are covered by
+ * `InputTest` and `AttributeInputCollectionTest`.
+ */
+ public function test_hook_block_template_add_attribute_blocks() {
+ // The total number of attribute blocks to be added to the simple product template is 16
+ $this->simple['attributes_section']
+ ->expects( $this->exactly( 16 ) )
+ ->method( 'add_block' );
+
+ $this->simple['attributes_block']
+ ->expects( $this->exactly( 16 ) )
+ ->method( 'add_hide_condition' );
+
+ // The total number of visible attribute blocks to be added to the variation product template is 15
+ $this->variation['attributes_section']
+ ->expects( $this->exactly( 15 ) )
+ ->method( 'add_block' );
+
+ $this->variation['attributes_block']
+ ->expects( $this->exactly( 0 ) )
+ ->method( 'add_hide_condition' );
+
+ $this->product_blocks_service->hook_block_template( $this->simple_anchor_group );
+ $this->product_blocks_service->hook_block_template( $this->variation_anchor_group );
+ }
+
+ public function test_register_custom_blocks() {
+ $custom_blocks = [ 'existing-block', 'non-existent-block' ];
+ $expected_script_asset = new AdminScriptWithBuiltDependenciesAsset(
+ 'google-listings-and-ads-product-blocks',
+ 'tests/data/blocks',
+ GLA_TESTS_DATA_DIR . '/blocks.asset.php',
+ new BuiltScriptDependencyArray(
+ [
+ 'dependencies' => [],
+ 'version' => (string) filemtime( GLA_TESTS_DATA_DIR . '/blocks.js' ),
+ ]
+ )
+ );
+ $expected_style_asset = new AdminStyleAsset(
+ 'google-listings-and-ads-product-blocks-css',
+ 'tests/data/blocks',
+ [],
+ (string) filemtime( GLA_TESTS_DATA_DIR . '/blocks.css' )
+ );
+
+ $block_registry = $this->createMock( BlockRegistry::class );
+ $block_registry
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'register_block_type_from_metadata' )
+ ->with( $this->stringContains( 'tests/data/existing-block/block.json' ) );
+
+ $this->assets_handler
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'register_many' )
+ ->with( [ $expected_script_asset, $expected_style_asset ] );
+
+ $this->assets_handler
+ ->expects( $this->exactly( 1 ) )
+ ->method( 'enqueue_many' )
+ ->with( [ $expected_script_asset, $expected_style_asset ] );
+
+ $this->product_blocks_service->register_custom_blocks( $block_registry, GLA_TESTS_DATA_DIR, 'tests/data/blocks', $custom_blocks );
+ }
+}
diff --git a/tests/Unit/Assets/ScriptAssetTest.php b/tests/Unit/Assets/ScriptAssetTest.php
new file mode 100644
index 0000000000..fc206f0e11
--- /dev/null
+++ b/tests/Unit/Assets/ScriptAssetTest.php
@@ -0,0 +1,32 @@
+assertTrue( $asset->can_enqueue() );
+
+ $asset = new ScriptAsset( __FUNCTION__, self::URI, [], '', $dont_enqueue );
+ $this->assertFalse( $asset->can_enqueue() );
+
+ // Can enqueue when callback isn't set
+ $asset = new ScriptAsset( __FUNCTION__, self::URI, [], '' );
+ $this->assertTrue( $asset->can_enqueue() );
+ }
+}
diff --git a/tests/Unit/Product/ProductHelperTest.php b/tests/Unit/Product/ProductHelperTest.php
index f60046d1e4..62c3929880 100644
--- a/tests/Unit/Product/ProductHelperTest.php
+++ b/tests/Unit/Product/ProductHelperTest.php
@@ -823,6 +823,66 @@ public function test_get_channel_visibility_variation_product_returns_dont_sync_
$this->assertEquals( ChannelVisibility::DONT_SYNC_AND_SHOW, $this->product_helper->get_channel_visibility( $variation ) );
}
+ /**
+ * @param WC_Product $product
+ *
+ * @dataProvider return_test_products
+ */
+ public function test_update_channel_visibility( WC_Product $product ) {
+ $this->assertNull( $this->product_meta->get_visibility( $product ) );
+
+ $this->product_helper->update_channel_visibility( $product, ChannelVisibility::SYNC_AND_SHOW );
+ $this->assertEquals( ChannelVisibility::SYNC_AND_SHOW, $this->product_meta->get_visibility( $product ) );
+
+ $this->product_helper->update_channel_visibility( $product, ChannelVisibility::DONT_SYNC_AND_SHOW );
+ $this->assertEquals( ChannelVisibility::DONT_SYNC_AND_SHOW, $this->product_meta->get_visibility( $product ) );
+ }
+
+ public function test_update_channel_visibility_variation_product_inherits_from_parent() {
+ $parent = WC_Helper_Product::create_variation_product();
+ $variation = $this->wc->get_product( $parent->get_children()[0] );
+
+ $this->product_helper->update_channel_visibility( $parent, ChannelVisibility::DONT_SYNC_AND_SHOW );
+ $this->assertEquals( ChannelVisibility::DONT_SYNC_AND_SHOW, $this->product_meta->get_visibility( $parent ) );
+
+ $this->product_helper->update_channel_visibility( $variation, ChannelVisibility::SYNC_AND_SHOW );
+
+ // The `$parent` must be recreated to sync the latest product data updated by
+ // a different instance.
+ $parent = $this->wc->get_product( $parent->get_id() );
+ $this->assertEquals( ChannelVisibility::SYNC_AND_SHOW, $this->product_meta->get_visibility( $parent ) );
+ }
+
+ /**
+ * @param WC_Product $product
+ *
+ * @dataProvider return_test_products
+ */
+ public function test_update_channel_visibility_wont_update_if_invalid_value( WC_Product $product ) {
+ $this->assertNull( $this->product_meta->get_visibility( $product ) );
+ $this->product_helper->update_channel_visibility( $product, 'phpunit-test-disallowed-value' );
+ $this->assertNull( $this->product_meta->get_visibility( $product ) );
+ }
+
+ public function test_update_channel_visibility_wont_update_if_orphan() {
+ $variable = WC_Helper_Product::create_variation_product();
+ $variation = $this->wc->get_product( $variable->get_children()[0] );
+
+ $this->product_helper->update_channel_visibility( $variable, ChannelVisibility::DONT_SYNC_AND_SHOW );
+ $this->assertEquals( ChannelVisibility::DONT_SYNC_AND_SHOW, $this->product_meta->get_visibility( $variable ) );
+
+ // Make the variation orphan by setting its parent to 0.
+ $variation->set_parent_id( 0 );
+ $variation->save();
+
+ $this->product_helper->update_channel_visibility( $variation, ChannelVisibility::SYNC_AND_SHOW );
+
+ // The `$variable` must be recreated to sync the latest product data therefore
+ // it can determine whether a different instance has updated it.
+ $variable = $this->wc->get_product( $variable->get_id() );
+ $this->assertEquals( ChannelVisibility::DONT_SYNC_AND_SHOW, $this->product_meta->get_visibility( $variable ) );
+ }
+
/**
* @param WC_Product $product
*
diff --git a/tests/Unit/Value/ChannelVisibilityTest.php b/tests/Unit/Value/ChannelVisibilityTest.php
new file mode 100644
index 0000000000..c8e1e93580
--- /dev/null
+++ b/tests/Unit/Value/ChannelVisibilityTest.php
@@ -0,0 +1,68 @@
+assertEquals(
+ 'sync-and-show',
+ ( new ChannelVisibility( 'sync-and-show' ) )->get()
+ );
+
+ $this->assertEquals(
+ 'dont-sync-and-show',
+ ( new ChannelVisibility( 'dont-sync-and-show' ) )->get()
+ );
+ }
+
+ public function test_constructor_invalid_value() {
+ $this->expectException( InvalidValue::class );
+
+ new ChannelVisibility( '123' );
+ }
+
+ public function test_cast() {
+ $this->assertEquals(
+ 'sync-and-show',
+ ChannelVisibility::cast( 'sync-and-show' )->get()
+ );
+
+ $this->assertEquals(
+ 'dont-sync-and-show',
+ ChannelVisibility::cast( 'dont-sync-and-show' )->get()
+ );
+ }
+
+ public function test_cast_invalid_value() {
+ $this->expectException( InvalidValue::class );
+
+ ChannelVisibility::cast( '123' );
+ }
+
+ public function test_get_value_options() {
+ $this->assertEquals(
+ [
+ 'sync-and-show' => 'Sync and show',
+ 'dont-sync-and-show' => "Don't Sync and show",
+ ],
+ ChannelVisibility::get_value_options(),
+ );
+ }
+
+ public function test_to_string() {
+ $this->assertEquals(
+ 'sync-and-show',
+ (string) ( new ChannelVisibility( 'sync-and-show' ) )
+ );
+ }
+}
diff --git a/tests/data/blocks.css b/tests/data/blocks.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/data/blocks.js b/tests/data/blocks.js
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/data/existing-block/block.json b/tests/data/existing-block/block.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/tests/data/existing-block/block.json
@@ -0,0 +1 @@
+{}
diff --git a/views/meta-box/channel_visibility.php b/views/meta-box/channel_visibility.php
index ed8122d64d..43546d3335 100644
--- a/views/meta-box/channel_visibility.php
+++ b/views/meta-box/channel_visibility.php
@@ -63,10 +63,7 @@
'label' => __( 'Google Listing & Ads', 'google-listings-and-ads' ),
'description' => $input_description,
'desc_tip' => false,
- 'options' => [
- ChannelVisibility::SYNC_AND_SHOW => __( 'Sync and show', 'google-listings-and-ads' ),
- ChannelVisibility::DONT_SYNC_AND_SHOW => __( 'Don\'t Sync and show', 'google-listings-and-ads' ),
- ],
+ 'options' => ChannelVisibility::get_value_options(),
'custom_attributes' => $custom_attributes,
'wrapper_class' => 'form-row form-row-full',
]
diff --git a/webpack.config.js b/webpack.config.js
index 9091a142d1..fb3f95e203 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -63,7 +63,6 @@ const webpackConfig = {
* - https://github.com/WordPress/gutenberg/tree/%40wordpress/scripts%4022.1.0/packages/scripts#default-webpack-config
* - https://github.com/WordPress/gutenberg/blob/%40wordpress/scripts%4022.1.0/packages/scripts/config/webpack.config.js#L232-L240
*/
- 'CopyPlugin',
'MiniCssExtractPlugin',
'ReactRefreshPlugin',
];
@@ -78,19 +77,21 @@ const webpackConfig = {
chunkFilename: '[name].css?ver=[chunkhash]',
} ),
],
- entry: {
+ entry: () => ( {
+ ...defaultConfig.entry(),
index: path.resolve( process.cwd(), 'js/src', 'index.js' ),
'product-attributes': path.resolve(
process.cwd(),
'js/src/product-attributes',
'index.js'
),
+ blocks: path.join( __dirname, 'js/src/blocks/index.js' ),
'gtag-events': path.resolve(
process.cwd(),
'js/src/gtag-events',
'index.js'
),
- },
+ } ),
output: {
...defaultConfig.output,
path: path.resolve( process.cwd(), 'js/build' ),