Merge branch 'trunk' into update/woocommerce-blocks-10.4.0
This commit is contained in:
commit
f896f268e0
|
@ -43,7 +43,7 @@ runs:
|
|||
with:
|
||||
php-version: ${{ inputs.php-version }}
|
||||
coverage: none
|
||||
tools: phpcs, sirbrillig/phpcs-changed
|
||||
tools: phpcs, sirbrillig/phpcs-changed:2.10.2
|
||||
|
||||
- name: Cache Composer Dependencies
|
||||
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
== Changelog ==
|
||||
|
||||
= 7.7.2 2023-06-01 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Update - Update WooCommerce Blocks to 10.0.6 [#38533](https://github.com/woocommerce/woocommerce/pull/38533)
|
||||
|
||||
= 7.7.1 2023-05-26 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Update - Update WooCommerce Blocks to 10.0.5 [#38427](https://github.com/woocommerce/woocommerce/pull/38427)
|
||||
|
||||
|
||||
= 7.7.0 2023-05-10 =
|
||||
|
||||
**WooCommerce**
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Add extraFields and showDescription props
|
|
@ -46,7 +46,8 @@ export const CustomerEffortScoreModalContainer: React.FC = () => {
|
|||
const recordScore = (
|
||||
score: number,
|
||||
secondScore: number,
|
||||
comments: string
|
||||
comments: string,
|
||||
extraFieldsValues: { [ key: string ]: string } = {}
|
||||
) => {
|
||||
recordEvent( 'ces_feedback', {
|
||||
action: visibleCESModalData.action,
|
||||
|
@ -54,6 +55,7 @@ export const CustomerEffortScoreModalContainer: React.FC = () => {
|
|||
score_second_question: secondScore ?? null,
|
||||
score_combined: score + ( secondScore ?? 0 ),
|
||||
comments: comments || '',
|
||||
...extraFieldsValues,
|
||||
store_age: storeAgeInWeeks,
|
||||
...visibleCESModalData.tracksProps,
|
||||
} );
|
||||
|
@ -75,6 +77,7 @@ export const CustomerEffortScoreModalContainer: React.FC = () => {
|
|||
return (
|
||||
<CustomerFeedbackModal
|
||||
title={ visibleCESModalData.title }
|
||||
showDescription={ visibleCESModalData.showDescription }
|
||||
firstQuestion={ visibleCESModalData.firstQuestion }
|
||||
secondQuestion={ visibleCESModalData.secondQuestion }
|
||||
recordScoreCallback={ ( ...args ) => {
|
||||
|
@ -87,6 +90,10 @@ export const CustomerEffortScoreModalContainer: React.FC = () => {
|
|||
hideCesModal();
|
||||
} }
|
||||
shouldShowComments={ visibleCESModalData.props?.shouldShowComments }
|
||||
getExtraFieldsToBeShown={
|
||||
visibleCESModalData.getExtraFieldsToBeShown
|
||||
}
|
||||
validateExtraFields={ visibleCESModalData.validateExtraFields }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ type CustomerEffortScoreProps = {
|
|||
) => void;
|
||||
title?: string;
|
||||
description?: string;
|
||||
showDescription?: boolean;
|
||||
noticeLabel?: string;
|
||||
firstQuestion: string;
|
||||
secondQuestion?: string;
|
||||
|
@ -33,6 +34,14 @@ type CustomerEffortScoreProps = {
|
|||
firstQuestionScore: number,
|
||||
secondQuestionScore: number
|
||||
) => boolean;
|
||||
getExtraFieldsToBeShown?: (
|
||||
extraFieldsValues: { [ key: string ]: string },
|
||||
setExtraFieldsValues: ( values: { [ key: string ]: string } ) => void,
|
||||
errors: Record< string, string > | undefined
|
||||
) => JSX.Element;
|
||||
validateExtraFields?: ( values: { [ key: string ]: string } ) => {
|
||||
[ key: string ]: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -45,6 +54,7 @@ type CustomerEffortScoreProps = {
|
|||
* @param {Function} props.recordScoreCallback Function to call when the score should be recorded.
|
||||
* @param {string} [props.title] The title displayed in the modal.
|
||||
* @param {string} props.description The description displayed in the modal.
|
||||
* @param {boolean} props.showDescription Show description in the modal.
|
||||
* @param {string} props.noticeLabel The notice label displayed in the notice.
|
||||
* @param {string} props.firstQuestion The first survey question.
|
||||
* @param {string} [props.secondQuestion] The second survey question.
|
||||
|
@ -54,11 +64,14 @@ type CustomerEffortScoreProps = {
|
|||
* @param {Function} props.onModalDismissedCallback Function to call when modal is dismissed.
|
||||
* @param {Function} props.shouldShowComments Callback to determine if comments section should be shown.
|
||||
* @param {Object} props.icon Icon (React component) to be shown on the notice.
|
||||
* @param {Function} props.getExtraFieldsToBeShown Function that returns the extra fields to be shown.
|
||||
* @param {Function} props.validateExtraFields Function that validates the extra fields.
|
||||
*/
|
||||
const CustomerEffortScore: React.VFC< CustomerEffortScoreProps > = ( {
|
||||
recordScoreCallback,
|
||||
title,
|
||||
description,
|
||||
showDescription = true,
|
||||
noticeLabel,
|
||||
firstQuestion,
|
||||
secondQuestion,
|
||||
|
@ -71,6 +84,8 @@ const CustomerEffortScore: React.VFC< CustomerEffortScoreProps > = ( {
|
|||
[ firstQuestionScore, secondQuestionScore ].some(
|
||||
( score ) => score === 1 || score === 2
|
||||
),
|
||||
getExtraFieldsToBeShown,
|
||||
validateExtraFields,
|
||||
} ) => {
|
||||
const [ shouldCreateNotice, setShouldCreateNotice ] = useState( true );
|
||||
const [ visible, setVisible ] = useState( false );
|
||||
|
@ -113,11 +128,14 @@ const CustomerEffortScore: React.VFC< CustomerEffortScoreProps > = ( {
|
|||
<CustomerFeedbackModal
|
||||
title={ title }
|
||||
description={ description }
|
||||
showDescription={ showDescription }
|
||||
firstQuestion={ firstQuestion }
|
||||
secondQuestion={ secondQuestion }
|
||||
recordScoreCallback={ recordScoreCallback }
|
||||
onCloseModal={ onModalDismissedCallback }
|
||||
shouldShowComments={ shouldShowComments }
|
||||
getExtraFieldsToBeShown={ getExtraFieldsToBeShown }
|
||||
validateExtraFields={ validateExtraFields }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,21 +23,25 @@ import { __ } from '@wordpress/i18n';
|
|||
*
|
||||
* Upon completion, the score and comments is sent to a callback function.
|
||||
*
|
||||
* @param {Object} props Component props.
|
||||
* @param {Function} props.recordScoreCallback Function to call when the results are sent.
|
||||
* @param {string} props.title Title displayed in the modal.
|
||||
* @param {string} props.description Description displayed in the modal.
|
||||
* @param {string} props.firstQuestion The first survey question.
|
||||
* @param {string} [props.secondQuestion] An optional second survey question.
|
||||
* @param {string} props.defaultScore Default score.
|
||||
* @param {Function} props.onCloseModal Callback for when user closes modal by clicking cancel.
|
||||
* @param {Function} props.customOptions List of custom score options, contains label and value.
|
||||
* @param {Function} props.shouldShowComments A function to determine whether or not the comments field shown be shown.
|
||||
* @param {Object} props Component props.
|
||||
* @param {Function} props.recordScoreCallback Function to call when the results are sent.
|
||||
* @param {string} props.title Title displayed in the modal.
|
||||
* @param {string} props.description Description displayed in the modal.
|
||||
* @param {boolean} props.showDescription Show description in the modal.
|
||||
* @param {string} props.firstQuestion The first survey question.
|
||||
* @param {string} [props.secondQuestion] An optional second survey question.
|
||||
* @param {string} props.defaultScore Default score.
|
||||
* @param {Function} props.onCloseModal Callback for when user closes modal by clicking cancel.
|
||||
* @param {Function} props.customOptions List of custom score options, contains label and value.
|
||||
* @param {Function} props.shouldShowComments A function to determine whether or not the comments field shown be shown.
|
||||
* @param {Function} props.getExtraFieldsToBeShown Function that returns the extra fields to be shown.
|
||||
* @param {Function} props.validateExtraFields Function that validates the extra fields.
|
||||
*/
|
||||
function CustomerFeedbackModal( {
|
||||
recordScoreCallback,
|
||||
title = __( 'Please share your feedback', 'woocommerce' ),
|
||||
description,
|
||||
showDescription = true,
|
||||
firstQuestion,
|
||||
secondQuestion,
|
||||
defaultScore = NaN,
|
||||
|
@ -47,14 +51,18 @@ function CustomerFeedbackModal( {
|
|||
[ firstQuestionScore, secondQuestionScore ].some(
|
||||
( score ) => score === 1 || score === 2
|
||||
),
|
||||
getExtraFieldsToBeShown,
|
||||
validateExtraFields,
|
||||
}: {
|
||||
recordScoreCallback: (
|
||||
score: number,
|
||||
secondScore: number,
|
||||
comments: string
|
||||
comments: string,
|
||||
extraFieldsValues: { [ key: string ]: string }
|
||||
) => void;
|
||||
title?: string;
|
||||
description?: string;
|
||||
showDescription?: boolean;
|
||||
firstQuestion: string;
|
||||
secondQuestion?: string;
|
||||
defaultScore?: number;
|
||||
|
@ -64,6 +72,14 @@ function CustomerFeedbackModal( {
|
|||
firstQuestionScore: number,
|
||||
secondQuestionScore: number
|
||||
) => boolean;
|
||||
getExtraFieldsToBeShown?: (
|
||||
extraFieldsValues: { [ key: string ]: string },
|
||||
setExtraFieldsValues: ( values: { [ key: string ]: string } ) => void,
|
||||
errors: Record< string, string > | undefined
|
||||
) => JSX.Element;
|
||||
validateExtraFields?: ( values: { [ key: string ]: string } ) => {
|
||||
[ key: string ]: string;
|
||||
};
|
||||
} ): JSX.Element | null {
|
||||
const options =
|
||||
customOptions && customOptions.length > 0
|
||||
|
@ -100,6 +116,12 @@ function CustomerFeedbackModal( {
|
|||
const [ comments, setComments ] = useState( '' );
|
||||
const [ showNoScoreMessage, setShowNoScoreMessage ] = useState( false );
|
||||
const [ isOpen, setOpen ] = useState( true );
|
||||
const [ extraFieldsValues, setExtraFieldsValues ] = useState< {
|
||||
[ key: string ]: string;
|
||||
} >( {} );
|
||||
const [ errors, setErrors ] = useState<
|
||||
Record< string, string > | undefined
|
||||
>( {} );
|
||||
|
||||
const closeModal = () => {
|
||||
setOpen( false );
|
||||
|
@ -118,18 +140,27 @@ function CustomerFeedbackModal( {
|
|||
};
|
||||
|
||||
const sendScore = () => {
|
||||
if (
|
||||
const missingFirstOrSecondQuestions =
|
||||
! Number.isInteger( firstQuestionScore ) ||
|
||||
( secondQuestion && ! Number.isInteger( secondQuestionScore ) )
|
||||
) {
|
||||
( secondQuestion && ! Number.isInteger( secondQuestionScore ) );
|
||||
if ( missingFirstOrSecondQuestions ) {
|
||||
setShowNoScoreMessage( true );
|
||||
}
|
||||
const extraFieldsErrors =
|
||||
typeof validateExtraFields === 'function'
|
||||
? validateExtraFields( extraFieldsValues )
|
||||
: {};
|
||||
const validExtraFields = Object.keys( extraFieldsErrors ).length === 0;
|
||||
if ( missingFirstOrSecondQuestions || ! validExtraFields ) {
|
||||
setErrors( extraFieldsErrors );
|
||||
return;
|
||||
}
|
||||
setOpen( false );
|
||||
recordScoreCallback(
|
||||
firstQuestionScore,
|
||||
secondQuestionScore,
|
||||
comments
|
||||
comments,
|
||||
extraFieldsValues
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -144,20 +175,22 @@ function CustomerFeedbackModal( {
|
|||
onRequestClose={ closeModal }
|
||||
shouldCloseOnClickOutside={ false }
|
||||
>
|
||||
<Text
|
||||
variant="body"
|
||||
as="p"
|
||||
className="woocommerce-customer-effort-score__intro"
|
||||
size={ 14 }
|
||||
lineHeight="20px"
|
||||
marginBottom="1.5em"
|
||||
>
|
||||
{ description ||
|
||||
__(
|
||||
'Your feedback will help create a better experience for thousands of merchants like you. Please tell us to what extent you agree or disagree with the statements below.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Text>
|
||||
{ showDescription && (
|
||||
<Text
|
||||
variant="body"
|
||||
as="p"
|
||||
className="woocommerce-customer-effort-score__intro"
|
||||
size={ 14 }
|
||||
lineHeight="20px"
|
||||
marginBottom="1.5em"
|
||||
>
|
||||
{ description ||
|
||||
__(
|
||||
'Your feedback will help create a better experience for thousands of merchants like you. Please tell us to what extent you agree or disagree with the statements below.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Text>
|
||||
) }
|
||||
|
||||
<Text
|
||||
variant="subtitle.small"
|
||||
|
@ -244,13 +277,20 @@ function CustomerFeedbackModal( {
|
|||
>
|
||||
<Text variant="body" as="p">
|
||||
{ __(
|
||||
'Please provide feedback by selecting an option above.',
|
||||
'Please tell us to what extent you agree or disagree with the statements above.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Text>
|
||||
</div>
|
||||
) }
|
||||
|
||||
{ typeof getExtraFieldsToBeShown === 'function' &&
|
||||
getExtraFieldsToBeShown(
|
||||
extraFieldsValues,
|
||||
setExtraFieldsValues,
|
||||
errors
|
||||
) }
|
||||
|
||||
<div className="woocommerce-customer-effort-score__buttons">
|
||||
<Button isTertiary onClick={ closeModal } name="cancel">
|
||||
{ __( 'Cancel', 'woocommerce' ) }
|
||||
|
@ -270,6 +310,8 @@ CustomerFeedbackModal.propTypes = {
|
|||
secondQuestion: PropTypes.string,
|
||||
defaultScore: PropTypes.number,
|
||||
onCloseModal: PropTypes.func,
|
||||
getExtraFieldsToBeShown: PropTypes.func,
|
||||
validateExtraFields: PropTypes.func,
|
||||
};
|
||||
|
||||
export { CustomerFeedbackModal };
|
||||
|
|
|
@ -26,6 +26,7 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
|
|||
case TYPES.SHOW_CES_MODAL:
|
||||
const cesModalData = {
|
||||
action: action.surveyProps.action,
|
||||
showDescription: action.surveyProps.showDescription,
|
||||
title: action.surveyProps.title,
|
||||
onSubmitLabel: action.onSubmitLabel,
|
||||
firstQuestion: action.surveyProps.firstQuestion,
|
||||
|
@ -33,6 +34,9 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
|
|||
onSubmitNoticeProps: action.onSubmitNoticeProps || {},
|
||||
props: action.props,
|
||||
tracksProps: action.tracksProps,
|
||||
getExtraFieldsToBeShown:
|
||||
action.surveyProps.getExtraFieldsToBeShown,
|
||||
validateExtraFields: action.surveyProps.validateExtraFields,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add Jetpack Connection Auth endpoint.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Redirect users to WooCommerce Home when Jetpack auth endpoint returns an invalid URL
|
|
@ -36,6 +36,7 @@ const TYPES = {
|
|||
VISITED_TASK: 'VISITED_TASK',
|
||||
KEEP_COMPLETED_TASKS_REQUEST: 'KEEP_COMPLETED_TASKS_REQUEST',
|
||||
KEEP_COMPLETED_TASKS_SUCCESS: 'KEEP_COMPLETED_TASKS_SUCCESS',
|
||||
SET_JETPACK_AUTH_URL: 'SET_JETPACK_AUTH_URL',
|
||||
} as const;
|
||||
|
||||
export default TYPES;
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
TaskType,
|
||||
OnboardingProductTypes,
|
||||
InstallAndActivatePluginsAsyncResponse,
|
||||
GetJetpackAuthUrlResponse,
|
||||
} from './types';
|
||||
import { Plugin, PluginNames } from '../plugins/types';
|
||||
|
||||
|
@ -488,6 +489,19 @@ export function* installAndActivatePluginsAsync(
|
|||
}
|
||||
}
|
||||
|
||||
export function setJetpackAuthUrl(
|
||||
results: GetJetpackAuthUrlResponse,
|
||||
redirectUrl: string,
|
||||
from = ''
|
||||
) {
|
||||
return {
|
||||
type: TYPES.SET_JETPACK_AUTH_URL,
|
||||
results,
|
||||
redirectUrl,
|
||||
from,
|
||||
};
|
||||
}
|
||||
|
||||
export type Action = ReturnType<
|
||||
| typeof getFreeExtensionsError
|
||||
| typeof getFreeExtensionsSuccess
|
||||
|
@ -524,4 +538,5 @@ export type Action = ReturnType<
|
|||
| typeof actionTaskRequest
|
||||
| typeof getProductTypesError
|
||||
| typeof getProductTypesSuccess
|
||||
| typeof setJetpackAuthUrl
|
||||
>;
|
||||
|
|
|
@ -38,6 +38,7 @@ export const defaultState: OnboardingState = {
|
|||
productTypes: {},
|
||||
requesting: {},
|
||||
taskLists: {},
|
||||
jetpackAuthUrls: {},
|
||||
};
|
||||
|
||||
const getUpdatedTaskLists = (
|
||||
|
@ -428,6 +429,14 @@ const reducer: Reducer< OnboardingState, Action > = (
|
|||
},
|
||||
taskLists: getUpdatedTaskLists( state.taskLists, action.task ),
|
||||
};
|
||||
case TYPES.SET_JETPACK_AUTH_URL:
|
||||
return {
|
||||
...state,
|
||||
jetpackAuthUrls: {
|
||||
...state.jetpackAuthUrls,
|
||||
[ action.redirectUrl ]: action.results,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,12 @@ import {
|
|||
setEmailPrefill,
|
||||
getProductTypesSuccess,
|
||||
getProductTypesError,
|
||||
setJetpackAuthUrl,
|
||||
} from './actions';
|
||||
import { DeprecatedTasks } from './deprecated-tasks';
|
||||
import {
|
||||
ExtensionList,
|
||||
GetJetpackAuthUrlResponse,
|
||||
OnboardingProductTypes,
|
||||
ProfileItems,
|
||||
TaskListType,
|
||||
|
@ -136,3 +138,28 @@ export function* getProductTypes() {
|
|||
yield getProductTypesError( error );
|
||||
}
|
||||
}
|
||||
|
||||
export function* getJetpackAuthUrl( query: {
|
||||
redirectUrl: string;
|
||||
from?: string;
|
||||
} ) {
|
||||
try {
|
||||
let path =
|
||||
WC_ADMIN_NAMESPACE +
|
||||
'/onboarding/plugins/jetpack-authorization-url?redirect_url=' +
|
||||
encodeURIComponent( query.redirectUrl );
|
||||
|
||||
if ( query.from ) {
|
||||
path += '&from=' + query.from;
|
||||
}
|
||||
|
||||
const results: GetJetpackAuthUrlResponse = yield apiFetch( {
|
||||
path,
|
||||
method: 'GET',
|
||||
} );
|
||||
|
||||
yield setJetpackAuthUrl( results, query.redirectUrl, query.from ?? '' );
|
||||
} catch ( error ) {
|
||||
yield setError( 'getJetpackAuthUrl', error );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
OnboardingState,
|
||||
ExtensionList,
|
||||
ProfileItems,
|
||||
GetJetpackAuthUrlResponse,
|
||||
} from './types';
|
||||
import { WPDataSelectors } from '../types';
|
||||
import { Plugin } from '../plugins/types';
|
||||
|
@ -95,6 +96,16 @@ export const getProductTypes = ( state: OnboardingState ) => {
|
|||
return state.productTypes || {};
|
||||
};
|
||||
|
||||
export const getJetpackAuthUrl = (
|
||||
state: OnboardingState,
|
||||
query: {
|
||||
redirectUrl: string;
|
||||
from?: string;
|
||||
}
|
||||
): GetJetpackAuthUrlResponse => {
|
||||
return state.jetpackAuthUrls[ query.redirectUrl ] || '';
|
||||
};
|
||||
|
||||
export type OnboardingSelectors = {
|
||||
getProfileItems: () => ReturnType< typeof getProfileItems >;
|
||||
getPaymentGatewaySuggestions: () => ReturnType<
|
||||
|
|
|
@ -57,6 +57,7 @@ describe( 'plugins reducer', () => {
|
|||
emailPrefill: '',
|
||||
errors: {},
|
||||
requesting: {},
|
||||
jetpackAuthUrls: {},
|
||||
},
|
||||
{
|
||||
type: TYPES.SET_PROFILE_ITEMS,
|
||||
|
@ -79,6 +80,7 @@ describe( 'plugins reducer', () => {
|
|||
emailPrefill: '',
|
||||
errors: {},
|
||||
requesting: {},
|
||||
jetpackAuthUrls: {},
|
||||
},
|
||||
{
|
||||
type: TYPES.SET_PROFILE_ITEMS,
|
||||
|
|
|
@ -80,12 +80,19 @@ export type OnboardingState = {
|
|||
// TODO clarify what the error record's type is
|
||||
errors: Record< string, unknown >;
|
||||
requesting: Record< string, boolean >;
|
||||
jetpackAuthUrls: Record< string, GetJetpackAuthUrlResponse >;
|
||||
};
|
||||
|
||||
export type Industry = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type GetJetpackAuthUrlResponse = {
|
||||
url: string;
|
||||
success: boolean;
|
||||
errors: string[];
|
||||
};
|
||||
|
||||
export type ProductCount = '0' | '1-10' | '11-100' | '101 - 1000' | '1000+';
|
||||
|
||||
export type ProductTypeSlug =
|
||||
|
@ -191,7 +198,7 @@ export type Extension = {
|
|||
|
||||
export type InstallAndActivatePluginsAsyncResponse = {
|
||||
job_id: string;
|
||||
status: 'pending' | 'in-progress' | 'completed' | 'failed';
|
||||
status: 'pendi<ng' | 'in-progress' | 'completed' | 'failed';
|
||||
plugins: Array< {
|
||||
status: 'pending' | 'installing' | 'installed' | 'activated' | 'failed';
|
||||
errors: string[];
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add tool selector option to iframe editor
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Fix CES modal
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Fix empty state for currency inputs in product editor
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix overlapping TransientNotices with product editor footer
|
|
@ -86,13 +86,17 @@ export function Editor( { product, settings }: EditorProps ) {
|
|||
<PluginArea scope="woocommerce-product-block-editor" />
|
||||
</>
|
||||
}
|
||||
footer={ <Footer product={ product } /> }
|
||||
/>
|
||||
|
||||
<Popover.Slot />
|
||||
</ValidationProvider>
|
||||
</SlotFillProvider>
|
||||
</ShortcutProvider>
|
||||
{ /* We put Footer here instead of in InterfaceSkeleton because Footer uses
|
||||
WooFooterItem to actually render in the WooFooterItem.Slot defined by
|
||||
WooCommerce Admin. And, we need to put it outside of the SlotFillProvider
|
||||
we create in this component. */ }
|
||||
<Footer product={ product } />
|
||||
</EntityProvider>
|
||||
</StrictMode>
|
||||
</LayoutContextProvider>
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import {
|
||||
BaseControl,
|
||||
Button,
|
||||
TextControl,
|
||||
TextareaControl,
|
||||
} from '@wordpress/components';
|
||||
import {
|
||||
createElement,
|
||||
createInterpolateElement,
|
||||
Fragment,
|
||||
} from '@wordpress/element';
|
||||
import { closeSmall } from '@wordpress/icons';
|
||||
import { WooFooterItem } from '@woocommerce/admin-layout';
|
||||
import { Pill } from '@woocommerce/components';
|
||||
import { useCustomerEffortScoreModal } from '@woocommerce/customer-effort-score';
|
||||
import { Product } from '@woocommerce/data';
|
||||
|
@ -20,6 +24,7 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
*/
|
||||
import { PRODUCT_EDITOR_FEEDBACK_CES_ACTION } from '../../constants';
|
||||
import { useFeedbackBar } from '../../hooks/use-feedback-bar';
|
||||
import { isValidEmail } from '../../utils';
|
||||
|
||||
export type FeedbackBarProps = {
|
||||
product: Partial< Product >;
|
||||
|
@ -46,8 +51,9 @@ export function FeedbackBar( { product }: FeedbackBarProps ) {
|
|||
showCesModal(
|
||||
{
|
||||
action: PRODUCT_EDITOR_FEEDBACK_CES_ACTION,
|
||||
showDescription: false,
|
||||
title: __(
|
||||
"How's your experience with the product editor?",
|
||||
"How's your experience with the new product form?",
|
||||
'woocommerce'
|
||||
),
|
||||
firstQuestion: __(
|
||||
|
@ -55,14 +61,115 @@ export function FeedbackBar( { product }: FeedbackBarProps ) {
|
|||
'woocommerce'
|
||||
),
|
||||
secondQuestion: __(
|
||||
"The product editing screen's functionality meets my needs",
|
||||
'Product form is easy to use',
|
||||
'woocommerce'
|
||||
),
|
||||
onsubmitLabel: __(
|
||||
"Thanks for the feedback. We'll put it to good use!",
|
||||
'woocommerce'
|
||||
),
|
||||
shouldShowComments: () => true,
|
||||
shouldShowComments: () => false,
|
||||
getExtraFieldsToBeShown: (
|
||||
values: {
|
||||
email?: string;
|
||||
additional_thoughts?: string;
|
||||
},
|
||||
setValues: ( value: {
|
||||
email?: string;
|
||||
additional_thoughts?: string;
|
||||
} ) => void,
|
||||
errors: Record< string, string > | undefined
|
||||
) => (
|
||||
<Fragment>
|
||||
<BaseControl
|
||||
id={ 'feedback_additional_thoughts' }
|
||||
className="woocommerce-product-feedback__additional-thoughts"
|
||||
label={ createInterpolateElement(
|
||||
__(
|
||||
'ADDITIONAL THOUGHTS <optional />',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
optional: (
|
||||
<span className="woocommerce-product-feedback__optional-input">
|
||||
{ __(
|
||||
'(OPTIONAL)',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
),
|
||||
}
|
||||
) }
|
||||
>
|
||||
<TextareaControl
|
||||
value={ values.additional_thoughts || '' }
|
||||
onChange={ ( value: string ) =>
|
||||
setValues( {
|
||||
...values,
|
||||
additional_thoughts: value,
|
||||
} )
|
||||
}
|
||||
help={ errors?.additional_thoughts || '' }
|
||||
/>
|
||||
</BaseControl>
|
||||
<BaseControl
|
||||
id={ 'feedback_email' }
|
||||
className="woocommerce-product-feedback__email"
|
||||
label={ createInterpolateElement(
|
||||
__(
|
||||
'YOUR EMAIL ADDRESS <optional />',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
optional: (
|
||||
<span className="woocommerce-product-feedback__optional-input">
|
||||
{ __(
|
||||
'(OPTIONAL)',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
),
|
||||
}
|
||||
) }
|
||||
>
|
||||
<TextControl
|
||||
value={ values.email || '' }
|
||||
onChange={ ( value: string ) =>
|
||||
setValues( { ...values, email: value } )
|
||||
}
|
||||
help={ errors?.email || '' }
|
||||
/>
|
||||
<span>
|
||||
{ __(
|
||||
'In case you want to participate in further discussion and future user research.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
</BaseControl>
|
||||
</Fragment>
|
||||
),
|
||||
validateExtraFields: ( {
|
||||
email = '',
|
||||
additional_thoughts = '',
|
||||
}: {
|
||||
email?: string;
|
||||
additional_thoughts?: string;
|
||||
} ) => {
|
||||
const errors: Record< string, string > | undefined = {};
|
||||
if ( email.length > 0 && ! isValidEmail( email ) ) {
|
||||
errors.email = __(
|
||||
'Please enter a valid email address.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
if ( additional_thoughts?.length > 500 ) {
|
||||
errors.additional_thoughts = __(
|
||||
'Please enter no more than 500 characters.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
return errors;
|
||||
},
|
||||
},
|
||||
{},
|
||||
{
|
||||
|
@ -93,42 +200,40 @@ export function FeedbackBar( { product }: FeedbackBarProps ) {
|
|||
return (
|
||||
<>
|
||||
{ shouldShowFeedbackBar && (
|
||||
<WooFooterItem>
|
||||
<div className="woocommerce-product-mvp-ces-footer">
|
||||
<Pill>Beta</Pill>
|
||||
<div className="woocommerce-product-mvp-ces-footer__message">
|
||||
{ createInterpolateElement(
|
||||
__(
|
||||
'How is your experience with the new product form? <span><shareButton>Share feedback</shareButton> or <turnOffButton>turn it off</turnOffButton></span>',
|
||||
'woocommerce'
|
||||
<div className="woocommerce-product-mvp-ces-footer">
|
||||
<Pill>Beta</Pill>
|
||||
<div className="woocommerce-product-mvp-ces-footer__message">
|
||||
{ createInterpolateElement(
|
||||
__(
|
||||
'How is your experience with the new product form? <span><shareButton>Share feedback</shareButton> or <turnOffButton>turn it off</turnOffButton></span>',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
span: (
|
||||
<span className="woocommerce-product-mvp-ces-footer__message-buttons" />
|
||||
),
|
||||
{
|
||||
span: (
|
||||
<span className="woocommerce-product-mvp-ces-footer__message-buttons" />
|
||||
),
|
||||
shareButton: (
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={ onShareFeedbackClick }
|
||||
/>
|
||||
),
|
||||
turnOffButton: (
|
||||
<Button
|
||||
onClick={ onTurnOffEditorClick }
|
||||
variant="link"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
</div>
|
||||
<Button
|
||||
className="woocommerce-product-mvp-ces-footer__close-button"
|
||||
icon={ closeSmall }
|
||||
label={ __( 'Hide this message', 'woocommerce' ) }
|
||||
onClick={ onHideFeedbackBarClick }
|
||||
></Button>
|
||||
shareButton: (
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={ onShareFeedbackClick }
|
||||
/>
|
||||
),
|
||||
turnOffButton: (
|
||||
<Button
|
||||
onClick={ onTurnOffEditorClick }
|
||||
variant="link"
|
||||
/>
|
||||
),
|
||||
}
|
||||
) }
|
||||
</div>
|
||||
</WooFooterItem>
|
||||
<Button
|
||||
className="woocommerce-product-mvp-ces-footer__close-button"
|
||||
icon={ closeSmall }
|
||||
label={ __( 'Hide this message', 'woocommerce' ) }
|
||||
onClick={ onHideFeedbackBarClick }
|
||||
></Button>
|
||||
</div>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
$gutenberg-blue: var(--wp-admin-theme-color);
|
||||
|
||||
.woocommerce-product-mvp-ces-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -40,3 +42,59 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.components-modal__content {
|
||||
> p {
|
||||
text-transform: uppercase;
|
||||
color: $gray-900;
|
||||
}
|
||||
> .components-base-control {
|
||||
margin-top: $gap-small;
|
||||
}
|
||||
.woocommerce-customer-effort-score {
|
||||
&__selection {
|
||||
.components-radio-control__option {
|
||||
margin-right: 0;
|
||||
label {
|
||||
color: $gray-900;
|
||||
padding: 1em 0;
|
||||
width: 8.8em;
|
||||
height: 80px;
|
||||
&::before {
|
||||
margin: $gap-smaller 0;
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1.5px $gutenberg-blue;
|
||||
border-radius: 2px;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&__errors {
|
||||
> p {
|
||||
color: $error-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
.woocommerce-product-feedback__additional-thoughts,
|
||||
.woocommerce-product-feedback__email {
|
||||
.components-base-control__help {
|
||||
color: $error-red;
|
||||
}
|
||||
.components-base-control__label {
|
||||
color: $gray-900;
|
||||
line-height: 16px;
|
||||
text-transform: uppercase;
|
||||
.woocommerce-product-feedback__optional-input {
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
.components-base-control__field {
|
||||
> span {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { WooFooterItem } from '@woocommerce/admin-layout';
|
||||
import { Product } from '@woocommerce/data';
|
||||
|
||||
|
@ -18,16 +17,11 @@ export type FooterProps = {
|
|||
|
||||
export function Footer( { product }: FooterProps ) {
|
||||
return (
|
||||
<div
|
||||
className="woocommerce-product-footer"
|
||||
role="region"
|
||||
aria-label={ __( 'Product Editor bottom bar.', 'woocommerce' ) }
|
||||
tabIndex={ -1 }
|
||||
>
|
||||
<WooFooterItem.Slot name="product" />
|
||||
|
||||
<FeedbackBar product={ product } />
|
||||
<ProductMVPFeedbackModalContainer productId={ product.id } />
|
||||
</div>
|
||||
<WooFooterItem>
|
||||
<>
|
||||
<FeedbackBar product={ product } />
|
||||
<ProductMVPFeedbackModalContainer productId={ product.id } />
|
||||
</>
|
||||
</WooFooterItem>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
import { useSelect } from '@wordpress/data';
|
||||
import { useViewportMatch } from '@wordpress/compose';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
NavigableToolbar,
|
||||
store as blockEditorStore,
|
||||
} from '@wordpress/block-editor';
|
||||
import { plus } from '@wordpress/icons';
|
||||
import {
|
||||
createElement,
|
||||
|
@ -17,6 +13,13 @@ import {
|
|||
useContext,
|
||||
} from '@wordpress/element';
|
||||
import { MouseEvent } from 'react';
|
||||
import {
|
||||
NavigableToolbar,
|
||||
store as blockEditorStore,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ToolSelector exists in WordPress components.
|
||||
ToolSelector,
|
||||
} from '@wordpress/block-editor';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore ToolbarItem exists in WordPress components.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
|
@ -33,8 +36,9 @@ export function HeaderToolbar() {
|
|||
const { isInserterOpened, setIsInserterOpened } =
|
||||
useContext( EditorContext );
|
||||
const isWideViewport = useViewportMatch( 'wide' );
|
||||
const isLargeViewport = useViewportMatch( 'medium' );
|
||||
const inserterButton = useRef< HTMLButtonElement | null >( null );
|
||||
const { isInserterEnabled } = useSelect( ( select ) => {
|
||||
const { isInserterEnabled, isTextModeEnabled } = useSelect( ( select ) => {
|
||||
const {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore These selectors are available in the block data store.
|
||||
|
@ -45,9 +49,13 @@ export function HeaderToolbar() {
|
|||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore These selectors are available in the block data store.
|
||||
getBlockSelectionEnd,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore These selectors are available in the block data store.
|
||||
__unstableGetEditorMode: getEditorMode,
|
||||
} = select( blockEditorStore );
|
||||
|
||||
return {
|
||||
isTextModeEnabled: getEditorMode() === 'text',
|
||||
isInserterEnabled: hasInserterItems(
|
||||
getBlockRootClientId( getBlockSelectionEnd() )
|
||||
),
|
||||
|
@ -98,6 +106,12 @@ export function HeaderToolbar() {
|
|||
/>
|
||||
{ isWideViewport && (
|
||||
<>
|
||||
{ isLargeViewport && (
|
||||
<ToolbarItem
|
||||
as={ ToolSelector }
|
||||
disabled={ isTextModeEnabled }
|
||||
/>
|
||||
) }
|
||||
<ToolbarItem as={ EditorHistoryUndo } />
|
||||
<ToolbarItem as={ EditorHistoryRedo } />
|
||||
</>
|
||||
|
|
|
@ -81,7 +81,7 @@ export const useCurrencyInputProps = ( {
|
|||
}
|
||||
},
|
||||
onChange( newValue: string ) {
|
||||
const sanitizeValue = sanitizePrice( newValue || '0' );
|
||||
const sanitizeValue = sanitizePrice( newValue );
|
||||
if ( onChange ) {
|
||||
onChange( sanitizeValue );
|
||||
}
|
||||
|
|
|
@ -312,6 +312,10 @@ export function useProductHelper() {
|
|||
*/
|
||||
const sanitizePrice = useCallback(
|
||||
( price: string ) => {
|
||||
if ( ! price.length ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { getCurrencyConfig } = context;
|
||||
const { decimalSeparator } = getCurrencyConfig();
|
||||
// Build regex to strip out everything except digits, decimal point and minus sign.
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
getTruncatedProductVariationTitle,
|
||||
} from './get-product-variation-title';
|
||||
import { preventLeavingProductForm } from './prevent-leaving-product-form';
|
||||
import { isValidEmail } from './validate-email';
|
||||
|
||||
export * from './create-ordered-children';
|
||||
export * from './sort-fills-by-order';
|
||||
|
@ -38,6 +39,7 @@ export {
|
|||
getProductTitle,
|
||||
getProductVariationTitle,
|
||||
getTruncatedProductVariationTitle,
|
||||
isValidEmail,
|
||||
preventLeavingProductForm,
|
||||
PRODUCT_STATUS_LABELS,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Checks if the provided email address is valid.
|
||||
*
|
||||
* @param {string} email - The email address to be tested.
|
||||
* @return {boolean} Returns true if the email address is valid.
|
||||
*/
|
||||
export const isValidEmail = ( email: string ) => {
|
||||
const re =
|
||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
return re.test( String( email ).toLowerCase() );
|
||||
};
|
|
@ -12,8 +12,15 @@ import {
|
|||
UserProfileEvent,
|
||||
BusinessInfoEvent,
|
||||
PluginsLearnMoreLinkClicked,
|
||||
PluginsInstallationCompletedWithErrorsEvent,
|
||||
PluginsInstallationCompletedEvent,
|
||||
} from '..';
|
||||
import { POSSIBLY_DEFAULT_STORE_NAMES } from '../pages/BusinessInfo';
|
||||
import {
|
||||
InstalledPlugin,
|
||||
PluginInstallError,
|
||||
} from '../services/installAndActivatePlugins';
|
||||
import { getPluginTrackKey, getTimeFrame } from '~/utils';
|
||||
|
||||
const recordTracksStepViewed = (
|
||||
_context: unknown,
|
||||
|
@ -21,7 +28,7 @@ const recordTracksStepViewed = (
|
|||
{ action }: { action: unknown }
|
||||
) => {
|
||||
const { step } = action as { step: string };
|
||||
recordEvent( 'storeprofiler_step_view', {
|
||||
recordEvent( 'coreprofiler_step_view', {
|
||||
step,
|
||||
wc_version: getSetting( 'wcVersion' ),
|
||||
} );
|
||||
|
@ -33,12 +40,12 @@ const recordTracksStepSkipped = (
|
|||
{ action }: { action: unknown }
|
||||
) => {
|
||||
const { step } = action as { step: string };
|
||||
recordEvent( `storeprofiler_${ step }_skip` );
|
||||
recordEvent( `coreprofiler_${ step }_skip` );
|
||||
};
|
||||
|
||||
const recordTracksIntroCompleted = () => {
|
||||
recordEvent( 'storeprofiler_step_complete', {
|
||||
step: 'store_details',
|
||||
recordEvent( 'coreprofiler_step_complete', {
|
||||
step: 'intro_opt_in',
|
||||
wc_version: getSetting( 'wcVersion' ),
|
||||
} );
|
||||
};
|
||||
|
@ -47,12 +54,12 @@ const recordTracksUserProfileCompleted = (
|
|||
_context: CoreProfilerStateMachineContext,
|
||||
event: Extract< UserProfileEvent, { type: 'USER_PROFILE_COMPLETED' } >
|
||||
) => {
|
||||
recordEvent( 'storeprofiler_step_complete', {
|
||||
recordEvent( 'coreprofiler_step_complete', {
|
||||
step: 'user_profile',
|
||||
wc_version: getSetting( 'wcVersion' ),
|
||||
} );
|
||||
|
||||
recordEvent( 'storeprofiler_user_profile', {
|
||||
recordEvent( 'coreprofiler_user_profile', {
|
||||
business_choice: event.payload.userProfile.businessChoice,
|
||||
selling_online_answer: event.payload.userProfile.sellingOnlineAnswer,
|
||||
selling_platforms: event.payload.userProfile.sellingPlatforms
|
||||
|
@ -62,7 +69,7 @@ const recordTracksUserProfileCompleted = (
|
|||
};
|
||||
|
||||
const recordTracksSkipBusinessLocationCompleted = () => {
|
||||
recordEvent( 'storeprofiler_step_complete', {
|
||||
recordEvent( 'coreprofiler_step_complete', {
|
||||
step: 'skip_business_location',
|
||||
wc_version: getSetting( 'wcVersion' ),
|
||||
} );
|
||||
|
@ -72,12 +79,12 @@ const recordTracksBusinessInfoCompleted = (
|
|||
_context: CoreProfilerStateMachineContext,
|
||||
event: Extract< BusinessInfoEvent, { type: 'BUSINESS_INFO_COMPLETED' } >
|
||||
) => {
|
||||
recordEvent( 'storeprofiler_step_complete', {
|
||||
recordEvent( 'coreprofiler_step_complete', {
|
||||
step: 'business_info',
|
||||
wc_version: getSetting( 'wcVersion' ),
|
||||
} );
|
||||
|
||||
recordEvent( 'storeprofiler_business_info', {
|
||||
recordEvent( 'coreprofiler_business_info', {
|
||||
business_name_filled:
|
||||
POSSIBLY_DEFAULT_STORE_NAMES.findIndex(
|
||||
( name ) => name === event.payload.storeName
|
||||
|
@ -96,12 +103,57 @@ const recordTracksPluginsLearnMoreLinkClicked = (
|
|||
{ action }: { action: unknown }
|
||||
) => {
|
||||
const { step } = action as { step: string };
|
||||
recordEvent( `storeprofiler_${ step }_learn_more_link_clicked`, {
|
||||
recordEvent( `coreprofiler_${ step }_learn_more_link_clicked`, {
|
||||
plugin: _event.payload.plugin,
|
||||
link: _event.payload.learnMoreLink,
|
||||
} );
|
||||
};
|
||||
|
||||
const recordFailedPluginInstallations = (
|
||||
_context: unknown,
|
||||
_event: PluginsInstallationCompletedWithErrorsEvent
|
||||
) => {
|
||||
recordEvent( 'coreprofiler_store_extensions_installed_and_activated', {
|
||||
success: false,
|
||||
failed_extensions: _event.payload.errors.map(
|
||||
( error: PluginInstallError ) => getPluginTrackKey( error.plugin )
|
||||
),
|
||||
} );
|
||||
};
|
||||
|
||||
const recordSuccessfulPluginInstallation = (
|
||||
_context: unknown,
|
||||
_event: PluginsInstallationCompletedEvent
|
||||
) => {
|
||||
const installationCompletedResult =
|
||||
_event.payload.installationCompletedResult;
|
||||
|
||||
const trackData: {
|
||||
success: boolean;
|
||||
installed_extensions: string[];
|
||||
total_time: string;
|
||||
[ key: string ]: number | boolean | string | string[];
|
||||
} = {
|
||||
success: true,
|
||||
installed_extensions: installationCompletedResult.installedPlugins.map(
|
||||
( installedPlugin: InstalledPlugin ) =>
|
||||
getPluginTrackKey( installedPlugin.plugin )
|
||||
),
|
||||
total_time: getTimeFrame( installationCompletedResult.totalTime ),
|
||||
};
|
||||
|
||||
for ( const installedPlugin of installationCompletedResult.installedPlugins ) {
|
||||
trackData[
|
||||
'install_time_' + getPluginTrackKey( installedPlugin.plugin )
|
||||
] = getTimeFrame( installedPlugin.installTime );
|
||||
}
|
||||
|
||||
recordEvent(
|
||||
'coreprofiler_store_extensions_installed_and_activated',
|
||||
trackData
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
recordTracksStepViewed,
|
||||
recordTracksStepSkipped,
|
||||
|
@ -110,4 +162,6 @@ export default {
|
|||
recordTracksSkipBusinessLocationCompleted,
|
||||
recordTracksBusinessInfoCompleted,
|
||||
recordTracksPluginsLearnMoreLinkClicked,
|
||||
recordFailedPluginInstallations,
|
||||
recordSuccessfulPluginInstallation,
|
||||
};
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.components-checkbox-control__input-container {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 3px 26px 0 0;
|
||||
width: 20px;
|
||||
|
|
|
@ -16,7 +16,12 @@ import {
|
|||
import { useMachine, useSelector } from '@xstate/react';
|
||||
import { useEffect, useMemo } from '@wordpress/element';
|
||||
import { resolveSelect, dispatch } from '@wordpress/data';
|
||||
import { updateQueryString, getQuery } from '@woocommerce/navigation';
|
||||
import {
|
||||
updateQueryString,
|
||||
getQuery,
|
||||
getNewPath,
|
||||
navigateTo,
|
||||
} from '@woocommerce/navigation';
|
||||
import {
|
||||
ExtensionList,
|
||||
OPTIONS_STORE_NAME,
|
||||
|
@ -25,10 +30,11 @@ import {
|
|||
ONBOARDING_STORE_NAME,
|
||||
Extension,
|
||||
GeolocationResponse,
|
||||
PLUGINS_STORE_NAME,
|
||||
} from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { initializeExPlat } from '@woocommerce/explat';
|
||||
import { CountryStateOption } from '@woocommerce/onboarding';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -49,7 +55,7 @@ import { BusinessLocation } from './pages/BusinessLocation';
|
|||
import { getCountryStateOptions } from './services/country';
|
||||
import { Loader } from './pages/Loader';
|
||||
import { Plugins } from './pages/Plugins';
|
||||
import { getPluginSlug, getPluginTrackKey, getTimeFrame } from '~/utils';
|
||||
import { getPluginSlug } from '~/utils';
|
||||
import './style.scss';
|
||||
import {
|
||||
InstallationCompletedResult,
|
||||
|
@ -174,6 +180,7 @@ export type CoreProfilerStateMachineContext = {
|
|||
stageIndex?: number;
|
||||
};
|
||||
onboardingProfile: OnboardingProfile;
|
||||
jetpackAuthUrl?: string;
|
||||
};
|
||||
|
||||
const getAllowTrackingOption = async () =>
|
||||
|
@ -320,10 +327,16 @@ const handleGeolocation = assign( {
|
|||
} );
|
||||
|
||||
const redirectToWooHome = () => {
|
||||
/**
|
||||
* @todo replace with navigateTo
|
||||
*/
|
||||
window.location.href = '/wp-admin/admin.php?page=wc-admin';
|
||||
navigateTo( {
|
||||
url: getNewPath( {}, '/', {} ),
|
||||
} );
|
||||
};
|
||||
|
||||
const redirectToJetpackAuthPage = (
|
||||
_context: CoreProfilerStateMachineContext,
|
||||
event: { data: { url: string } }
|
||||
) => {
|
||||
window.location.href = event.data.url + '&installed_ext_success=1';
|
||||
};
|
||||
|
||||
const updateTrackingOption = (
|
||||
|
@ -364,6 +377,16 @@ const updateOnboardingProfileOption = (
|
|||
} );
|
||||
};
|
||||
|
||||
const spawnUpdateOnboardingProfileOption = assign( {
|
||||
spawnUpdateOnboardingProfileOptionRef: (
|
||||
context: CoreProfilerStateMachineContext
|
||||
) =>
|
||||
spawn(
|
||||
() => updateOnboardingProfileOption( context ),
|
||||
'update-onboarding-profile'
|
||||
),
|
||||
} );
|
||||
|
||||
const updateBusinessLocation = ( countryAndState: string ) => {
|
||||
return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
|
||||
woocommerce_default_country: countryAndState,
|
||||
|
@ -389,7 +412,7 @@ const assignUserProfile = assign( {
|
|||
|
||||
const updateBusinessInfo = async (
|
||||
_context: CoreProfilerStateMachineContext,
|
||||
event: BusinessInfoEvent
|
||||
event: AnyEventObject
|
||||
) => {
|
||||
const refreshedOnboardingProfile = ( await resolveSelect(
|
||||
OPTIONS_STORE_NAME
|
||||
|
@ -518,7 +541,9 @@ const coreProfilerMachineActions = {
|
|||
handleOnboardingProfileOption,
|
||||
assignOnboardingProfile,
|
||||
persistBusinessInfo,
|
||||
spawnUpdateOnboardingProfileOption,
|
||||
redirectToWooHome,
|
||||
redirectToJetpackAuthPage,
|
||||
};
|
||||
|
||||
const coreProfilerMachineServices = {
|
||||
|
@ -530,6 +555,7 @@ const coreProfilerMachineServices = {
|
|||
getOnboardingProfileOption,
|
||||
getPlugins,
|
||||
browserPopstateHandler,
|
||||
updateBusinessInfo,
|
||||
};
|
||||
export const coreProfilerStateMachineDefinition = createMachine( {
|
||||
id: 'coreProfiler',
|
||||
|
@ -561,6 +587,7 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
pluginsSelected: [],
|
||||
loader: {},
|
||||
onboardingProfile: {} as OnboardingProfile,
|
||||
jetpackAuthUrl: undefined,
|
||||
} as CoreProfilerStateMachineContext,
|
||||
states: {
|
||||
navigate: {
|
||||
|
@ -681,7 +708,7 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
entry: [
|
||||
{
|
||||
type: 'recordTracksStepViewed',
|
||||
step: 'store_details',
|
||||
step: 'intro_opt_in',
|
||||
},
|
||||
{ type: 'updateQueryStep', step: 'intro-opt-in' },
|
||||
],
|
||||
|
@ -697,7 +724,7 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
actions: [
|
||||
{
|
||||
type: 'recordTracksStepSkipped',
|
||||
step: 'store_details',
|
||||
step: 'intro_opt_in',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -772,16 +799,9 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
] ),
|
||||
},
|
||||
postUserProfile: {
|
||||
invoke: {
|
||||
src: ( context ) => {
|
||||
return updateOnboardingProfileOption( context );
|
||||
},
|
||||
onDone: {
|
||||
target: '#businessInfo',
|
||||
},
|
||||
onError: {
|
||||
target: '#businessInfo',
|
||||
},
|
||||
entry: [ 'spawnUpdateOnboardingProfileOption' ],
|
||||
always: {
|
||||
target: '#businessInfo',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -938,11 +958,19 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
],
|
||||
on: {
|
||||
BUSINESS_INFO_COMPLETED: {
|
||||
target: 'postBusinessInfo',
|
||||
actions: [ 'recordTracksBusinessInfoCompleted' ],
|
||||
},
|
||||
},
|
||||
},
|
||||
postBusinessInfo: {
|
||||
invoke: {
|
||||
src: 'updateBusinessInfo',
|
||||
onDone: {
|
||||
target: '#plugins',
|
||||
},
|
||||
onError: {
|
||||
target: '#plugins',
|
||||
actions: [
|
||||
'persistBusinessInfo',
|
||||
'recordTracksBusinessInfoCompleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -999,9 +1027,20 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
} ),
|
||||
invoke: {
|
||||
src: ( context ) => {
|
||||
return updateBusinessLocation(
|
||||
context.businessInfo.location as string
|
||||
);
|
||||
const skipped = dispatch(
|
||||
ONBOARDING_STORE_NAME
|
||||
).updateProfileItems( {
|
||||
skipped: true,
|
||||
} );
|
||||
const businessLocation =
|
||||
updateBusinessLocation(
|
||||
context.businessInfo
|
||||
.location as string
|
||||
);
|
||||
return Promise.all( [
|
||||
skipped,
|
||||
businessLocation,
|
||||
] );
|
||||
},
|
||||
onDone: {
|
||||
target: 'progress20',
|
||||
|
@ -1147,8 +1186,65 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
completed: true,
|
||||
} );
|
||||
},
|
||||
onDone: [
|
||||
{
|
||||
target: 'isJetpackConnected',
|
||||
cond: 'hasJetpackSelected',
|
||||
},
|
||||
{ actions: 'redirectToWooHome' },
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
component: Loader,
|
||||
progress: 100,
|
||||
},
|
||||
},
|
||||
isJetpackConnected: {
|
||||
invoke: {
|
||||
src: async () => {
|
||||
return await resolveSelect(
|
||||
PLUGINS_STORE_NAME
|
||||
).isJetpackConnected();
|
||||
},
|
||||
onDone: [
|
||||
{
|
||||
target: 'sendToJetpackAuthPage',
|
||||
cond: ( _context, event ) => {
|
||||
return ! event.data;
|
||||
},
|
||||
},
|
||||
{ actions: 'redirectToWooHome' },
|
||||
],
|
||||
},
|
||||
meta: {
|
||||
component: Loader,
|
||||
progress: 100,
|
||||
},
|
||||
},
|
||||
sendToJetpackAuthPage: {
|
||||
invoke: {
|
||||
src: async () =>
|
||||
await resolveSelect(
|
||||
ONBOARDING_STORE_NAME
|
||||
).getJetpackAuthUrl( {
|
||||
redirectUrl: getAdminLink(
|
||||
'admin.php?page=wc-admin'
|
||||
),
|
||||
from: 'woocommerce-core-profiler',
|
||||
} ),
|
||||
onDone: {
|
||||
actions: 'redirectToWooHome',
|
||||
actions: actions.choose( [
|
||||
{
|
||||
cond: ( _context, event ) =>
|
||||
event.data.success === true,
|
||||
actions: 'redirectToJetpackAuthPage',
|
||||
},
|
||||
{
|
||||
cond: ( _context, event ) =>
|
||||
event.data.success === false,
|
||||
actions: 'redirectToWooHome',
|
||||
},
|
||||
] ),
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
|
@ -1198,73 +1294,16 @@ export const coreProfilerStateMachineDefinition = createMachine( {
|
|||
event
|
||||
) => event.payload.errors,
|
||||
} ),
|
||||
( _context, event ) => {
|
||||
recordEvent(
|
||||
'storeprofiler_store_extensions_installed_and_activated',
|
||||
{
|
||||
success: false,
|
||||
failed_extensions:
|
||||
event.payload.errors.map(
|
||||
(
|
||||
error: PluginInstallError
|
||||
) =>
|
||||
getPluginTrackKey(
|
||||
error.plugin
|
||||
)
|
||||
),
|
||||
}
|
||||
);
|
||||
{
|
||||
type: 'recordFailedPluginInstallations',
|
||||
},
|
||||
],
|
||||
},
|
||||
PLUGINS_INSTALLATION_COMPLETED: {
|
||||
target: 'postPluginInstallation',
|
||||
actions: [
|
||||
( _context, event ) => {
|
||||
const installationCompletedResult =
|
||||
event.payload
|
||||
.installationCompletedResult;
|
||||
|
||||
const trackData: {
|
||||
success: boolean;
|
||||
installed_extensions: string[];
|
||||
total_time: string;
|
||||
[ key: string ]:
|
||||
| number
|
||||
| boolean
|
||||
| string
|
||||
| string[];
|
||||
} = {
|
||||
success: true,
|
||||
installed_extensions:
|
||||
installationCompletedResult.installedPlugins.map(
|
||||
(
|
||||
installedPlugin: InstalledPlugin
|
||||
) =>
|
||||
getPluginTrackKey(
|
||||
installedPlugin.plugin
|
||||
)
|
||||
),
|
||||
total_time: getTimeFrame(
|
||||
installationCompletedResult.totalTime
|
||||
),
|
||||
};
|
||||
|
||||
for ( const installedPlugin of installationCompletedResult.installedPlugins ) {
|
||||
trackData[
|
||||
'install_time_' +
|
||||
getPluginTrackKey(
|
||||
installedPlugin.plugin
|
||||
)
|
||||
] = getTimeFrame(
|
||||
installedPlugin.installTime
|
||||
);
|
||||
}
|
||||
|
||||
recordEvent(
|
||||
'storeprofiler_store_extensions_installed_and_activated',
|
||||
trackData
|
||||
);
|
||||
{
|
||||
type: 'recordSuccessfulPluginInstallation',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -1323,6 +1362,17 @@ export const CoreProfilerController = ( {
|
|||
step === ( cond as { step: string | undefined } ).step
|
||||
);
|
||||
},
|
||||
hasJetpackSelected: ( context ) => {
|
||||
return (
|
||||
context.pluginsSelected.find(
|
||||
( plugin ) => plugin === 'jetpack'
|
||||
) !== undefined ||
|
||||
context.pluginsAvailable.find(
|
||||
( plugin: Extension ) =>
|
||||
plugin.key === 'jetpack' && plugin.is_activated
|
||||
) !== undefined
|
||||
);
|
||||
},
|
||||
},
|
||||
} );
|
||||
}, [ actionOverrides, servicesOverrides ] );
|
||||
|
@ -1337,8 +1387,8 @@ export const CoreProfilerController = ( {
|
|||
);
|
||||
|
||||
const navigationProgress = currentNodeMeta?.progress;
|
||||
const CurrentComponent =
|
||||
currentNodeMeta?.component ?? ( () => <ProfileSpinner /> ); // If no component is defined for the state then its a loading state
|
||||
|
||||
const CurrentComponent = currentNodeMeta?.component;
|
||||
|
||||
const currentNodeCssLabel =
|
||||
state.value instanceof Object
|
||||
|
@ -1364,13 +1414,15 @@ export const CoreProfilerController = ( {
|
|||
<div
|
||||
className={ `woocommerce-profile-wizard__container woocommerce-profile-wizard__step-${ currentNodeCssLabel }` }
|
||||
>
|
||||
{
|
||||
{ CurrentComponent ? (
|
||||
<CurrentComponent
|
||||
navigationProgress={ navigationProgress }
|
||||
sendEvent={ send }
|
||||
context={ state.context }
|
||||
/>
|
||||
}
|
||||
) : (
|
||||
<ProfileSpinner />
|
||||
) }
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -91,6 +91,7 @@ export const BusinessInfo = ( {
|
|||
onboardingProfile: {
|
||||
is_store_country_set: isStoreCountrySet,
|
||||
industry: industryFromOnboardingProfile,
|
||||
business_choice: businessChoiceFromOnboardingProfile,
|
||||
},
|
||||
} = context;
|
||||
|
||||
|
@ -152,7 +153,9 @@ export const BusinessInfo = ( {
|
|||
const selectCountryLabel = __( 'Select country/region', 'woocommerce' );
|
||||
const selectIndustryQuestionLabel =
|
||||
selectIndustryMapping[
|
||||
businessChoice || 'im_just_starting_my_business'
|
||||
businessChoice ||
|
||||
businessChoiceFromOnboardingProfile ||
|
||||
'im_just_starting_my_business'
|
||||
];
|
||||
|
||||
const [ dismissedGeolocationNotice, setDismissedGeolocationNotice ] =
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
.woocommerce-layout__footer {
|
||||
background: $studio-white;
|
||||
border-top: 1px solid $gray-200;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
width: calc(100% - 160px);
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
/* on top of #wp-content-editor-tools */
|
||||
bottom: -1px; /* to hide the border when no visible children */
|
||||
z-index: 1001; /* on top of #wp-content-editor-tools */
|
||||
|
||||
.woocommerce-profile-wizard__body & {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include breakpoint('782px-960px') {
|
||||
width: calc(100% - 36px);
|
||||
|
|
|
@ -43,7 +43,7 @@ import { Controller, getPages } from './controller';
|
|||
import { Header } from '../header';
|
||||
import { Footer } from './footer';
|
||||
import Notices from './notices';
|
||||
import TransientNotices from './transient-notices';
|
||||
import { TransientNotices } from './transient-notices';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
import { usePageClasses } from './hooks/use-page-classes';
|
||||
import '~/activity-panel';
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { applyFilters } from '@wordpress/hooks';
|
||||
import classnames from 'classnames';
|
||||
import { WooFooterItem } from '@woocommerce/admin-layout';
|
||||
import { OPTIONS_STORE_NAME, USER_STORE_NAME } from '@woocommerce/data';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
|
@ -17,7 +18,7 @@ import './style.scss';
|
|||
const QUEUE_OPTION = 'woocommerce_admin_transient_notices_queue';
|
||||
const QUEUED_NOTICE_FILTER = 'woocommerce_admin_queued_notice_filter';
|
||||
|
||||
function TransientNotices( props ) {
|
||||
export function TransientNotices( props ) {
|
||||
const { removeNotice: onRemove } = useDispatch( 'core/notices' );
|
||||
const { createNotice: createNotice2, removeNotice: onRemove2 } =
|
||||
useDispatch( 'core/notices2' );
|
||||
|
@ -89,12 +90,14 @@ function TransientNotices( props ) {
|
|||
const combinedNotices = getNotices();
|
||||
|
||||
return (
|
||||
<SnackbarList
|
||||
notices={ combinedNotices }
|
||||
className={ classes }
|
||||
onRemove={ onRemove }
|
||||
onRemove2={ onRemove2 }
|
||||
/>
|
||||
<WooFooterItem>
|
||||
<SnackbarList
|
||||
notices={ combinedNotices }
|
||||
className={ classes }
|
||||
onRemove={ onRemove }
|
||||
onRemove2={ onRemove2 }
|
||||
/>
|
||||
</WooFooterItem>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -108,5 +111,3 @@ TransientNotices.propTypes = {
|
|||
*/
|
||||
notices: PropTypes.array,
|
||||
};
|
||||
|
||||
export default TransientNotices;
|
||||
|
|
|
@ -1,32 +1,22 @@
|
|||
@import '../../navigation/stylesheets/variables.scss';
|
||||
|
||||
.woocommerce-transient-notices {
|
||||
position: fixed;
|
||||
bottom: $gap-small;
|
||||
left: $admin-menu-width + $gap;
|
||||
position: absolute;
|
||||
left: $gap;
|
||||
bottom: 100%;
|
||||
margin-bottom: $gap-small;
|
||||
z-index: calc(z-index('.components-snackbar-list') + 1);
|
||||
width: auto;
|
||||
|
||||
@media ( max-width: 960px ) {
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
@media ( max-width: 782px ) {
|
||||
left: $gap;
|
||||
}
|
||||
|
||||
.woocommerce-profile-wizard__body & {
|
||||
left: unset;
|
||||
width: 100%;
|
||||
|
||||
.components-snackbar {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.has-woocommerce-navigation & {
|
||||
left: $navigation-width + $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.components-snackbar {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useSelect, useDispatch } from '@wordpress/data';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import TransientNotices from '..';
|
||||
import { TransientNotices } from '..';
|
||||
|
||||
jest.mock( '@wordpress/data', () => {
|
||||
// Require the original module to not be mocked...
|
||||
|
@ -26,6 +26,18 @@ useDispatch.mockReturnValue( {
|
|||
createNotice: jest.fn(),
|
||||
} );
|
||||
|
||||
jest.mock( '@woocommerce/admin-layout', () => {
|
||||
const originalModule = jest.requireActual( '@woocommerce/admin-layout' );
|
||||
|
||||
return {
|
||||
__esModule: true, // Use it when dealing with esModules
|
||||
...originalModule,
|
||||
WooFooterItem: jest.fn( ( { children } ) => {
|
||||
return <div>{ children }</div>;
|
||||
} ),
|
||||
};
|
||||
} );
|
||||
|
||||
jest.mock( '../snackbar/list', () =>
|
||||
jest.fn( ( { notices } ) => {
|
||||
return notices.map( ( notice ) => (
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { MenuItem } from '@wordpress/components';
|
||||
import { info, Icon } from '@wordpress/icons';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import BlockEditorGuide from '~/products/tour/block-editor/block-editor-guide';
|
||||
import { usePublishedProductsCount } from '~/products/tour/block-editor/use-published-products-count';
|
||||
|
||||
export const AboutTheEditorMenuItem = ( {
|
||||
onClose,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
} ) => {
|
||||
const [ isGuideOpen, setIsGuideOpen ] = useState( false );
|
||||
const { isNewUser } = usePublishedProductsCount();
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
recordEvent(
|
||||
'block_product_editor_about_the_editor_menu_item_clicked'
|
||||
);
|
||||
setIsGuideOpen( true );
|
||||
} }
|
||||
icon={ <Icon icon={ info } /> }
|
||||
iconPosition="right"
|
||||
>
|
||||
{ __( 'About the editor…', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
{ isGuideOpen && (
|
||||
<BlockEditorGuide
|
||||
isNewUser={ isNewUser }
|
||||
onCloseGuide={ () => {
|
||||
setIsGuideOpen( false );
|
||||
onClose();
|
||||
} }
|
||||
/>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,20 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { OPTIONS_STORE_NAME, Product } from '@woocommerce/data';
|
||||
import { MenuItem } from '@wordpress/components';
|
||||
import {
|
||||
ALLOW_TRACKING_OPTION_NAME,
|
||||
STORE_KEY as CES_STORE_KEY,
|
||||
} from '@woocommerce/customer-effort-score';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ClassicEditorIcon } from '../../images/classic-editor-icon';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
|
||||
export const ClassicEditorMenuItem = ( {
|
||||
|
@ -53,24 +53,46 @@ export const ClassicEditorMenuItem = ( {
|
|||
`post-new.php?post_type=product&product_block_editor=0&_feature_nonce=${ _feature_nonce }`
|
||||
);
|
||||
|
||||
const { type: productType, status: productStatus } = useSelect(
|
||||
( select ) => {
|
||||
const { getEntityRecord } = select( 'core' );
|
||||
return getEntityRecord(
|
||||
'postType',
|
||||
'product',
|
||||
productId
|
||||
) as Product;
|
||||
},
|
||||
[ productId ]
|
||||
);
|
||||
|
||||
if ( isLoading ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleMenuItemClick() {
|
||||
recordEvent( 'product_editor_options_turn_off_editor_click', {
|
||||
product_id: productId,
|
||||
product_type: productType,
|
||||
product_status: productStatus,
|
||||
} );
|
||||
|
||||
if ( allowTracking ) {
|
||||
showProductMVPFeedbackModal();
|
||||
} else {
|
||||
window.location.href = classicEditorUrl;
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
onClick={ () => {
|
||||
if ( allowTracking ) {
|
||||
showProductMVPFeedbackModal();
|
||||
} else {
|
||||
window.location.href = classicEditorUrl;
|
||||
}
|
||||
onClose();
|
||||
} }
|
||||
icon={ <ClassicEditorIcon /> }
|
||||
iconPosition="right"
|
||||
onClick={ handleMenuItemClick }
|
||||
info={ __(
|
||||
'Save changes and go back to the classic product editing screen.',
|
||||
'woocommerce'
|
||||
) }
|
||||
>
|
||||
{ __( 'Use the classic editor', 'woocommerce' ) }
|
||||
{ __( 'Turn off the new product form', 'woocommerce' ) }
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -2,10 +2,21 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { MenuItem } from '@wordpress/components';
|
||||
import {
|
||||
BaseControl,
|
||||
MenuItem,
|
||||
TextControl,
|
||||
TextareaControl,
|
||||
} from '@wordpress/components';
|
||||
import {
|
||||
createElement,
|
||||
createInterpolateElement,
|
||||
Fragment,
|
||||
} from '@wordpress/element';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { STORE_KEY as CES_STORE_KEY } from '@woocommerce/customer-effort-score';
|
||||
import { useLayoutContext } from '@woocommerce/admin-layout';
|
||||
import { isValidEmail } from '@woocommerce/product-editor';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -22,8 +33,9 @@ export const FeedbackMenuItem = ( { onClose }: { onClose: () => void } ) => {
|
|||
showCesModal(
|
||||
{
|
||||
action: 'new_product',
|
||||
showDescription: false,
|
||||
title: __(
|
||||
"How's your experience with the product editor?",
|
||||
"How's your experience with the new product form?",
|
||||
'woocommerce'
|
||||
),
|
||||
firstQuestion: __(
|
||||
|
@ -34,9 +46,118 @@ export const FeedbackMenuItem = ( { onClose }: { onClose: () => void } ) => {
|
|||
"The product editing screen's functionality meets my needs",
|
||||
'woocommerce'
|
||||
),
|
||||
getExtraFieldsToBeShown: (
|
||||
values: {
|
||||
email?: string;
|
||||
additional_thoughts?: string;
|
||||
},
|
||||
setValues: ( value: {
|
||||
email?: string;
|
||||
additional_thoughts?: string;
|
||||
} ) => void,
|
||||
errors: Record< string, string > | undefined
|
||||
) => (
|
||||
<Fragment>
|
||||
<BaseControl
|
||||
id={ 'feedback_additional_thoughts' }
|
||||
className="woocommerce-product-feedback__additional-thoughts"
|
||||
label={ createInterpolateElement(
|
||||
__(
|
||||
'ADDITIONAL THOUGHTS <optional />',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
optional: (
|
||||
<span className="woocommerce-product-feedback__optional-input">
|
||||
{ __(
|
||||
'(OPTIONAL)',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
),
|
||||
}
|
||||
) }
|
||||
>
|
||||
<TextareaControl
|
||||
value={
|
||||
values.additional_thoughts || ''
|
||||
}
|
||||
onChange={ ( value: string ) =>
|
||||
setValues( {
|
||||
...values,
|
||||
additional_thoughts: value,
|
||||
} )
|
||||
}
|
||||
help={
|
||||
errors?.additional_thoughts || ''
|
||||
}
|
||||
/>
|
||||
</BaseControl>
|
||||
<BaseControl
|
||||
id={ 'feedback_email' }
|
||||
className="woocommerce-product-feedback__email"
|
||||
label={ createInterpolateElement(
|
||||
__(
|
||||
'YOUR EMAIL ADDRESS <optional />',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
optional: (
|
||||
<span className="woocommerce-product-feedback__optional-input">
|
||||
{ __(
|
||||
'(OPTIONAL)',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
),
|
||||
}
|
||||
) }
|
||||
>
|
||||
<TextControl
|
||||
value={ values.email || '' }
|
||||
onChange={ ( value: string ) =>
|
||||
setValues( {
|
||||
...values,
|
||||
email: value,
|
||||
} )
|
||||
}
|
||||
help={ errors?.email || '' }
|
||||
/>
|
||||
<span>
|
||||
{ __(
|
||||
'In case you want to participate in further discussion and future user research.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
</BaseControl>
|
||||
</Fragment>
|
||||
),
|
||||
validateExtraFields: ( {
|
||||
email = '',
|
||||
additional_thoughts = '',
|
||||
}: {
|
||||
email?: string;
|
||||
additional_thoughts?: string;
|
||||
} ) => {
|
||||
const errors: Record< string, string > | undefined =
|
||||
{};
|
||||
if ( email.length > 0 && ! isValidEmail( email ) ) {
|
||||
errors.email = __(
|
||||
'Please enter a valid email address.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
if ( additional_thoughts?.length > 500 ) {
|
||||
errors.additional_thoughts = __(
|
||||
'Please enter no more than 500 characters.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
return errors;
|
||||
},
|
||||
},
|
||||
{
|
||||
shouldShowComments: () => true,
|
||||
shouldShowComments: () => false,
|
||||
},
|
||||
{
|
||||
type: 'snackbar',
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './feedback-menu-item';
|
||||
export * from './classic-editor-menu-item';
|
||||
export * from './about-the-editor-menu-item';
|
||||
|
|
|
@ -19,6 +19,7 @@ import { useEntityProp } from '@wordpress/core-data';
|
|||
import {
|
||||
FeedbackMenuItem,
|
||||
ClassicEditorMenuItem,
|
||||
AboutTheEditorMenuItem,
|
||||
} from '../fills/more-menu-items';
|
||||
|
||||
const MoreMenuFill = ( { onClose }: { onClose: () => void } ) => {
|
||||
|
@ -28,6 +29,7 @@ const MoreMenuFill = ( { onClose }: { onClose: () => void } ) => {
|
|||
<>
|
||||
<FeedbackMenuItem onClose={ onClose } />
|
||||
<ClassicEditorMenuItem productId={ id } onClose={ onClose } />
|
||||
<AboutTheEditorMenuItem onClose={ onClose } />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,11 +10,95 @@ import { __ } from '@wordpress/i18n';
|
|||
import Guide from '../components/guide';
|
||||
import './style.scss';
|
||||
|
||||
interface Props {
|
||||
const PageContent = ( {
|
||||
page,
|
||||
}: {
|
||||
page: {
|
||||
heading: string;
|
||||
text: string;
|
||||
};
|
||||
} ) => (
|
||||
<>
|
||||
<h1 className="woocommerce-block-editor-guide__heading">
|
||||
{ page.heading }
|
||||
</h1>
|
||||
<p className="woocommerce-block-editor-guide__text">{ page.text }</p>
|
||||
</>
|
||||
);
|
||||
|
||||
const PageImage = ( {
|
||||
page,
|
||||
}: {
|
||||
page: {
|
||||
index: number;
|
||||
};
|
||||
} ) => (
|
||||
<div
|
||||
className={ `woocommerce-block-editor-guide__header woocommerce-block-editor-guide__header-${
|
||||
page.index + 1
|
||||
}` }
|
||||
></div>
|
||||
);
|
||||
|
||||
interface BlockEditorGuideProps {
|
||||
isNewUser: boolean;
|
||||
onCloseGuide: ( currentPage: number, origin: 'close' | 'finish' ) => void;
|
||||
}
|
||||
|
||||
const BlockEditorGuide = ( { onCloseGuide }: Props ) => {
|
||||
const BlockEditorGuide = ( {
|
||||
isNewUser,
|
||||
onCloseGuide,
|
||||
}: BlockEditorGuideProps ) => {
|
||||
const pagesConfig = [
|
||||
{
|
||||
heading: isNewUser
|
||||
? __( 'Fresh and modern interface', 'woocommerce' )
|
||||
: __( 'Refreshed, streamlined interface', 'woocommerce' ),
|
||||
text: isNewUser
|
||||
? __(
|
||||
'Using the product form means less clicking around. Product details are neatly grouped by tabs, so you always know where to go.',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Experience a simpler, more focused interface with a modern design that enhances usability.',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
heading: __( 'Content-rich product descriptions', 'woocommerce' ),
|
||||
text: __(
|
||||
'Create compelling product pages with blocks, media, images, videos, and any content you desire to engage customers.',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
heading: isNewUser
|
||||
? __( 'Speed & performance', 'woocommerce' )
|
||||
: __( 'Improved speed & performance', 'woocommerce' ),
|
||||
text: isNewUser
|
||||
? __(
|
||||
'Create a product from start to finish without page reloads. Our modern technology ensures reliability and lightning-fast performance.',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Enjoy a seamless experience without page reloads. Our modern technology ensures reliability and lightning-fast performance.',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
{
|
||||
heading: __( 'More features are on the way', 'woocommerce' ),
|
||||
text: __(
|
||||
'While we currently support physical products, exciting updates are coming to accommodate more types, like digital products, variations, and more. Stay tuned!',
|
||||
'woocommerce'
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const pages = pagesConfig.map( ( page, index ) => ( {
|
||||
content: <PageContent page={ page } />,
|
||||
image: <PageImage page={ { ...page, index } } />,
|
||||
} ) );
|
||||
|
||||
return (
|
||||
<Guide
|
||||
className="woocommerce-block-editor-guide"
|
||||
|
@ -22,92 +106,7 @@ const BlockEditorGuide = ( { onCloseGuide }: Props ) => {
|
|||
finishButtonText={ __( 'Tell me more', 'woocommerce' ) }
|
||||
finishButtonLink="https://woocommerce.com/product-form-beta"
|
||||
onFinish={ onCloseGuide }
|
||||
pages={ [
|
||||
{
|
||||
content: (
|
||||
<>
|
||||
<h1 className="woocommerce-block-editor-guide__heading">
|
||||
{ __(
|
||||
'Refreshed, streamlined interface',
|
||||
'woocommerce'
|
||||
) }
|
||||
</h1>
|
||||
<p className="woocommerce-block-editor-guide__text">
|
||||
{ __(
|
||||
'Experience a simpler, more focused interface with a modern design that enhances usability.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
image: (
|
||||
<div className="woocommerce-block-editor-guide__background1"></div>
|
||||
),
|
||||
},
|
||||
{
|
||||
content: (
|
||||
<>
|
||||
<h1 className="woocommerce-block-editor-guide__heading">
|
||||
{ __(
|
||||
'Content-rich product descriptions',
|
||||
'woocommerce'
|
||||
) }
|
||||
</h1>
|
||||
<p className="woocommerce-block-editor-guide__text">
|
||||
{ __(
|
||||
'Create compelling product pages with blocks, media, images, videos, and any content you desire to engage customers.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
image: (
|
||||
<div className="woocommerce-block-editor-guide__background2"></div>
|
||||
),
|
||||
},
|
||||
{
|
||||
content: (
|
||||
<>
|
||||
<h1 className="woocommerce-block-editor-guide__heading">
|
||||
{ __(
|
||||
'Improved speed & performance',
|
||||
'woocommerce'
|
||||
) }
|
||||
</h1>
|
||||
<p className="woocommerce-block-editor-guide__text">
|
||||
{ __(
|
||||
'Enjoy a seamless experience without page reloads. Our modern technology ensures reliability and lightning-fast performance.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
image: (
|
||||
<div className="woocommerce-block-editor-guide__background3"></div>
|
||||
),
|
||||
},
|
||||
{
|
||||
content: (
|
||||
<>
|
||||
<h1 className="woocommerce-block-editor-guide__heading">
|
||||
{ __(
|
||||
'More features are on the way',
|
||||
'woocommerce'
|
||||
) }
|
||||
</h1>
|
||||
<p className="woocommerce-block-editor-guide__text">
|
||||
{ __(
|
||||
'While we currently support physical products, exciting updates are coming to accommodate more types, like digital products, variations, and more. Stay tuned!',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
image: (
|
||||
<div className="woocommerce-block-editor-guide__background4"></div>
|
||||
),
|
||||
},
|
||||
] }
|
||||
pages={ pages }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Pill, TourKit } from '@woocommerce/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Pill, TourKit } from '@woocommerce/components';
|
||||
import { __experimentalUseFeedbackBar as useFeedbackBar } from '@woocommerce/product-editor';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -13,6 +13,7 @@ import { __experimentalUseFeedbackBar as useFeedbackBar } from '@woocommerce/pro
|
|||
|
||||
import './style.scss';
|
||||
import BlockEditorGuide from './block-editor-guide';
|
||||
import { usePublishedProductsCount } from './use-published-products-count';
|
||||
|
||||
interface Props {
|
||||
shouldTourBeShown: boolean;
|
||||
|
@ -20,6 +21,9 @@ interface Props {
|
|||
}
|
||||
|
||||
const BlockEditorTour = ( { shouldTourBeShown, dismissModal }: Props ) => {
|
||||
const { isNewUser, loadingPublishedProductsCount } =
|
||||
usePublishedProductsCount();
|
||||
|
||||
useEffect( () => {
|
||||
if ( shouldTourBeShown ) {
|
||||
recordEvent( 'block_product_editor_spotlight_view' );
|
||||
|
@ -34,9 +38,31 @@ const BlockEditorTour = ( { shouldTourBeShown, dismissModal }: Props ) => {
|
|||
setIsGuideOpen( true );
|
||||
};
|
||||
|
||||
const getTourText = () => {
|
||||
return {
|
||||
heading: isNewUser
|
||||
? __( 'Meet the product editing form', 'woocommerce' )
|
||||
: __( 'A new way to edit your products', 'woocommerce' ),
|
||||
description: isNewUser
|
||||
? __(
|
||||
"Discover the form's unique features designed to help you make this product stand out.",
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Introducing the upgraded experience designed to help you create and edit products easier.',
|
||||
'woocommerce'
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
if ( loadingPublishedProductsCount ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( isGuideOpen ) {
|
||||
return (
|
||||
<BlockEditorGuide
|
||||
isNewUser={ isNewUser }
|
||||
onCloseGuide={ ( currentPage, source ) => {
|
||||
dismissModal();
|
||||
if ( source === 'finish' ) {
|
||||
|
@ -58,6 +84,8 @@ const BlockEditorTour = ( { shouldTourBeShown, dismissModal }: Props ) => {
|
|||
/>
|
||||
);
|
||||
} else if ( shouldTourBeShown ) {
|
||||
const { heading, description } = getTourText();
|
||||
|
||||
return (
|
||||
<TourKit
|
||||
config={ {
|
||||
|
@ -72,20 +100,12 @@ const BlockEditorTour = ( { shouldTourBeShown, dismissModal }: Props ) => {
|
|||
),
|
||||
},
|
||||
descriptions: {
|
||||
desktop: __(
|
||||
"We designed a brand new product editing experience to let you focus on what's important.",
|
||||
'woocommerce'
|
||||
),
|
||||
desktop: description,
|
||||
},
|
||||
heading: (
|
||||
<>
|
||||
<span>
|
||||
{ __(
|
||||
'Meet a streamlined product form',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>{ ' ' }
|
||||
<Pill className="woocommerce-block-editor-guide__pill">
|
||||
<span>{ heading }</span>
|
||||
<Pill>
|
||||
{ __( 'Beta', 'woocommerce' ) }
|
||||
</Pill>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<svg width="312" height="222" viewBox="0 0 312 222" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="312" height="222" fill="#F6F7F7"/>
|
||||
<path d="M87.6168 96L92 96L92 135L92 174L87.6168 174C87.6168 156.805 82.1737 139.763 77.5075 139.763C66.1856 139.763 60.3578 146.362 52.0988 146.362C47.2305 146.362 40 144.478 40 135C40 125.527 47.2305 123.638 52.0988 123.638C60.3578 123.638 66.1856 130.237 77.5075 130.237C82.1737 130.237 87.6168 113.195 87.6168 96ZM45.7754 135C45.7754 137.175 47.5763 138.94 49.7949 138.94C52.0135 138.94 53.8144 137.175 53.8144 135C53.8144 132.825 52.0135 131.06 49.7949 131.06C47.5763 131.06 45.7754 132.825 45.7754 135Z" fill="#FF6BBB"/>
|
||||
<path d="M91.8418 96L174.842 96L174.842 135L174.842 174L91.8418 174L91.8418 96Z" fill="#966CCF"/>
|
||||
<g style="mix-blend-mode:soft-light">
|
||||
<path d="M175 174L153.339 174C128.943 171.352 104 165.176 104 157.441C104 145.4 131.103 147.776 131.103 139.435C131.103 131.093 117.909 135.078 117.909 125.438C117.909 115.797 152.495 113 174.984 113L174.984 174L175 174Z" fill="white"/>
|
||||
</g>
|
||||
<rect x="193" y="49" width="51" height="51" rx="25.5" fill="#FFC350"/>
|
||||
<rect x="193" y="49" width="51" height="51" rx="25.5" fill="url(#paint0_linear_1997_17544)"/>
|
||||
<path d="M219.619 84.0169L217.458 78.7849H206.589L204.428 84.0169H203.348L211.471 64.5217H212.58L220.703 84.0169H219.622H219.619ZM212.024 65.6027L206.938 77.9082H217.106L212.02 65.6027H212.024Z" fill="white"/>
|
||||
<path d="M232.944 84.0169V82.2918C231.627 83.7247 230.051 84.3657 228.065 84.3657C225.581 84.3657 223.127 82.6688 223.127 79.6585C223.127 76.6481 225.552 74.9512 228.065 74.9512C230.054 74.9512 231.631 75.5954 232.944 77.0252V73.8388C232.944 71.5575 231.131 70.3319 228.97 70.3319C227.129 70.3319 225.757 70.9761 224.44 72.5536L223.768 71.9691C225.141 70.3602 226.602 69.5432 228.97 69.5432C231.747 69.5432 233.82 70.9478 233.82 73.8105V84.0106H232.944V84.0169ZM232.944 81.415V77.9082C231.863 76.4753 230.139 75.7462 228.298 75.7462C225.725 75.7462 224.092 77.4714 224.092 79.6616C224.092 81.8518 225.728 83.577 228.298 83.577C230.139 83.577 231.863 82.848 232.944 81.415Z" fill="white"/>
|
||||
<g style="mix-blend-mode:multiply">
|
||||
<rect x="92" y="49" width="83" height="28" rx="14" fill="#007CBA"/>
|
||||
<rect x="92" y="49" width="83" height="28" rx="14" fill="url(#paint1_linear_1997_17544)" fill-opacity="0.4"/>
|
||||
<path d="M160.655 70.3093C164.882 70.3093 168.309 66.8822 168.309 62.6547C168.309 58.4271 164.882 55 160.655 55C156.427 55 153 58.4271 153 62.6547C153 66.8822 156.427 70.3093 160.655 70.3093Z" fill="white"/>
|
||||
</g>
|
||||
<circle cx="218" cy="167" r="6.44257" transform="rotate(-90 218 167)" stroke="#BBBBBB" stroke-width="1.11486"/>
|
||||
<circle cx="218" cy="121" r="6.44257" transform="rotate(-90 218 121)" stroke="#BBBBBB" stroke-width="1.11486"/>
|
||||
<ellipse cx="218" cy="144" rx="7" ry="7" transform="rotate(-90 218 144)" fill="#007CBA"/>
|
||||
<circle cx="218" cy="144" r="10.4426" transform="rotate(-90 218 144)" stroke="#BBBBBB" stroke-width="1.11486"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1997_17544" x1="207.845" y1="52.0186" x2="224.58" y2="97.7763" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FBA500"/>
|
||||
<stop offset="1" stop-color="#FBA500" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1997_17544" x1="94" y1="55" x2="116.529" y2="74.1452" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#2508D3"/>
|
||||
<stop offset="1" stop-color="#2508D3" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -0,0 +1,69 @@
|
|||
<svg width="381" height="194" viewBox="0 0 381 194" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1865_198367)">
|
||||
<rect width="381" height="194.31" fill="#D4AAF6"/>
|
||||
<g clip-path="url(#clip1_1865_198367)">
|
||||
<rect width="272.832" height="167.123" transform="translate(54.576 27.3745)" fill="white"/>
|
||||
<g clip-path="url(#clip2_1865_198367)">
|
||||
<rect x="128.162" y="113.562" width="51.2842" height="5.92333" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="128.417" y="89.7881" width="74.9435" height="3.59011" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="142.521" y="123.863" width="35.2835" height="3.38476" rx="1.69238" fill="#E0E0E0"/>
|
||||
<rect x="128.417" y="96.0708" width="94.2403" height="3.59011" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="142.521" y="131.459" width="44.3096" height="3.38476" rx="1.69238" fill="#E0E0E0"/>
|
||||
<rect x="128.417" y="102.354" width="33.2085" height="3.59011" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="142.521" y="139.055" width="35.2835" height="3.38476" rx="1.69238" fill="#E0E0E0"/>
|
||||
<ellipse cx="136.506" cy="125.662" rx="1.35834" ry="1.39935" fill="#007CBA"/>
|
||||
<rect x="128.412" y="122.913" width="10.7545" height="5.49692" rx="2.74846" stroke="#BBBBBB" stroke-width="0.500309"/>
|
||||
<ellipse cx="131.46" cy="133.258" rx="1.35834" ry="1.39935" fill="#CCCCCC"/>
|
||||
<rect x="128.412" y="130.51" width="10.7545" height="5.49692" rx="2.74846" stroke="#BBBBBB" stroke-width="0.500309"/>
|
||||
<ellipse cx="136.506" cy="140.855" rx="1.35834" ry="1.39935" fill="#007CBA"/>
|
||||
<rect x="128.412" y="138.106" width="10.7545" height="5.49691" rx="2.74846" stroke="#BBBBBB" stroke-width="0.500309"/>
|
||||
</g>
|
||||
<rect x="128.048" y="74.2329" width="51.2842" height="5.92333" rx="1.79505" fill="#E0E0E0"/>
|
||||
<g clip-path="url(#clip3_1865_198367)">
|
||||
<rect x="128.347" y="155.979" width="37.2573" height="14.7931" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect opacity="0.6" x="169.097" y="155.979" width="37.2573" height="14.7931" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect opacity="0.6" x="209.847" y="155.979" width="37.2573" height="14.7931" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="128.414" y="177.969" width="51.2842" height="5.92333" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="128.414" y="188.124" width="35.2835" height="3.38476" rx="1.69238" fill="#E0E0E0"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip4_1865_198367)">
|
||||
<mask id="path-20-inside-1_1865_198367" fill="white">
|
||||
<path d="M54.3712 27.113H327.668V57.1802H54.3712V27.113Z"/>
|
||||
</mask>
|
||||
<path d="M54.3712 27.113H327.668V57.1802H54.3712V27.113Z" fill="white"/>
|
||||
<rect x="160.123" y="34.312" width="60.9476" height="4.65829" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="288.488" y="31.7712" width="33.3761" height="9.74006" rx="1.79505" fill="#007CBA"/>
|
||||
<rect x="258.981" y="34.312" width="24.6693" height="4.65829" rx="1.79505" fill="#E0E0E0"/>
|
||||
<rect x="125.732" y="48.4114" width="21.2833" height="3.38785" rx="1.69392" fill="#E0E0E0"/>
|
||||
<rect x="125.732" y="55.6106" width="21.2833" height="1.69392" fill="#007CBA"/>
|
||||
<rect x="179.908" y="48.4114" width="21.2833" height="3.38785" rx="1.69392" fill="#E0E0E0"/>
|
||||
<rect x="152.82" y="48.4114" width="21.2833" height="3.38785" rx="1.69392" fill="#E0E0E0"/>
|
||||
<rect x="206.996" y="48.4114" width="21.2833" height="3.38785" rx="1.69392" fill="#E0E0E0"/>
|
||||
<rect x="234.083" y="48.4114" width="21.2833" height="3.38785" rx="1.69392" fill="#E0E0E0"/>
|
||||
<rect width="17.9505" height="17.9505" transform="translate(54.127 26.9258)" fill="#23282D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.6536 35.7083C67.6536 33.0946 65.5234 30.9648 62.9093 30.9648C60.2904 30.9648 58.1649 33.0946 58.1649 35.7083C58.1649 38.3266 60.2904 40.4517 62.9093 40.4517C65.5234 40.4517 67.6536 38.3266 67.6536 35.7083ZM61.8561 38.2555L60.2382 33.9153C60.4992 33.9058 60.7933 33.8773 60.7933 33.8773C61.0305 33.8489 61.0021 33.3413 60.7649 33.3508C60.7649 33.3508 60.0769 33.403 59.6404 33.403C59.555 33.403 59.4649 33.403 59.3653 33.3983C60.1196 32.2409 61.4243 31.4914 62.9093 31.4914C64.0147 31.4914 65.0205 31.9041 65.7796 32.6014C65.457 32.5492 64.9968 32.7864 64.9968 33.3508C64.9968 33.6582 65.1605 33.9219 65.3446 34.2184L65.3446 34.2184L65.3446 34.2184C65.3707 34.2605 65.3973 34.3033 65.4238 34.3469C65.5899 34.6363 65.6847 34.992 65.6847 35.5138C65.6847 36.2206 65.0205 37.8855 65.0205 37.8855L63.583 33.9153C63.8392 33.9058 63.972 33.8346 63.972 33.8346C64.2092 33.8109 64.1808 33.2417 63.9436 33.256C63.9436 33.256 63.2604 33.3129 62.8144 33.3129C62.4017 33.3129 61.709 33.256 61.709 33.256C61.4718 33.2417 61.4433 33.8252 61.6805 33.8346L62.117 33.8726L62.7148 35.4901L61.8561 38.2555ZM66.4354 35.6807L66.425 35.7082C66.0812 36.6132 65.74 37.5259 65.3995 38.4368C65.2795 38.7578 65.1596 39.0785 65.0397 39.3986C66.3064 38.6681 67.1272 37.2688 67.1272 35.7082C67.1272 34.9777 66.9611 34.3042 66.629 33.6923C66.7718 34.7879 66.5525 35.3698 66.4354 35.6807ZM61.0591 39.5457C59.6453 38.8627 58.6917 37.3827 58.6917 35.7083C58.6917 35.0916 58.8008 34.5319 59.0333 34.0054L59.4596 35.1735C59.9914 36.6307 60.5238 38.0897 61.0591 39.5457ZM64.1951 39.7118L62.9711 36.4008C62.7456 37.066 62.5184 37.7312 62.2905 38.3983C62.1349 38.8539 61.9789 39.3105 61.823 39.7687C62.1645 39.873 62.5346 39.9252 62.9094 39.9252C63.3601 39.9252 63.7871 39.8493 64.1951 39.7118Z" fill="white"/>
|
||||
</g>
|
||||
<path d="M327.668 56.507H54.3712V57.8533H327.668V56.507Z" fill="#F0F0F0" mask="url(#path-20-inside-1_1865_198367)"/>
|
||||
</g>
|
||||
<path d="M335.943 125.677C340.391 121.466 339.702 105.926 339.702 104.113H340.487C340.487 105.926 339.801 121.466 344.246 125.677C347.527 128.784 349.836 129.969 363.309 129.969V130.678C349.836 130.678 347.527 131.863 344.246 134.97C339.797 139.182 340.487 154.721 340.487 156.534H339.702C339.702 154.721 340.388 139.182 335.943 134.97C332.661 131.863 330.352 130.678 316.879 130.678V129.969C330.352 129.969 332.661 128.784 335.943 125.677Z" fill="#F4C759"/>
|
||||
<path opacity="0.5" d="M296.273 118.445C298.179 116.642 297.884 109.99 297.884 109.213H298.22C298.22 109.99 297.926 116.642 299.831 118.445C301.236 119.775 302.226 120.283 307.998 120.283V120.586C302.226 120.586 301.236 121.093 299.831 122.424C297.925 124.226 298.22 130.879 298.22 131.655H297.884C297.884 130.879 298.178 124.226 296.273 122.424C294.867 121.093 293.878 120.586 288.106 120.586V120.283C293.878 120.283 294.867 119.775 296.273 118.445Z" fill="#F4C759"/>
|
||||
<path opacity="0.7" d="M304.496 158.655C306.927 156.353 306.55 147.862 306.55 146.871H306.979C306.979 147.862 306.604 156.353 309.033 158.655C310.826 160.353 312.088 161 319.45 161V161.387C312.088 161.387 310.826 162.035 309.033 163.733C306.602 166.034 306.979 174.526 306.979 175.516H306.55C306.55 174.526 306.925 166.034 304.496 163.733C302.703 162.035 301.441 161.387 294.079 161.387V161C301.441 161 302.703 160.353 304.496 158.655Z" fill="#F4C759"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1865_198367">
|
||||
<rect width="381" height="194.31" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_1865_198367">
|
||||
<rect width="272.832" height="167.123" fill="white" transform="translate(54.576 27.3745)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip2_1865_198367">
|
||||
<rect width="126.102" height="61.4806" fill="white" transform="translate(127.968 89.7881)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip3_1865_198367">
|
||||
<rect width="119.371" height="48.9152" fill="white" transform="translate(128.173 155.721)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip4_1865_198367">
|
||||
<path d="M54.3712 27.113H327.668V57.1802H54.3712V27.113Z" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
|
@ -1,30 +1,29 @@
|
|||
$background-height: 220px;
|
||||
$yellow: #f5e6ab;
|
||||
$light-purple: #f2edff;
|
||||
|
||||
.woocommerce-block-editor-guide {
|
||||
&__background1 {
|
||||
height: $background-height;
|
||||
background-color: $light-purple;
|
||||
}
|
||||
&__background2 {
|
||||
height: $background-height;
|
||||
background-color: #dfd1fb;
|
||||
}
|
||||
&__background3 {
|
||||
height: $background-height;
|
||||
background-color: #cfb9f6;
|
||||
}
|
||||
&__background4 {
|
||||
height: $background-height;
|
||||
background-color: #bea0f2;
|
||||
}
|
||||
&__pill {
|
||||
border: 1px solid $yellow;
|
||||
background-color: $yellow;
|
||||
&__header {
|
||||
width: 312px;
|
||||
height: 222px;
|
||||
background-color: #f6f7f7; /* WP Gray 0; no var available */
|
||||
background-size: cover;
|
||||
|
||||
&-1 {
|
||||
background-image: url(./images/guide-1.svg);
|
||||
}
|
||||
|
||||
&-2 {
|
||||
background-image: url(./images/guide-2.png);
|
||||
}
|
||||
|
||||
&-3 {
|
||||
background-image: url(./images/guide-3.png);
|
||||
}
|
||||
|
||||
&-4 {
|
||||
background-image: url(./images/guide-4.png);
|
||||
}
|
||||
}
|
||||
|
||||
&.components-modal__frame {
|
||||
max-width: 320px;
|
||||
max-width: 312px;
|
||||
}
|
||||
&__heading,
|
||||
&__text {
|
||||
|
@ -59,10 +58,26 @@ $light-purple: #f2edff;
|
|||
}
|
||||
|
||||
.woocommerce-block-editor-tourkit {
|
||||
.components-card__header {
|
||||
align-items: flex-start;
|
||||
height: 200px;
|
||||
background-color: $light-purple;
|
||||
margin-bottom: $gap;
|
||||
.woocommerce-tour-kit-step {
|
||||
width: 381px;
|
||||
|
||||
.components-card__header {
|
||||
align-items: flex-start;
|
||||
height: 194px;
|
||||
background-color: #d4aaf6; /* no var available */
|
||||
background-image: url(./images/tour-header.svg);
|
||||
border-bottom: 1px solid $gray-200;
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
|
||||
&__heading {
|
||||
.woocommerce-pill {
|
||||
margin-left: $gap-small;
|
||||
background-color: $studio-yellow-5;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { PRODUCTS_STORE_NAME } from '@woocommerce/data';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
|
||||
const PUBLISHED_PRODUCTS_QUERY_PARAMS = {
|
||||
status: 'publish',
|
||||
_fields: [ 'id' ],
|
||||
};
|
||||
|
||||
export const usePublishedProductsCount = () => {
|
||||
return useSelect( ( select ) => {
|
||||
const { getProductsTotalCount, hasFinishedResolution } =
|
||||
select( PRODUCTS_STORE_NAME );
|
||||
|
||||
const publishedProductsCount = getProductsTotalCount(
|
||||
PUBLISHED_PRODUCTS_QUERY_PARAMS,
|
||||
0
|
||||
) as number;
|
||||
|
||||
const loadingPublishedProductsCount = ! hasFinishedResolution(
|
||||
'getProductsTotalCount',
|
||||
[ PUBLISHED_PRODUCTS_QUERY_PARAMS, 0 ]
|
||||
);
|
||||
|
||||
return {
|
||||
publishedProductsCount,
|
||||
loadingPublishedProductsCount,
|
||||
// we consider a user new if they have no published products
|
||||
isNewUser: publishedProductsCount < 1,
|
||||
};
|
||||
} );
|
||||
};
|
|
@ -2,11 +2,10 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { ITEMS_STORE_NAME, OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { ITEMS_STORE_NAME } from '@woocommerce/data';
|
||||
import { getNewPath, navigateTo } from '@woocommerce/navigation';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
import { loadExperimentAssignment } from '@woocommerce/explat';
|
||||
import moment from 'moment';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
|
@ -14,13 +13,11 @@ import { useState } from '@wordpress/element';
|
|||
*/
|
||||
import { ProductTypeKey } from './constants';
|
||||
import { createNoticesFromResponse } from '../../../lib/notices';
|
||||
|
||||
const NEW_PRODUCT_MANAGEMENT = 'woocommerce_new_product_management_enabled';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
|
||||
export const useCreateProductByType = () => {
|
||||
const { createProductFromTemplate } = useDispatch( ITEMS_STORE_NAME );
|
||||
const [ isRequesting, setIsRequesting ] = useState< boolean >( false );
|
||||
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||
const isNewExperienceEnabled =
|
||||
window.wcAdminFeatures[ 'new-product-management-experience' ];
|
||||
|
||||
|
@ -35,23 +32,19 @@ export const useCreateProductByType = () => {
|
|||
setIsRequesting( true );
|
||||
|
||||
if ( type === 'physical' ) {
|
||||
const momentDate = moment().utc();
|
||||
const year = momentDate.format( 'YYYY' );
|
||||
const month = momentDate.format( 'MM' );
|
||||
const assignment = await loadExperimentAssignment(
|
||||
`woocommerce_product_creation_experience_${ year }${ month }_v1`
|
||||
);
|
||||
|
||||
if ( isNewExperienceEnabled ) {
|
||||
navigateTo( { url: getNewPath( {}, '/add-product', {} ) } );
|
||||
return;
|
||||
}
|
||||
|
||||
const assignment = await loadExperimentAssignment(
|
||||
'woocommerce_product_creation_experience_202306_v2'
|
||||
);
|
||||
|
||||
if ( assignment.variationName === 'treatment' ) {
|
||||
await updateOptions( {
|
||||
[ NEW_PRODUCT_MANAGEMENT ]: 'yes',
|
||||
} );
|
||||
const _feature_nonce = getAdminSetting( '_feature_nonce' );
|
||||
window.location.href = getAdminLink(
|
||||
'admin.php?page=wc-admin&path=/add-product'
|
||||
`post-new.php?post_type=product&product_block_editor=1&_feature_nonce=${ _feature_nonce }`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@
|
|||
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
right: 6%;
|
||||
right: 24px;
|
||||
max-width: 25%;
|
||||
max-height: 150px;
|
||||
width: auto;
|
||||
|
@ -264,7 +264,7 @@
|
|||
max-width: 380px;
|
||||
}
|
||||
|
||||
max-width: 75%;
|
||||
max-width: 70%;
|
||||
p,
|
||||
span {
|
||||
color: $gray-600;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Replace 'use classic editor' with 'Turn off the new product editor' in options menu#38575
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Implement the product blocks experiment within code for new users
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Product Editor Onboarding: Add About the editor... option the more menu in product block editor
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Update status only when it's changed.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Fix number of orders under tax report
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
CES modal: styling fixes and extraFiels prop added
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Add support for taxonomy meta boxes in HPOS order edit screen.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fixed a visual bug where text overlapped the image in the task list header.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add re-migrate support to HPOS CLI.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Redirect users to WooCommerce Home when Jetpack auth endpoint returns an invalid URL.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
|
||||
Fixed race condition in core profiler's plugin list fetching and also minor spinner fixes
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
Comment: Consider if user is new or not when clicking in "About the editor"
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
add HPOSToggleTrait.php to unit test loader
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix overlapping TransientNotices with footer
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Adds info about features and plugin compatibility to the data collected by WC Tracker
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Use coreprofiler_ prefix for core profiler track names
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Remove StoreDetails task when core-profiler flag is on
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Update Action Scheduler to 3.6.1.
|
|
@ -0,0 +1,5 @@
|
|||
Significance: patch
|
||||
Type: enhancement
|
||||
Comment: Add wcadmin_settings_change tracks event when adding/removing entries in shipping
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add Jetpack Connection package
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: enhancement
|
||||
|
||||
Update product editor tour/guide copy and style.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Set woocommerce_onboarding_profile.skipped when guided set is skipped
|
|
@ -15,12 +15,14 @@
|
|||
],
|
||||
"require": {
|
||||
"php": ">=7.3",
|
||||
"automattic/jetpack-autoloader": "2.10.1",
|
||||
"automattic/jetpack-constants": "1.5.1",
|
||||
"automattic/jetpack-autoloader": "2.11.18",
|
||||
"automattic/jetpack-config": "1.15.2",
|
||||
"automattic/jetpack-connection": "1.51.7",
|
||||
"automattic/jetpack-constants": "^1.6.22",
|
||||
"composer/installers": "^1.9",
|
||||
"maxmind-db/reader": "^1.11",
|
||||
"pelago/emogrifier": "^6.0",
|
||||
"woocommerce/action-scheduler": "3.5.4",
|
||||
"woocommerce/action-scheduler": "3.6.1",
|
||||
"woocommerce/woocommerce-blocks": "10.4.2"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
@ -7,35 +7,136 @@
|
|||
"content-hash": "79382fd9f5521821b18242e0214c91a1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
"version": "2.10.1",
|
||||
"name": "automattic/jetpack-a8c-mc-stats",
|
||||
"version": "v1.4.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-autoloader.git",
|
||||
"reference": "20393c4677765c3e737dcb5aee7a3f7b90dce4b3"
|
||||
"url": "https://github.com/Automattic/jetpack-a8c-mc-stats.git",
|
||||
"reference": "6743d34fe7556455e17cbe1b7c90ed39a1f69089"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/20393c4677765c3e737dcb5aee7a3f7b90dce4b3",
|
||||
"reference": "20393c4677765c3e737dcb5aee7a3f7b90dce4b3",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-a8c-mc-stats/zipball/6743d34fe7556455e17cbe1b7c90ed39a1f69089",
|
||||
"reference": "6743d34fe7556455e17cbe1b7c90ed39a1f69089",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-a8c-mc-stats",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.4.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Used to record internal usage stats for Automattic. Not visible to site owners.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-a8c-mc-stats/tree/v1.4.20"
|
||||
},
|
||||
"time": "2023-04-10T11:43:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-admin-ui",
|
||||
"version": "v0.2.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-admin-ui.git",
|
||||
"reference": "90f4de6c9d936bbf161f1c2356d98b00ba33576f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/90f4de6c9d936bbf161f1c2356d98b00ba33576f",
|
||||
"reference": "90f4de6c9d936bbf161f1c2356d98b00ba33576f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"automattic/jetpack-logo": "^1.6.1",
|
||||
"automattic/wordbless": "dev-master",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-admin-ui",
|
||||
"textdomain": "jetpack-admin-ui",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "0.2.x-dev"
|
||||
},
|
||||
"version-constants": {
|
||||
"::PACKAGE_VERSION": "src/class-admin-menu.php"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Generic Jetpack wp-admin UI elements",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.2.20"
|
||||
},
|
||||
"time": "2023-04-25T15:05:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-autoloader",
|
||||
"version": "v2.11.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-autoloader.git",
|
||||
"reference": "53cbf0528fa6931c4fa6465bccd37514f9eda720"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/53cbf0528fa6931c4fa6465bccd37514f9eda720",
|
||||
"reference": "53cbf0528fa6931c4fa6465bccd37514f9eda720",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-plugin-api": "^1.1 || ^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^1.1",
|
||||
"yoast/phpunit-polyfills": "0.2.0"
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"type": "composer-plugin",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin",
|
||||
"mirror-repo": "Automattic/jetpack-autoloader",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "2.10.x-dev"
|
||||
"dev-trunk": "2.11.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -52,29 +153,153 @@
|
|||
],
|
||||
"description": "Creates a custom autoloader for a plugin or theme.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-autoloader/tree/2.10.1"
|
||||
"source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.11.18"
|
||||
},
|
||||
"time": "2021-03-30T15:15:59+00:00"
|
||||
"time": "2023-03-29T12:51:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-constants",
|
||||
"version": "v1.5.1",
|
||||
"name": "automattic/jetpack-config",
|
||||
"version": "v1.15.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-constants.git",
|
||||
"reference": "18f772daddc8be5df76c9f4a92e017a3c2569a5b"
|
||||
"url": "https://github.com/Automattic/jetpack-config.git",
|
||||
"reference": "f1fa6e24a89192336a1499968bf8c68e173b6e34"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/18f772daddc8be5df76c9f4a92e017a3c2569a5b",
|
||||
"reference": "18f772daddc8be5df76c9f4a92e017a3c2569a5b",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-config/zipball/f1fa6e24a89192336a1499968bf8c68e173b6e34",
|
||||
"reference": "f1fa6e24a89192336a1499968bf8c68e173b6e34",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"php-mock/php-mock": "^2.1",
|
||||
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5"
|
||||
"automattic/jetpack-changelogger": "^3.3.2"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-config",
|
||||
"textdomain": "jetpack-config",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-config/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.15.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Jetpack configuration package that initializes other packages and configures Jetpack's functionality. Can be used as a base for all variants of Jetpack package usage.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-config/tree/v1.15.2"
|
||||
},
|
||||
"time": "2023-04-10T11:43:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-connection",
|
||||
"version": "v1.51.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-connection.git",
|
||||
"reference": "4c4bae836858957d9aaf6854cf4e24c3261242c4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/4c4bae836858957d9aaf6854cf4e24c3261242c4",
|
||||
"reference": "4c4bae836858957d9aaf6854cf4e24c3261242c4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"automattic/jetpack-a8c-mc-stats": "^1.4.20",
|
||||
"automattic/jetpack-admin-ui": "^0.2.19",
|
||||
"automattic/jetpack-constants": "^1.6.22",
|
||||
"automattic/jetpack-redirect": "^1.7.25",
|
||||
"automattic/jetpack-roles": "^1.4.23",
|
||||
"automattic/jetpack-status": "^1.16.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"automattic/wordbless": "@dev",
|
||||
"brain/monkey": "2.6.1",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-connection",
|
||||
"textdomain": "jetpack-connection",
|
||||
"version-constants": {
|
||||
"::PACKAGE_VERSION": "src/class-package-version.php"
|
||||
},
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.51.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"legacy",
|
||||
"src/",
|
||||
"src/webhooks"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Everything needed to connect to the Jetpack infrastructure",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-connection/tree/v1.51.7"
|
||||
},
|
||||
"time": "2023-04-10T11:44:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-constants",
|
||||
"version": "v1.6.22",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-constants.git",
|
||||
"reference": "7b5c44d763c7b0dd7498be2b41a89bfefe84834c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/7b5c44d763c7b0dd7498be2b41a89bfefe84834c",
|
||||
"reference": "7b5c44d763c7b0dd7498be2b41a89bfefe84834c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"brain/monkey": "2.6.1",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-constants",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.6.x-dev"
|
||||
}
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
|
@ -86,9 +311,160 @@
|
|||
],
|
||||
"description": "A wrapper for defining constants in a more testable way.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-constants/tree/v1.5.1"
|
||||
"source": "https://github.com/Automattic/jetpack-constants/tree/v1.6.22"
|
||||
},
|
||||
"time": "2020-10-28T19:00:31+00:00"
|
||||
"time": "2023-04-10T11:43:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-redirect",
|
||||
"version": "v1.7.25",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-redirect.git",
|
||||
"reference": "67d7dce123d4af4fec4b4fe15e99aaad85308314"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-redirect/zipball/67d7dce123d4af4fec4b4fe15e99aaad85308314",
|
||||
"reference": "67d7dce123d4af4fec4b4fe15e99aaad85308314",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"automattic/jetpack-status": "^1.16.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"brain/monkey": "2.6.1",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-redirect",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-redirect/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.7.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Utilities to build URLs to the jetpack.com/redirect/ service",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-redirect/tree/v1.7.25"
|
||||
},
|
||||
"time": "2023-04-10T11:44:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-roles",
|
||||
"version": "v1.4.23",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-roles.git",
|
||||
"reference": "f147b3e8061fc0de2a892ddc4f4156eb995545f9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/f147b3e8061fc0de2a892ddc4f4156eb995545f9",
|
||||
"reference": "f147b3e8061fc0de2a892ddc4f4156eb995545f9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"brain/monkey": "2.6.1",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-roles",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-roles/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.4.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Utilities, related with user roles and capabilities.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-roles/tree/v1.4.23"
|
||||
},
|
||||
"time": "2023-04-10T11:43:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "automattic/jetpack-status",
|
||||
"version": "v1.17.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Automattic/jetpack-status.git",
|
||||
"reference": "0032ee4bce1d4644722ba46858c702a0afa76cff"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/0032ee4bce1d4644722ba46858c702a0afa76cff",
|
||||
"reference": "0032ee4bce1d4644722ba46858c702a0afa76cff",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"automattic/jetpack-constants": "^1.6.22"
|
||||
},
|
||||
"require-dev": {
|
||||
"automattic/jetpack-changelogger": "^3.3.2",
|
||||
"automattic/jetpack-ip": "^0.1.3",
|
||||
"brain/monkey": "2.6.1",
|
||||
"yoast/phpunit-polyfills": "1.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"automattic/jetpack-autoloader": "Allow for better interoperability with other plugins that use this package."
|
||||
},
|
||||
"type": "jetpack-library",
|
||||
"extra": {
|
||||
"autotagger": true,
|
||||
"mirror-repo": "Automattic/jetpack-status",
|
||||
"changelogger": {
|
||||
"link-template": "https://github.com/Automattic/jetpack-status/compare/v${old}...v${new}"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-trunk": "1.17.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"GPL-2.0-or-later"
|
||||
],
|
||||
"description": "Used to retrieve information about the current status of Jetpack and the site overall.",
|
||||
"support": {
|
||||
"source": "https://github.com/Automattic/jetpack-status/tree/v1.17.1"
|
||||
},
|
||||
"time": "2023-05-11T05:50:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/installers",
|
||||
|
@ -588,16 +964,16 @@
|
|||
},
|
||||
{
|
||||
"name": "woocommerce/action-scheduler",
|
||||
"version": "3.5.4",
|
||||
"version": "3.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/woocommerce/action-scheduler.git",
|
||||
"reference": "9533e71b0eba4a519721dde84a34dfb161f11eb8"
|
||||
"reference": "7fd383cad3d64b419ec81bcd05bab44355a6e6ef"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/9533e71b0eba4a519721dde84a34dfb161f11eb8",
|
||||
"reference": "9533e71b0eba4a519721dde84a34dfb161f11eb8",
|
||||
"url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/7fd383cad3d64b419ec81bcd05bab44355a6e6ef",
|
||||
"reference": "7fd383cad3d64b419ec81bcd05bab44355a6e6ef",
|
||||
"shasum": ""
|
||||
},
|
||||
"require-dev": {
|
||||
|
@ -622,9 +998,9 @@
|
|||
"homepage": "https://actionscheduler.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/woocommerce/action-scheduler/issues",
|
||||
"source": "https://github.com/woocommerce/action-scheduler/tree/3.5.4"
|
||||
"source": "https://github.com/woocommerce/action-scheduler/tree/3.6.1"
|
||||
},
|
||||
"time": "2023-01-17T20:20:43+00:00"
|
||||
"time": "2023-06-14T19:23:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "woocommerce/woocommerce-blocks",
|
||||
|
|
|
@ -158,22 +158,28 @@ class WC_Report_Taxes_By_Code extends WC_Admin_Report {
|
|||
|
||||
// Merge.
|
||||
$tax_rows = array();
|
||||
// Initialize an associative array to store unique post_ids.
|
||||
$unique_post_ids = array();
|
||||
|
||||
foreach ( $tax_rows_orders + $tax_rows_partial_refunds as $tax_row ) {
|
||||
$key = $tax_row->rate_id;
|
||||
$key = $tax_row->tax_rate;
|
||||
$tax_rows[ $key ] = isset( $tax_rows[ $key ] ) ? $tax_rows[ $key ] : (object) array(
|
||||
'tax_amount' => 0,
|
||||
'shipping_tax_amount' => 0,
|
||||
'total_orders' => 0,
|
||||
);
|
||||
$tax_rows[ $key ]->total_orders += 1;
|
||||
$tax_rows[ $key ]->tax_rate = $tax_row->tax_rate;
|
||||
$tax_rows[ $key ]->tax_amount += wc_round_tax_total( $tax_row->tax_amount );
|
||||
$tax_rows[ $key ]->shipping_tax_amount += wc_round_tax_total( $tax_row->shipping_tax_amount );
|
||||
if ( ! isset( $unique_post_ids[ $key ] ) || ! in_array( $tax_row->post_id, $unique_post_ids[ $key ], true ) ) {
|
||||
$unique_post_ids[ $key ] = isset( $unique_post_ids[ $key ] ) ? $unique_post_ids[ $key ] : array();
|
||||
$unique_post_ids[ $key ][] = $tax_row->post_id;
|
||||
$tax_rows[ $key ]->total_orders += 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $tax_rows_full_refunds as $tax_row ) {
|
||||
$key = $tax_row->rate_id;
|
||||
$key = $tax_row->tax_rate;
|
||||
$tax_rows[ $key ] = isset( $tax_rows[ $key ] ) ? $tax_rows[ $key ] : (object) array(
|
||||
'tax_amount' => 0,
|
||||
'shipping_tax_amount' => 0,
|
||||
|
|
|
@ -2995,6 +2995,18 @@ class WC_AJAX {
|
|||
// That's fine, it's not in the database anyways. NEXT!
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Notify that a non-option setting has been deleted.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_update_non_option_setting',
|
||||
array(
|
||||
'id' => 'shipping_zone',
|
||||
'action' => 'delete',
|
||||
)
|
||||
);
|
||||
WC_Shipping_Zones::delete_zone( $zone_id );
|
||||
continue;
|
||||
}
|
||||
|
@ -3024,19 +3036,18 @@ class WC_AJAX {
|
|||
);
|
||||
$zone->set_zone_order( $zone_data['zone_order'] );
|
||||
}
|
||||
|
||||
global $current_tab;
|
||||
$current_tab = 'shipping';
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action( 'woocommerce_update_options' );
|
||||
$zone->save();
|
||||
}
|
||||
}
|
||||
|
||||
global $current_tab;
|
||||
$current_tab = 'shipping';
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action( 'woocommerce_update_options' );
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'zones' => WC_Shipping_Zones::get_zones( 'json' ),
|
||||
|
@ -3066,15 +3077,31 @@ class WC_AJAX {
|
|||
|
||||
$zone_id = wc_clean( wp_unslash( $_POST['zone_id'] ) );
|
||||
$zone = new WC_Shipping_Zone( $zone_id );
|
||||
// A shipping zone can be created here if the user is adding a method without first saving the shipping zone.
|
||||
if ( '' === $zone_id ) {
|
||||
/**
|
||||
* Notified that a non-option setting has been added.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_update_non_option_setting',
|
||||
array(
|
||||
'id' => 'shipping_zone',
|
||||
'action' => 'add',
|
||||
)
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Notify that a non-option setting has been updated.
|
||||
* Notify that a non-option setting has been added.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_update_non_option_setting',
|
||||
array(
|
||||
'id' => 'zone_method',
|
||||
'id' => 'zone_method',
|
||||
'action' => 'add',
|
||||
)
|
||||
);
|
||||
$instance_id = $zone->add_shipping_method( wc_clean( wp_unslash( $_POST['method_id'] ) ) );
|
||||
|
@ -3178,11 +3205,26 @@ class WC_AJAX {
|
|||
|
||||
$zone_id = wc_clean( wp_unslash( $_POST['zone_id'] ) );
|
||||
$zone = new WC_Shipping_Zone( $zone_id );
|
||||
// A shipping zone can be created here if the user is adding a method without first saving the shipping zone.
|
||||
if ( '' === $zone_id ) {
|
||||
/**
|
||||
* Notifies that a non-option setting has been added.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_update_non_option_setting',
|
||||
array(
|
||||
'id' => 'shipping_zone',
|
||||
'action' => 'add',
|
||||
)
|
||||
);
|
||||
}
|
||||
$changes = wp_unslash( $_POST['changes'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
if ( isset( $changes['zone_name'] ) ) {
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
* Notifies that a non-option setting has been updated.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
|
@ -3192,7 +3234,7 @@ class WC_AJAX {
|
|||
|
||||
if ( isset( $changes['zone_locations'] ) ) {
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
* Notifies that a non-option setting has been updated.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
|
@ -3218,7 +3260,7 @@ class WC_AJAX {
|
|||
|
||||
if ( isset( $changes['zone_postcodes'] ) ) {
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
* Notifies that a non-option setting has been updated.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
|
@ -3231,12 +3273,6 @@ class WC_AJAX {
|
|||
}
|
||||
|
||||
if ( isset( $changes['methods'] ) ) {
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action( 'woocommerce_update_non_option_setting', array( 'id' => 'zone_methods' ) );
|
||||
foreach ( $changes['methods'] as $instance_id => $data ) {
|
||||
$method_id = $wpdb->get_var( $wpdb->prepare( "SELECT method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d", $instance_id ) );
|
||||
|
||||
|
@ -3245,6 +3281,18 @@ class WC_AJAX {
|
|||
$option_key = $shipping_method->get_instance_option_key();
|
||||
if ( $wpdb->delete( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'instance_id' => $instance_id ) ) ) {
|
||||
delete_option( $option_key );
|
||||
/**
|
||||
* Notifies that a non-option setting has been deleted.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_update_non_option_setting',
|
||||
array(
|
||||
'id' => 'zone_method',
|
||||
'action' => 'delete',
|
||||
)
|
||||
);
|
||||
do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method_id, $zone_id );
|
||||
}
|
||||
continue;
|
||||
|
@ -3260,7 +3308,7 @@ class WC_AJAX {
|
|||
|
||||
if ( isset( $method_data['method_order'] ) ) {
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
* Notifies that a non-option setting has been updated.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
|
@ -3270,7 +3318,7 @@ class WC_AJAX {
|
|||
|
||||
if ( isset( $method_data['enabled'] ) ) {
|
||||
/**
|
||||
* Completes the saving process for options.
|
||||
* Notifies that a non-option setting has been updated.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
|
@ -3385,6 +3433,18 @@ class WC_AJAX {
|
|||
// That's fine, it's not in the database anyways. NEXT!
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Notifies that a non-option setting has been deleted.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_update_non_option_setting',
|
||||
array(
|
||||
'id' => 'shipping_class',
|
||||
'action' => 'delete',
|
||||
)
|
||||
);
|
||||
wp_delete_term( $term_id, 'product_shipping_class' );
|
||||
continue;
|
||||
}
|
||||
|
@ -3426,9 +3486,27 @@ class WC_AJAX {
|
|||
if ( empty( $update_args['name'] ) ) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* Notifies that a non-option setting has been added.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action(
|
||||
'woocommerce_update_non_option_setting',
|
||||
array(
|
||||
'id' => 'shipping_class',
|
||||
'action' => 'add',
|
||||
)
|
||||
);
|
||||
$inserted_term = wp_insert_term( $update_args['name'], 'product_shipping_class', $update_args );
|
||||
$term_id = is_wp_error( $inserted_term ) ? 0 : $inserted_term['term_id'];
|
||||
} else {
|
||||
/**
|
||||
* Notifies that a non-option setting has been updated.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
do_action( 'woocommerce_update_non_option_setting', array( 'id' => 'shipping_class' ) );
|
||||
wp_update_term( $term_id, 'product_shipping_class', $update_args );
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
|
||||
use Automattic\Jetpack\Constants;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Utilities\{ FeaturesUtil, OrderUtil, PluginUtil };
|
||||
use Automattic\WooCommerce\Internal\Utilities\BlocksUtil;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
@ -153,7 +153,6 @@ class WC_Tracker {
|
|||
$data['inactive_plugins'] = $all_plugins['inactive_plugins'];
|
||||
|
||||
// Jetpack & WooCommerce Connect.
|
||||
|
||||
$data['jetpack_version'] = Constants::is_defined( 'JETPACK__VERSION' ) ? Constants::get_constant( 'JETPACK__VERSION' ) : 'none';
|
||||
$data['jetpack_connected'] = ( class_exists( 'Jetpack' ) && is_callable( 'Jetpack::is_active' ) && Jetpack::is_active() ) ? 'yes' : 'no';
|
||||
$data['jetpack_is_staging'] = self::is_jetpack_staging_site() ? 'yes' : 'no';
|
||||
|
@ -177,6 +176,9 @@ class WC_Tracker {
|
|||
// Shipping method info.
|
||||
$data['shipping_methods'] = self::get_active_shipping_methods();
|
||||
|
||||
// Features.
|
||||
$data['enabled_features'] = self::get_enabled_features();
|
||||
|
||||
// Get all WooCommerce options info.
|
||||
$data['settings'] = self::get_all_woocommerce_options_values();
|
||||
|
||||
|
@ -329,6 +331,10 @@ class WC_Tracker {
|
|||
if ( isset( $v['PluginURI'] ) ) {
|
||||
$formatted['plugin_uri'] = wp_strip_all_tags( $v['PluginURI'] );
|
||||
}
|
||||
$formatted['feature_compatibility'] = array();
|
||||
if ( wc_get_container()->get( PluginUtil::class )->is_woocommerce_aware_plugin( $k ) ) {
|
||||
$formatted['feature_compatibility'] = array_filter( FeaturesUtil::get_compatible_features_for_plugin( $k ) );
|
||||
}
|
||||
if ( in_array( $k, $active_plugins_keys, true ) ) {
|
||||
// Remove active plugins from list so we can show active and inactive separately.
|
||||
unset( $plugins[ $k ] );
|
||||
|
@ -904,6 +910,23 @@ class WC_Tracker {
|
|||
return $active_methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of slugs for WC features that are enabled on the site.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private static function get_enabled_features() {
|
||||
$all_features = FeaturesUtil::get_features( true, true );
|
||||
$enabled_features = array_filter(
|
||||
$all_features,
|
||||
function( $feature ) {
|
||||
return $feature['is_enabled'];
|
||||
}
|
||||
);
|
||||
|
||||
return array_keys( $enabled_features );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all options starting with woocommerce_ prefix.
|
||||
*
|
||||
|
|
|
@ -204,6 +204,22 @@ final class WooCommerce {
|
|||
do_action( 'woocommerce_loaded' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiali Jetpack Connection Config.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init_jetpack_connection_config() {
|
||||
$config = new Automattic\Jetpack\Config();
|
||||
$config->ensure(
|
||||
'connection',
|
||||
array(
|
||||
'slug' => 'woocommerce',
|
||||
'name' => __( 'WooCommerce', 'woocommerce' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into actions and filters.
|
||||
*
|
||||
|
@ -214,6 +230,7 @@ final class WooCommerce {
|
|||
register_shutdown_function( array( $this, 'log_errors' ) );
|
||||
|
||||
add_action( 'plugins_loaded', array( $this, 'on_plugins_loaded' ), -1 );
|
||||
add_action( 'plugins_loaded', array( $this, 'init_jetpack_connection_config' ), 1 );
|
||||
add_action( 'admin_notices', array( $this, 'build_dependencies_notice' ) );
|
||||
add_action( 'after_setup_theme', array( $this, 'setup_environment' ) );
|
||||
add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 );
|
||||
|
|
|
@ -43,6 +43,21 @@ class WC_Settings_Tracking {
|
|||
*/
|
||||
protected $modified_options = array();
|
||||
|
||||
/**
|
||||
* List of options that have been deleted.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $deleted_options = array();
|
||||
|
||||
/**
|
||||
* List of options that have been added.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $added_options = array();
|
||||
|
||||
|
||||
/**
|
||||
* Toggled options.
|
||||
*
|
||||
|
@ -74,7 +89,11 @@ class WC_Settings_Tracking {
|
|||
if ( ! in_array( $option['id'], $this->allowed_options, true ) ) {
|
||||
$this->allowed_options[] = $option['id'];
|
||||
}
|
||||
if ( ! in_array( $option['id'], $this->updated_options, true ) ) {
|
||||
if ( 'add' === $option['action'] ) {
|
||||
$this->added_options[] = $option['id'];
|
||||
} elseif ( 'delete' === $option['action'] ) {
|
||||
$this->deleted_options[] = $option['id'];
|
||||
} elseif ( ! in_array( $option['id'], $this->updated_options, true ) ) {
|
||||
$this->updated_options[] = $option['id'];
|
||||
}
|
||||
}
|
||||
|
@ -143,13 +162,23 @@ class WC_Settings_Tracking {
|
|||
public function send_settings_change_event() {
|
||||
global $current_tab, $current_section;
|
||||
|
||||
if ( empty( $this->updated_options ) ) {
|
||||
if ( empty( $this->updated_options ) && empty( $this->deleted_options ) && empty( $this->added_options ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$properties = array(
|
||||
'settings' => implode( ',', $this->updated_options ),
|
||||
);
|
||||
$properties = array();
|
||||
|
||||
if ( ! empty( $this->updated_options ) ) {
|
||||
$properties['settings'] = implode( ',', $this->updated_options );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->deleted_options ) ) {
|
||||
$properties['deleted'] = implode( ',', $this->deleted_options );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->added_options ) ) {
|
||||
$properties['added'] = implode( ',', $this->added_options );
|
||||
}
|
||||
|
||||
foreach ( $this->toggled_options as $state => $options ) {
|
||||
if ( ! empty( $options ) ) {
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Admin\API;
|
|||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
use ActionScheduler;
|
||||
use Automattic\Jetpack\Connection\Manager;
|
||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
||||
use Automattic\WooCommerce\Admin\PluginsInstallLoggers\AsynPluginsInstallLogger;
|
||||
use WC_REST_Data_Controller;
|
||||
|
@ -95,6 +96,34 @@ class OnboardingPlugins extends WC_REST_Data_Controller {
|
|||
'schema' => array( $this, 'get_install_async_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
// This is an experimental endpoint and is subject to change in the future.
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/jetpack-authorization-url',
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_jetpack_authorization_url' ),
|
||||
'permission_callback' => array( $this, 'can_install_plugins' ),
|
||||
'args' => array(
|
||||
'redirect_url' => array(
|
||||
'description' => 'The URL to redirect to after authorization',
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'required' => true,
|
||||
),
|
||||
'from' => array(
|
||||
'description' => 'from value for the jetpack authorization page',
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'required' => false,
|
||||
'default' => 'woocommerce-onboarding',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -183,6 +212,43 @@ class OnboardingPlugins extends WC_REST_Data_Controller {
|
|||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return Jetpack authorization URL.
|
||||
*
|
||||
* @param WP_REST_Request $request WP_REST_Request object.
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception If there is an error registering the site.
|
||||
*/
|
||||
public function get_jetpack_authorization_url( WP_REST_Request $request ) {
|
||||
$manager = new Manager( 'woocommerce' );
|
||||
$errors = new WP_Error();
|
||||
|
||||
// Register the site to wp.com.
|
||||
if ( ! $manager->is_connected() ) {
|
||||
$result = $manager->try_registration();
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$errors->add( $result->get_error_code(), $result->get_error_message() );
|
||||
}
|
||||
}
|
||||
|
||||
$redirect_url = $request->get_param( 'redirect_url' );
|
||||
$calypso_env = defined( 'WOOCOMMERCE_CALYPSO_ENVIRONMENT' ) && in_array( WOOCOMMERCE_CALYPSO_ENVIRONMENT, [ 'development', 'wpcalypso', 'horizon', 'stage' ], true ) ? WOOCOMMERCE_CALYPSO_ENVIRONMENT : 'production';
|
||||
|
||||
return [
|
||||
'success' => ! $errors->has_errors(),
|
||||
'errors' => $errors->get_error_messages(),
|
||||
'url' => add_query_arg(
|
||||
[
|
||||
'from' => $request->get_param( 'from' ),
|
||||
'calypso_env' => $calypso_env,
|
||||
],
|
||||
$manager->get_authorization_url( null, $redirect_url )
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user has permission to install plugins
|
||||
*
|
||||
|
|
|
@ -108,21 +108,27 @@ class TaskLists {
|
|||
* Initialize default lists.
|
||||
*/
|
||||
public static function init_default_lists() {
|
||||
$tasks = array(
|
||||
'StoreDetails',
|
||||
'Purchase',
|
||||
'Products',
|
||||
'WooCommercePayments',
|
||||
'Payments',
|
||||
'Tax',
|
||||
'Shipping',
|
||||
'Marketing',
|
||||
'Appearance',
|
||||
);
|
||||
|
||||
if ( Features::is_enabled( 'core-profiler' ) ) {
|
||||
array_shift( $tasks );
|
||||
}
|
||||
|
||||
self::add_list(
|
||||
array(
|
||||
'id' => 'setup',
|
||||
'title' => __( 'Get ready to start selling', 'woocommerce' ),
|
||||
'tasks' => array(
|
||||
'StoreDetails',
|
||||
'Purchase',
|
||||
'Products',
|
||||
'WooCommercePayments',
|
||||
'Payments',
|
||||
'Tax',
|
||||
'Shipping',
|
||||
'Marketing',
|
||||
'Appearance',
|
||||
),
|
||||
'tasks' => $tasks,
|
||||
'display_progress_header' => true,
|
||||
'event_prefix' => 'tasklist_',
|
||||
'options' => array(
|
||||
|
|
|
@ -304,6 +304,11 @@ class CLIRunner {
|
|||
* ---
|
||||
* default: Output of function `wc_get_order_types( 'cot-migration' )`
|
||||
*
|
||||
* [--re-migrate]
|
||||
* : Attempt to re-migrate orders that failed verification. You should only use this option when you have never run the site with HPOS as authoritative source of order data yet, or you have manually checked the reported errors, otherwise, you risk stale data overwriting the more recent data.
|
||||
* This option can only be enabled when --verbose flag is also set.
|
||||
* default: false
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* # Verify migrated order data, 500 orders at a time.
|
||||
|
@ -327,6 +332,7 @@ class CLIRunner {
|
|||
'end-at' => - 1,
|
||||
'verbose' => false,
|
||||
'order-types' => '',
|
||||
're-migrate' => false,
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -340,6 +346,7 @@ class CLIRunner {
|
|||
$batch_size = ( (int) $assoc_args['batch-size'] ) === 0 ? 500 : (int) $assoc_args['batch-size'];
|
||||
$verbose = (bool) $assoc_args['verbose'];
|
||||
$order_types = wc_get_order_types( 'cot-migration' );
|
||||
$remigrate = (bool) $assoc_args['re-migrate'];
|
||||
if ( ! empty( $assoc_args['order-types'] ) ) {
|
||||
$passed_order_types = array_map( 'trim', explode( ',', $assoc_args['order-types'] ) );
|
||||
$order_types = array_intersect( $order_types, $passed_order_types );
|
||||
|
@ -415,6 +422,36 @@ class CLIRunner {
|
|||
$errors
|
||||
)
|
||||
);
|
||||
if ( $remigrate ) {
|
||||
WP_CLI::warning(
|
||||
sprintf(
|
||||
__( 'Attempting to remigrate...', 'woocommerce' )
|
||||
)
|
||||
);
|
||||
$failed_ids = array_keys( $failed_ids_in_current_batch );
|
||||
$this->synchronizer->process_batch( $failed_ids );
|
||||
$errors_in_remigrate_batch = $this->post_to_cot_migrator->verify_migrated_orders( $failed_ids );
|
||||
$errors_in_remigrate_batch = $this->verify_meta_data( $failed_ids, $errors_in_remigrate_batch );
|
||||
if ( count( $errors_in_remigrate_batch ) > 0 ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- This is a CLI command and debugging code is intended.
|
||||
$formatted_errors = print_r( $errors_in_remigrate_batch, true );
|
||||
WP_CLI::warning(
|
||||
sprintf(
|
||||
/* Translators: %1$d is number of errors and %2$s is the formatted array of order IDs. */
|
||||
_n(
|
||||
'%1$d error found: %2$s when re-migrating order. Please review the error above.',
|
||||
'%1$d errors found: %2$s when re-migrating orders. Please review the errors above.',
|
||||
count( $errors_in_remigrate_batch ),
|
||||
'woocommerce'
|
||||
),
|
||||
count( $errors_in_remigrate_batch ),
|
||||
$formatted_errors
|
||||
)
|
||||
);
|
||||
} else {
|
||||
WP_CLI::warning( 'Re-migration successful.', 'woocommerce' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$progress->tick();
|
||||
|
|
|
@ -243,13 +243,7 @@ abstract class MetaToCustomTableMigrator extends TableMigrator {
|
|||
$to_insert = array_diff_key( $data['data'], $existing_records );
|
||||
$this->process_insert_batch( $to_insert );
|
||||
|
||||
$existing_records = array_filter(
|
||||
$existing_records,
|
||||
function( $record_data ) {
|
||||
return '1' === $record_data->modified;
|
||||
}
|
||||
);
|
||||
$to_update = array_intersect_key( $data['data'], $existing_records );
|
||||
$to_update = array_intersect_key( $data['data'], $existing_records );
|
||||
$this->process_update_batch( $to_update, $existing_records );
|
||||
}
|
||||
|
||||
|
@ -357,38 +351,13 @@ abstract class MetaToCustomTableMigrator extends TableMigrator {
|
|||
|
||||
$entity_id_placeholder = implode( ',', array_fill( 0, count( $entity_ids ), '%d' ) );
|
||||
|
||||
// Additional SQL to check if the row needs update according to the column mapping.
|
||||
// The IFNULL and CHAR(0) "hack" is needed because NULLs can't be directly compared in SQL.
|
||||
$modified_selector = array();
|
||||
$core_column_mapping = array_filter(
|
||||
$this->core_column_mapping,
|
||||
function( $mapping ) {
|
||||
return ! isset( $mapping['select_clause'] );
|
||||
}
|
||||
);
|
||||
foreach ( $core_column_mapping as $column_name => $mapping ) {
|
||||
if ( $column_name === $source_primary_key_column ) {
|
||||
continue;
|
||||
}
|
||||
$modified_selector[] =
|
||||
"IFNULL(source.$column_name,CHAR(0)) != IFNULL(destination.{$mapping['destination']},CHAR(0))"
|
||||
. ( 'string' === $mapping['type'] ? ' COLLATE ' . $wpdb->collate : '' );
|
||||
}
|
||||
|
||||
if ( empty( $modified_selector ) ) {
|
||||
$modified_selector = ', 1 AS modified';
|
||||
} else {
|
||||
$modified_selector = trim( implode( ' OR ', $modified_selector ) );
|
||||
$modified_selector = ", if( $modified_selector, 1, 0 ) AS modified";
|
||||
}
|
||||
|
||||
$additional_where = $this->get_additional_where_clause_for_get_data_to_insert_or_update( $entity_ids );
|
||||
|
||||
$already_migrated_entity_ids = $this->db_get_results(
|
||||
$wpdb->prepare(
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- All columns and table names are hardcoded.
|
||||
"
|
||||
SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id $modified_selector
|
||||
SELECT source.`$source_primary_key_column` as source_id, destination.`$destination_primary_key_column` as destination_id
|
||||
FROM `$destination_table` destination
|
||||
JOIN `$source_table` source ON source.`$source_destination_join_column` = destination.`$destination_source_join_column`
|
||||
WHERE source.`$source_primary_key_column` IN ( $entity_id_placeholder ) $additional_where
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\CustomMetaBox;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\TaxonomiesMetaBox;
|
||||
|
||||
/**
|
||||
* Class Edit.
|
||||
|
@ -26,6 +27,13 @@ class Edit {
|
|||
*/
|
||||
private $custom_meta_box;
|
||||
|
||||
/**
|
||||
* Instance of the TaxonomiesMetaBox class. Used to render meta box for taxonomies.
|
||||
*
|
||||
* @var TaxonomiesMetaBox
|
||||
*/
|
||||
private $taxonomies_meta_box;
|
||||
|
||||
/**
|
||||
* Instance of WC_Order to be used in metaboxes.
|
||||
*
|
||||
|
@ -110,10 +118,16 @@ class Edit {
|
|||
if ( ! isset( $this->custom_meta_box ) ) {
|
||||
$this->custom_meta_box = wc_get_container()->get( CustomMetaBox::class );
|
||||
}
|
||||
|
||||
if ( ! isset( $this->taxonomies_meta_box ) ) {
|
||||
$this->taxonomies_meta_box = wc_get_container()->get( TaxonomiesMetaBox::class );
|
||||
}
|
||||
|
||||
$this->add_save_meta_boxes();
|
||||
$this->handle_order_update();
|
||||
$this->add_order_meta_boxes( $this->screen_id, __( 'Order', 'woocommerce' ) );
|
||||
$this->add_order_specific_meta_box();
|
||||
$this->add_order_taxonomies_meta_box();
|
||||
|
||||
/**
|
||||
* From wp-admin/includes/meta-boxes.php.
|
||||
|
@ -159,6 +173,15 @@ class Edit {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom meta box.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_order_taxonomies_meta_box() {
|
||||
$this->taxonomies_meta_box->add_taxonomies_meta_boxes( $this->screen_id, $this->order->get_type() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of updating order data. Fires action that metaboxes can hook to for order data updating.
|
||||
*
|
||||
|
@ -176,6 +199,10 @@ class Edit {
|
|||
|
||||
check_admin_referer( $this->get_order_edit_nonce_action() );
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized later on by taxonomies_meta_box object.
|
||||
$taxonomy_input = isset( $_POST['tax_input'] ) ? wp_unslash( $_POST['tax_input'] ) : null;
|
||||
$this->taxonomies_meta_box->save_taxonomies( $this->order, $taxonomy_input );
|
||||
|
||||
/**
|
||||
* Save meta for shop order.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
|
||||
/**
|
||||
* TaxonomiesMetaBox class, renders taxonomy sidebar widget on order edit screen.
|
||||
*/
|
||||
class TaxonomiesMetaBox {
|
||||
|
||||
/**
|
||||
* Order Table data store class.
|
||||
*
|
||||
* @var OrdersTableDataStore
|
||||
*/
|
||||
private $orders_table_data_store;
|
||||
|
||||
/**
|
||||
* Dependency injection init method.
|
||||
*
|
||||
* @param OrdersTableDataStore $orders_table_data_store Order Table data store class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init( OrdersTableDataStore $orders_table_data_store ) {
|
||||
$this->orders_table_data_store = $orders_table_data_store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers meta boxes to be rendered in order edit screen for taxonomies.
|
||||
*
|
||||
* Note: This is re-implementation of part of WP core's `register_and_do_post_meta_boxes` function. Since the code block that add meta box for taxonomies is not filterable, we have to re-implement it.
|
||||
*
|
||||
* @param string $screen_id Screen ID.
|
||||
* @param string $order_type Order type to register meta boxes for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_taxonomies_meta_boxes( string $screen_id, string $order_type ) {
|
||||
include_once ABSPATH . 'wp-admin/includes/meta-boxes.php';
|
||||
$taxonomies = get_object_taxonomies( $order_type );
|
||||
// All taxonomies.
|
||||
foreach ( $taxonomies as $tax_name ) {
|
||||
$taxonomy = get_taxonomy( $tax_name );
|
||||
if ( ! $taxonomy->show_ui || false === $taxonomy->meta_box_cb ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'post_categories_meta_box' === $taxonomy->meta_box_cb ) {
|
||||
$taxonomy->meta_box_cb = array( $this, 'order_categories_meta_box' );
|
||||
}
|
||||
|
||||
if ( 'post_tags_meta_box' === $taxonomy->meta_box_cb ) {
|
||||
$taxonomy->meta_box_cb = array( $this, 'order_tags_meta_box' );
|
||||
}
|
||||
|
||||
$label = $taxonomy->labels->name;
|
||||
|
||||
if ( ! is_taxonomy_hierarchical( $tax_name ) ) {
|
||||
$tax_meta_box_id = 'tagsdiv-' . $tax_name;
|
||||
} else {
|
||||
$tax_meta_box_id = $tax_name . 'div';
|
||||
}
|
||||
|
||||
add_meta_box(
|
||||
$tax_meta_box_id,
|
||||
$label,
|
||||
$taxonomy->meta_box_cb,
|
||||
$screen_id,
|
||||
'side',
|
||||
'core',
|
||||
array(
|
||||
'taxonomy' => $tax_name,
|
||||
'__back_compat_meta_box' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save handler for taxonomy data.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array|null $taxonomy_input Taxonomy input passed from input.
|
||||
*/
|
||||
public function save_taxonomies( \WC_Abstract_Order $order, $taxonomy_input ) {
|
||||
if ( ! isset( $taxonomy_input ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sanitized_tax_input = $this->sanitize_tax_input( $taxonomy_input );
|
||||
|
||||
$sanitized_tax_input = $this->orders_table_data_store->init_default_taxonomies( $order, $sanitized_tax_input );
|
||||
$this->orders_table_data_store->set_custom_taxonomies( $order, $sanitized_tax_input );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize taxonomy input by calling sanitize callbacks for each registered taxonomy.
|
||||
*
|
||||
* @param array|null $taxonomy_data Nonce verified taxonomy input.
|
||||
*
|
||||
* @return array Sanitized taxonomy input.
|
||||
*/
|
||||
private function sanitize_tax_input( $taxonomy_data ) : array {
|
||||
$sanitized_tax_input = array();
|
||||
if ( ! is_array( $taxonomy_data ) ) {
|
||||
return $sanitized_tax_input;
|
||||
}
|
||||
|
||||
// Convert taxonomy input to term IDs, to avoid ambiguity.
|
||||
foreach ( $taxonomy_data as $taxonomy => $terms ) {
|
||||
$tax_object = get_taxonomy( $taxonomy );
|
||||
if ( $tax_object && isset( $tax_object->meta_box_sanitize_cb ) ) {
|
||||
$sanitized_tax_input[ $taxonomy ] = call_user_func_array( $tax_object->meta_box_sanitize_cb, array( $taxonomy, $terms ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $sanitized_tax_input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the categories meta box to the order screen. This is just a wrapper around the post_categories_meta_box.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array $box Meta box args.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function order_categories_meta_box( $order, $box ) {
|
||||
$post = get_post( $order->get_id() );
|
||||
post_categories_meta_box( $post, $box );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the tags meta box to the order screen. This is just a wrapper around the post_tags_meta_box.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array $box Meta box args.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function order_tags_meta_box( $order, $box ) {
|
||||
$post = get_post( $order->get_id() );
|
||||
post_tags_meta_box( $post, $box );
|
||||
}
|
||||
}
|
|
@ -1641,6 +1641,84 @@ FROM $order_meta_table
|
|||
|
||||
$changes = $order->get_changes();
|
||||
$this->update_address_index_meta( $order, $changes );
|
||||
$default_taxonomies = $this->init_default_taxonomies( $order, array() );
|
||||
$this->set_custom_taxonomies( $order, $default_taxonomies );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default taxonomies for the order.
|
||||
*
|
||||
* Note: This is re-implementation of part of WP core's `wp_insert_post` function. Since the code block that set default taxonomies is not filterable, we have to re-implement it.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array $sanitized_tax_input Sanitized taxonomy input.
|
||||
*
|
||||
* @return array Sanitized tax input with default taxonomies.
|
||||
*/
|
||||
public function init_default_taxonomies( \WC_Abstract_Order $order, array $sanitized_tax_input ) {
|
||||
if ( 'auto-draft' === $order->get_status() ) {
|
||||
return $sanitized_tax_input;
|
||||
}
|
||||
|
||||
foreach ( get_object_taxonomies( $order->get_type(), 'object' ) as $taxonomy => $tax_object ) {
|
||||
if ( empty( $tax_object->default_term ) ) {
|
||||
return $sanitized_tax_input;
|
||||
}
|
||||
|
||||
// Filter out empty terms.
|
||||
if ( isset( $sanitized_tax_input[ $taxonomy ] ) && is_array( $sanitized_tax_input[ $taxonomy ] ) ) {
|
||||
$sanitized_tax_input[ $taxonomy ] = array_filter( $sanitized_tax_input[ $taxonomy ] );
|
||||
}
|
||||
|
||||
// Passed custom taxonomy list overwrites the existing list if not empty.
|
||||
$terms = wp_get_object_terms( $order->get_id(), $taxonomy, array( 'fields' => 'ids' ) );
|
||||
if ( ! empty( $terms ) && empty( $sanitized_tax_input[ $taxonomy ] ) ) {
|
||||
$sanitized_tax_input[ $taxonomy ] = $terms;
|
||||
}
|
||||
|
||||
if ( empty( $sanitized_tax_input[ $taxonomy ] ) ) {
|
||||
$default_term_id = get_option( 'default_term_' . $taxonomy );
|
||||
if ( ! empty( $default_term_id ) ) {
|
||||
$sanitized_tax_input[ $taxonomy ] = array( (int) $default_term_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sanitized_tax_input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom taxonomies for the order.
|
||||
*
|
||||
* Note: This is re-implementation of part of WP core's `wp_insert_post` function. Since the code block that set custom taxonomies is not filterable, we have to re-implement it.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array $sanitized_tax_input Sanitized taxonomy input.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_custom_taxonomies( \WC_Abstract_Order $order, array $sanitized_tax_input ) {
|
||||
if ( empty( $sanitized_tax_input ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $sanitized_tax_input as $taxonomy => $tags ) {
|
||||
$taxonomy_obj = get_taxonomy( $taxonomy );
|
||||
|
||||
if ( ! $taxonomy_obj ) {
|
||||
/* translators: %s: Taxonomy name. */
|
||||
_doing_it_wrong( __FUNCTION__, esc_html( sprintf( __( 'Invalid taxonomy: %s.', 'woocommerce' ), $taxonomy ) ), '7.9.0' );
|
||||
continue;
|
||||
}
|
||||
|
||||
// array = hierarchical, string = non-hierarchical.
|
||||
if ( is_array( $tags ) ) {
|
||||
$tags = array_filter( $tags );
|
||||
}
|
||||
|
||||
if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
|
||||
wp_set_post_terms( $order->get_id(), $tags, $taxonomy );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1738,8 +1816,8 @@ FROM $order_meta_table
|
|||
|
||||
$changes['type'] = $order->get_type();
|
||||
|
||||
// Make sure 'status' is correct.
|
||||
if ( array_key_exists( 'status', $column_mapping ) ) {
|
||||
// Make sure 'status' is correctly prefixed.
|
||||
if ( array_key_exists( 'status', $column_mapping ) && array_key_exists( 'status', $changes ) ) {
|
||||
$changes['status'] = $this->get_post_status( $order );
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,9 @@ use Automattic\WooCommerce\Internal\Admin\Orders\COTRedirectionController;
|
|||
use Automattic\WooCommerce\Internal\Admin\Orders\Edit;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\EditLock;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\ListTable;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\TaxonomiesMetaBox;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\PageController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
|
||||
/**
|
||||
|
@ -28,6 +30,7 @@ class OrderAdminServiceProvider extends AbstractServiceProvider {
|
|||
Edit::class,
|
||||
ListTable::class,
|
||||
EditLock::class,
|
||||
TaxonomiesMetaBox::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -41,5 +44,6 @@ class OrderAdminServiceProvider extends AbstractServiceProvider {
|
|||
$this->share( Edit::class )->addArgument( PageController::class );
|
||||
$this->share( ListTable::class )->addArgument( PageController::class );
|
||||
$this->share( EditLock::class );
|
||||
$this->share( TaxonomiesMetaBox::class )->addArgument( OrdersTableDataStore::class );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -264,6 +264,7 @@ class WC_Unit_Tests_Bootstrap {
|
|||
|
||||
// Traits.
|
||||
require_once $this->tests_dir . '/framework/traits/trait-wc-rest-api-complex-meta.php';
|
||||
require_once dirname( $this->tests_dir ) . '/php/helpers/HPOSToggleTrait.php';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,12 +2,8 @@
|
|||
|
||||
namespace Automattic\WooCommerce\RestApi\UnitTests;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Internal\Features\FeaturesController;
|
||||
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
|
||||
use WC_Data_Store;
|
||||
|
||||
/**
|
||||
* Trait HPOSToggleTrait.
|
||||
|
|
|
@ -190,7 +190,7 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_apply_coupon_across_status() {
|
||||
$coupon_code = 'coupon_test_count_across_status';
|
||||
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
|
||||
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
|
||||
$this->assertEquals( 0, $coupon->get_usage_count() );
|
||||
|
||||
$order = WC_Helper_Order::create_order();
|
||||
|
@ -253,8 +253,8 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
|
|||
*/
|
||||
public function test_apply_coupon_stores_meta_data() {
|
||||
$coupon_code = 'coupon_test_meta_data';
|
||||
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
|
||||
$order = WC_Helper_Order::create_order();
|
||||
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
|
||||
$order = WC_Helper_Order::create_order();
|
||||
$order->set_status( 'processing' );
|
||||
$order->save();
|
||||
$order->apply_coupon( $coupon_code );
|
||||
|
@ -324,4 +324,29 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
|
|||
$order = wc_get_order( $order->get_id() );
|
||||
$this->assertInstanceOf( Automattic\WooCommerce\Admin\Overrides\Order::class, $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox When a taxonomy with a default term is set on the order, it's inserted when a new order is created.
|
||||
*/
|
||||
public function test_default_term_for_custom_taxonomy() {
|
||||
$custom_taxonomy = register_taxonomy(
|
||||
'custom_taxonomy',
|
||||
'shop_order',
|
||||
array(
|
||||
'default_term' => 'new_term',
|
||||
),
|
||||
);
|
||||
|
||||
// Set user who has access to create term.
|
||||
$current_user_id = get_current_user_id();
|
||||
$user = new WP_User( wp_create_user( 'test', '' ) );
|
||||
$user->set_role( 'administrator' );
|
||||
wp_set_current_user( $user->ID );
|
||||
|
||||
$order = wc_create_order();
|
||||
|
||||
wp_set_current_user( $current_user_id );
|
||||
$order_terms = wp_list_pluck( wp_get_object_terms( $order->get_id(), $custom_taxonomy->name ), 'name' );
|
||||
$this->assertContains( 'new_term', $order_terms );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,4 +118,13 @@ class WC_Tracker_Test extends \WC_Unit_Test_Case {
|
|||
$this->assertEquals( ( $order_count / count( $created_via_entries ) ), $order_data['created_via'][ $created_via_entry ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox Test enabled features tracking data.
|
||||
*/
|
||||
public function test_get_tracking_data_enabled_features() {
|
||||
$tracking_data = WC_Tracker::get_tracking_data();
|
||||
|
||||
$this->assertIsArray( $tracking_data['enabled_features'] );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
|
|||
use Automattic\WooCommerce\RestApi\UnitTests\HPOSToggleTrait;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
require_once __DIR__ . '/../../../../helpers/HPOSToggleTrait.php';
|
||||
|
||||
/**
|
||||
* Class OrdersTableDataStoreTests.
|
||||
*
|
||||
|
@ -2132,4 +2130,20 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case {
|
|||
|
||||
$this->assertEquals( 1, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* @testDox When saving an order, status is automatically prefixed even if it was not earlier.
|
||||
*/
|
||||
public function test_get_db_row_from_order_only_prefixed_status_is_written_to_db() {
|
||||
$order = wc_create_order();
|
||||
|
||||
$order->set_status( 'completed' );
|
||||
$db_row_callback = function ( $order, $only_changes ) {
|
||||
return $this->get_db_row_from_order( $order, $this->order_column_mapping, $only_changes );
|
||||
};
|
||||
|
||||
$db_row = $db_row_callback->call( $this->sut, $order, false );
|
||||
|
||||
$this->assertEquals( 'wc-completed', $db_row['data']['status'] );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,3 +60,8 @@ function wc_get_container() {
|
|||
|
||||
// Global for backwards compatibility.
|
||||
$GLOBALS['woocommerce'] = WC();
|
||||
|
||||
// Jetpack's Rest_Authentication needs to be initialized even before plugins_loaded.
|
||||
if ( class_exists( \Automattic\Jetpack\Connection\Rest_Authentication::class ) ) {
|
||||
\Automattic\Jetpack\Connection\Rest_Authentication::init();
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
* Register the JS and CSS.
|
||||
*/
|
||||
function add_extension_register_script() {
|
||||
if (
|
||||
! method_exists( 'Automattic\WooCommerce\Admin\Loader', 'is_admin_or_embed_page' ) ||
|
||||
! \Automattic\WooCommerce\Admin\Loader::is_admin_or_embed_page()
|
||||
if (
|
||||
! method_exists( 'Automattic\WooCommerce\Admin\PageController', 'is_admin_or_embed_page' ) ||
|
||||
! \Automattic\WooCommerce\Admin\PageController::is_admin_or_embed_page()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue