diff --git a/assets/source/catalog-sync/App.js b/assets/source/catalog-sync/App.js index 26c5c20a5..1ce573175 100644 --- a/assets/source/catalog-sync/App.js +++ b/assets/source/catalog-sync/App.js @@ -46,16 +46,14 @@ import { useSettingsSelect } from '../setup-guide/app/helpers/effects'; const CatalogSyncApp = () => { const adsCampaignIsActive = useSettingsSelect()?.ads_campaign_is_active; - const couponRedeemErrorID = useSettingsSelect()?.account_data - ?.coupon_redeem_info?.error_id; + const couponRedeemErrorID = + useSettingsSelect()?.account_data?.coupon_redeem_info?.error_id; useCreateNotice( wcSettings.pinterest_for_woocommerce.error ); - const [ isOnboardingModalOpen, setIsOnboardingModalOpen ] = useState( - false - ); - const [ isAdCreditsNoticeOpen, setIsAdCreditsNoticeOpen ] = useState( - false - ); + const [ isOnboardingModalOpen, setIsOnboardingModalOpen ] = + useState( false ); + const [ isAdCreditsNoticeOpen, setIsAdCreditsNoticeOpen ] = + useState( false ); const userInteractions = useSelect( ( select ) => select( USER_INTERACTION_STORE_NAME ).getUserInteractions() diff --git a/assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js b/assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js index c631ccfe9..d304adffc 100644 --- a/assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js +++ b/assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js @@ -29,8 +29,8 @@ const OnboardingErrorModal = ( { onCloseModal } ) => { const NOT_AVAILABLE_IN_COUNTRY_OR_CURRENCY_ERROR = 2327; const WRONG_BILLING_PROFILE_ERROR = 2006; - const couponRedeemInfo = useSettingsSelect()?.account_data - ?.coupon_redeem_info; + const couponRedeemInfo = + useSettingsSelect()?.account_data?.coupon_redeem_info; let errorMessageText = ''; switch ( couponRedeemInfo?.error_id ) { diff --git a/assets/source/catalog-sync/components/OnboardingModals/index.js b/assets/source/catalog-sync/components/OnboardingModals/index.js index 0553ef2c5..72a6b7f20 100644 --- a/assets/source/catalog-sync/components/OnboardingModals/index.js +++ b/assets/source/catalog-sync/components/OnboardingModals/index.js @@ -16,8 +16,8 @@ import OnboardingErrorModal from './OnboardingErrorModal'; */ const OnboardingModals = ( { onCloseModal } ) => { const adsCampaignIsActive = useSettingsSelect()?.ads_campaign_is_active; - const couponRedeemInfo = useSettingsSelect()?.account_data - ?.coupon_redeem_info; + const couponRedeemInfo = + useSettingsSelect()?.account_data?.coupon_redeem_info; // Generic modal when there is no campaign. if ( ! adsCampaignIsActive ) { diff --git a/assets/source/catalog-sync/sections/SyncState.js b/assets/source/catalog-sync/sections/SyncState.js index c5093ed01..b08f4481e 100644 --- a/assets/source/catalog-sync/sections/SyncState.js +++ b/assets/source/catalog-sync/sections/SyncState.js @@ -41,8 +41,9 @@ const SyncState = () => { select( REPORTS_STORE_NAME ).getFeedState() ); - const hasAvailableCredits = useSettingsSelect()?.account_data - ?.available_discounts?.marketing_offer?.remaining_discount; + const hasAvailableCredits = + useSettingsSelect()?.account_data?.available_discounts?.marketing_offer + ?.remaining_discount; const availableCredits = sprintf( /* translators: %s credits value with currency formatted using wc_price */ diff --git a/assets/source/components/prelaunch-notice/index.js b/assets/source/components/prelaunch-notice/index.js index d93beb9f8..0321b4bee 100644 --- a/assets/source/components/prelaunch-notice/index.js +++ b/assets/source/components/prelaunch-notice/index.js @@ -37,9 +37,8 @@ const PrelaunchNotice = () => {

{ recordEvent( 'pfw_account_disconnect_button_click', { context } ); diff --git a/assets/source/setup-guide/app/components/SyncSettings/index.js b/assets/source/setup-guide/app/components/SyncSettings/index.js index 16f939e01..d71a3c75b 100644 --- a/assets/source/setup-guide/app/components/SyncSettings/index.js +++ b/assets/source/setup-guide/app/components/SyncSettings/index.js @@ -30,9 +30,8 @@ const SyncSettings = () => { const syncAppSettings = useSyncSettingsDispatch(); const createNotice = useCreateNotice(); const { removeNotice } = useDispatch( 'core/notices' ); - const [ triggeredSyncSettings, setTriggeredSyncSettings ] = useState( - false - ); + const [ triggeredSyncSettings, setTriggeredSyncSettings ] = + useState( false ); const syncSettings = async () => { try { diff --git a/assets/source/setup-guide/app/components/UnsupportedCountryNotice/index.js b/assets/source/setup-guide/app/components/UnsupportedCountryNotice/index.js index 10db4e080..5c741ebbe 100644 --- a/assets/source/setup-guide/app/components/UnsupportedCountryNotice/index.js +++ b/assets/source/setup-guide/app/components/UnsupportedCountryNotice/index.js @@ -63,9 +63,8 @@ function UnsupportedCountryNotice( { countryCode } ) { supportedCountriesLink: ( { if ( reqError?.data?.pinterest_code === undefined ) { @@ -92,38 +88,43 @@ const StaticError = ( { reqError } ) => { * @fires wcadmin_pfw_documentation_link_click with `{ link_id: 'claim-website', context: props.view }` * @param {Object} props React props. * @param {'wizard'|'settings'} props.view Indicate which view this component is rendered on. - * @param {Function} [props.goToNextStep] * When the website claim is complete, called when clicking the "Continue" button. * The "Continue" button is only displayed when `props.view` is 'wizard'. * @return {JSX.Element} Rendered component. */ -const ClaimWebsite = ( { goToNextStep, view } ) => { +const ClaimWebsite = ( { view } ) => { const [ status, setStatus ] = useState( STATUS.IDLE ); const [ reqError, setReqError ] = useState(); const isDomainVerified = useSettingsSelect( 'isDomainVerified' ); - const setAppSettings = useSettingsDispatch( view === 'wizard' ); const createNotice = useCreateNotice(); const pfwSettings = wcSettings.pinterest_for_woocommerce; useEffect( () => { + // If domain is not verified and verification status is not pending, error nor success - start verification. + if ( + ! Object.values( { + ...LABEL_STATUS, + ERROR: STATUS.ERROR, + } ).includes( status ) && + ! isDomainVerified + ) { + handleClaimWebsite(); + } if ( status !== STATUS.PENDING && isDomainVerified ) { setStatus( STATUS.SUCCESS ); } - }, [ status, isDomainVerified ] ); + }, [ status, isDomainVerified ] ); // eslint-disable-line const handleClaimWebsite = async () => { setStatus( STATUS.PENDING ); setReqError(); try { - const results = await apiFetch( { + await apiFetch( { path: pfwSettings.apiRoute + '/domain_verification', method: 'POST', } ); - await setAppSettings( { account_data: results.account_data } ); - recordEvent( 'pfw_domain_verify_success' ); - setStatus( STATUS.SUCCESS ); } catch ( error ) { setStatus( STATUS.ERROR ); @@ -160,7 +161,12 @@ const ClaimWebsite = ( { goToNextStep, view } ) => { const text = buttonLabels[ status ]; - if ( Object.values( LABEL_STATUS ).includes( status ) ) { + if ( + Object.values( { + ...LABEL_STATUS, + IDLE: STATUS.IDLE, + } ).includes( status ) + ) { return ; } @@ -169,6 +175,57 @@ const ClaimWebsite = ( { goToNextStep, view } ) => { ); }; + const CompleteSetupButton = () => { + const buttonLabels = { + error: __( 'Try Again', 'pinterest-for-woocommerce' ), + success: __( 'Complete Setup', 'pinterest-for-woocommerce' ), + }; + + return ( + + ); + }; + + const handleCompleteSetup = async () => { + try { + createNotice( + 'success', + __( 'Connected successfully.', 'pinterest-for-woocommerce' ) + ); + + recordEvent( 'pfw_setup', { + target: 'complete', + trigger: 'setup-complete', + } ); + + // Force reload WC admin page to initiate the relevant dependencies of the Dashboard page. + const path = getNewPath( {}, '/pinterest/settings', {} ); + + window.location = new URL( wcSettings.adminUrl + path ); + } catch ( error ) { + createNotice( + 'error', + __( + 'There was a problem connecting the advertiser.', + 'pinterest-for-woocommerce' + ) + ); + } + }; + return (

{ view === 'wizard' && ( @@ -208,49 +265,35 @@ const ClaimWebsite = ( { goToNextStep, view } ) => {
- { undefined !== isDomainVerified ? ( - - - { __( - 'Verify your domain to claim your website', - 'pinterest-for-woocommerce' - ) } - - - { __( - 'This will allow access to analytics for the Pins you publish from your site, the analytics on Pins that other people create from your site, and let people know where they can find more of your content.', - 'pinterest-for-woocommerce' - ) } - + + + { __( + 'Verify your domain to claim your website', + 'pinterest-for-woocommerce' + ) } + + + { __( + 'This will allow access to analytics for the Pins you publish from your site, the analytics on Pins that other people create from your site, and let people know where they can find more of your content.', + 'pinterest-for-woocommerce' + ) } + - - - - + + + + - - - ) : ( - - - - ) } + + { view === 'wizard' && (
-
) }
diff --git a/assets/source/setup-guide/app/steps/ClaimWebsite.test.js b/assets/source/setup-guide/app/steps/ClaimWebsite.test.js index b2c79c433..63bb96da0 100644 --- a/assets/source/setup-guide/app/steps/ClaimWebsite.test.js +++ b/assets/source/setup-guide/app/steps/ClaimWebsite.test.js @@ -20,7 +20,7 @@ jest.mock( '@wordpress/api-fetch', () => { */ import { recordEvent } from '@woocommerce/tracks'; import apiFetch from '@wordpress/api-fetch'; -import { fireEvent, render, waitFor } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; /** * Internal dependencies @@ -37,11 +37,8 @@ describe( 'Claim Website Record Events', () => { throw 'Ups'; } ); - const { getByText } = render( - {} } view="wizard" /> - ); + render( {} } view="wizard" /> ); - fireEvent.click( getByText( 'Start verification' ) ); expect( recordEvent ).toHaveBeenCalledWith( 'pfw_domain_verify_failure', expect.any( Object ) @@ -53,11 +50,7 @@ describe( 'Claim Website Record Events', () => { return { account_data: { id: 'foo' } }; } ); - const { getByText } = render( - {} } view="wizard" /> - ); - - fireEvent.click( getByText( 'Start verification' ) ); + render( {} } view="wizard" /> ); // Wait for async click handler and apiFetch resolution. await waitFor( () => diff --git a/assets/source/setup-guide/app/steps/SetupAccount.js b/assets/source/setup-guide/app/steps/SetupAccount.js index 1f0d4789d..09b56275f 100644 --- a/assets/source/setup-guide/app/steps/SetupAccount.js +++ b/assets/source/setup-guide/app/steps/SetupAccount.js @@ -139,11 +139,9 @@ const SetupAccount = ( { // eslint-disable-next-line jsx-a11y/anchor-has-content
{ className="pinterest-tooltip-link" { ...documentationLinkProps( { - href: - wcSettings - .pinterest_for_woocommerce - .pinterestLinks - .enhancedMatch, - linkId: - 'enhanced-match', + href: wcSettings + .pinterest_for_woocommerce + .pinterestLinks + .enhancedMatch, + linkId: 'enhanced-match', context: 'settings', } @@ -160,7 +158,8 @@ const SetupPins = ( {} ) => { className={ classnames( 'woocommerce-setup-guide__checkbox-group', { - 'pinterest-for-woocommerce-settings-checkbox-disabled': ! appSettings.track_conversions, + 'pinterest-for-woocommerce-settings-checkbox-disabled': + ! appSettings.track_conversions, } ) } disabled={ @@ -212,13 +211,11 @@ const SetupPins = ( {} ) => { { @@ -515,8 +510,7 @@ const SetupTracking = ( { view = 'settings' } ) => { item.name, item.id ), - value: - item.id, + value: item.id, } ) ) } help={ __( @@ -554,14 +548,13 @@ const SetupTracking = ( { view = 'settings' } ) => { isLink { ...documentationLinkProps( { - href: - wcSettings - .pinterest_for_woocommerce - .countryTos - .terms_url, - linkId: - 'ad-terms-of-service', - context: view, + href: wcSettings + .pinterest_for_woocommerce + .countryTos + .terms_url, + linkId: 'ad-terms-of-service', + context: + view, } ) } > diff --git a/assets/source/setup-guide/app/steps/components/AdsCreditsPromo.js b/assets/source/setup-guide/app/steps/components/AdsCreditsPromo.js index 532976982..4037ce687 100644 --- a/assets/source/setup-guide/app/steps/components/AdsCreditsPromo.js +++ b/assets/source/setup-guide/app/steps/components/AdsCreditsPromo.js @@ -21,10 +21,8 @@ import GiftIcon from '../../components/GiftIcon'; const AdsCreditsPromo = () => { const appSettings = useSettingsSelect(); - const [ - isTermsAndConditionsModalOpen, - setIsTermsAndConditionsModalOpen, - ] = useState( false ); + const [ isTermsAndConditionsModalOpen, setIsTermsAndConditionsModalOpen ] = + useState( false ); const openTermsAndConditionsModal = () => { setIsTermsAndConditionsModalOpen( true ); diff --git a/assets/source/setup-guide/app/views/LandingPageApp.js b/assets/source/setup-guide/app/views/LandingPageApp.js index 848fe5cab..d3e88749c 100644 --- a/assets/source/setup-guide/app/views/LandingPageApp.js +++ b/assets/source/setup-guide/app/views/LandingPageApp.js @@ -138,10 +138,8 @@ const WelcomeSection = () => { * @return {JSX.Element} Rendered element. */ const AdsCreditSection = () => { - const [ - isTermsAndConditionsModalOpen, - setIsTermsAndConditionsModalOpen, - ] = useState( false ); + const [ isTermsAndConditionsModalOpen, setIsTermsAndConditionsModalOpen ] = + useState( false ); const openTermsAndConditionsModal = () => { setIsTermsAndConditionsModalOpen( true ); @@ -357,11 +355,8 @@ const FaqQuestion = ( { questionId, question, answer } ) => { }; const LandingPageApp = () => { - const { - pluginVersion, - isAdsSupportedCountry, - storeCountry, - } = wcSettings.pinterest_for_woocommerce; + const { pluginVersion, isAdsSupportedCountry, storeCountry } = + wcSettings.pinterest_for_woocommerce; const adsCampaignIsActive = useSettingsSelect()?.ads_campaign_is_active; diff --git a/assets/source/setup-guide/app/views/WizardApp.js b/assets/source/setup-guide/app/views/WizardApp.js index 24a435d5d..2c704066b 100644 --- a/assets/source/setup-guide/app/views/WizardApp.js +++ b/assets/source/setup-guide/app/views/WizardApp.js @@ -13,7 +13,6 @@ import { updateQueryString } from '@woocommerce/navigation'; */ import SetupAccount from '../steps/SetupAccount'; import ClaimWebsite from '../steps/ClaimWebsite'; -import SetupTracking from '../steps/SetupTracking'; import OnboardingTopBar from '../components/TopBar'; import TransientNotices from '../components/TransientNotices'; import { @@ -28,9 +27,8 @@ import { * @param {Object} props React props. * @param {Object} props.query The current query string, parsed into an object, from the page URL. * - * @fires wcadmin_pfw_setup with `{ target: 'setup-account' | 'claim-website' | 'setup-tracking', trigger: 'wizard-stepper' }` when wizard's header step is clicked. + * @fires wcadmin_pfw_setup with `{ target: 'setup-account' | 'claim-website', trigger: 'wizard-stepper' }` when wizard's header step is clicked. * @fires wcadmin_pfw_setup with `{ target: 'claim-website' , trigger: 'setup-account-continue' }` when continue button is clicked. - * @fires wcadmin_pfw_setup with `{ target: 'setup-tracking', trigger: 'claim-website-continue' }` when continue button is clicked. * * @return {JSX.Element} Rendered element. */ @@ -44,7 +42,6 @@ const WizardApp = ( { query } ) => { ); const appSettings = useSettingsSelect(); - const isDomainVerified = useSettingsSelect( 'isDomainVerified' ); const createNotice = useCreateNotice(); useEffect( () => { @@ -78,15 +75,6 @@ const WizardApp = ( { query } ) => { label: __( 'Claim your website', 'pinterest-for-woocommerce' ), isClickable: isBusinessConnected, }, - { - key: 'setup-tracking', - container: SetupTracking, - label: __( - 'Track conversions with the Pinterest tag', - 'pinterest-for-woocommerce' - ), - isClickable: isBusinessConnected && isDomainVerified, - }, ]; const getSteps = () => { diff --git a/assets/source/setup-guide/app/views/WizardApp.test.js b/assets/source/setup-guide/app/views/WizardApp.test.js index 3176cd366..d5e06d306 100644 --- a/assets/source/setup-guide/app/views/WizardApp.test.js +++ b/assets/source/setup-guide/app/views/WizardApp.test.js @@ -31,7 +31,6 @@ jest.mock( '../steps/SetupTracking', () => () => null ); const stepOne = /Set up your business account/; const stepTwo = /Claim your website/; -const stepThree = /Track conversions/; describe( 'WizardApp component', () => { describe( 'First rendering', () => { @@ -44,7 +43,6 @@ describe( 'WizardApp component', () => { test( 'should show all options and first step should be clickable', () => { expect( rendered.getByText( stepOne ) ).toBeInTheDocument(); expect( rendered.getByText( stepTwo ) ).toBeInTheDocument(); - expect( rendered.getByText( stepThree ) ).toBeInTheDocument(); expect( rendered.queryAllByRole( 'button' ).length ).toBe( 1 ); } ); @@ -83,7 +81,7 @@ describe( 'WizardApp component', () => { } ); test( 'should 3 steps button be clickable in the stepper', () => { - expect( rendered.queryAllByRole( 'button' ).length ).toBe( 3 ); + expect( rendered.queryAllByRole( 'button' ).length ).toBe( 2 ); const setUpButton = rendered.getByRole( 'button', { name: stepOne, @@ -127,7 +125,7 @@ describe( 'WizardApp component', () => { } ); test( 'should all three steps be clickable buttons', () => { - expect( rendered.queryAllByRole( 'button' ).length ).toBe( 3 ); + expect( rendered.queryAllByRole( 'button' ).length ).toBe( 2 ); expect( rendered.getByRole( 'button', { @@ -141,12 +139,6 @@ describe( 'WizardApp component', () => { exact: false, } ) ).toBeInTheDocument(); - expect( - rendered.getByRole( 'button', { - name: stepThree, - exact: false, - } ) - ).toBeInTheDocument(); } ); test( 'should event tracking be = claim-website after click', () => { @@ -168,18 +160,5 @@ describe( 'WizardApp component', () => { 'claim-website' ); } ); - - test( 'should event tracking be = setup-tracking after click', () => { - fireEvent.click( - rendered.getByRole( 'button', { - name: stepThree, - } ) - ); - - expect( recordEvent ).toHaveBeenCalledWith( 'pfw_setup', { - target: 'setup-tracking', - trigger: 'wizard-stepper', - } ); - } ); } ); } ); diff --git a/class-pinterest-for-woocommerce.php b/class-pinterest-for-woocommerce.php index 037b1f081..382233aaa 100644 --- a/class-pinterest-for-woocommerce.php +++ b/class-pinterest-for-woocommerce.php @@ -190,7 +190,7 @@ private function define_constants() { define( 'PINTEREST_FOR_WOOCOMMERCE_OPTION_NAME', 'pinterest_for_woocommerce' ); define( 'PINTEREST_FOR_WOOCOMMERCE_DATA_NAME', 'pinterest_for_woocommerce_data' ); define( 'PINTEREST_FOR_WOOCOMMERCE_LOG_PREFIX', 'pinterest-for-woocommerce' ); - define( 'PINTEREST_FOR_WOOCOMMERCE_WOO_CONNECT_URL', 'https://connect.woocommerce.local/' ); + define( 'PINTEREST_FOR_WOOCOMMERCE_WOO_CONNECT_URL', 'https://connect.woocommerce.com/' ); define( 'PINTEREST_FOR_WOOCOMMERCE_WOO_CONNECT_SERVICE', 'pinterestv5' ); define( 'PINTEREST_FOR_WOOCOMMERCE_API_NAMESPACE', 'pinterest' ); define( 'PINTEREST_FOR_WOOCOMMERCE_CONNECT_NONCE', 'wp_rest' ); @@ -274,13 +274,15 @@ public function init_plugin() { add_action( 'admin_init', array( Pinterest\AdCredits::class, 'check_if_ads_campaign_is_active' ) ); add_action( 'pinterest_for_woocommerce_token_saved', array( $this, 'set_default_settings' ) ); + add_action( 'pinterest_for_woocommerce_token_saved', array( $this, 'create_commerce_integration' ) ); add_action( 'pinterest_for_woocommerce_token_saved', array( $this, 'update_account_data' ) ); + add_action( 'pinterest_for_woocommerce_token_saved', array( $this, 'update_linked_businesses' ) ); // Handle the Pinterest verification URL. add_action( 'parse_request', array( $this, 'verification_request' ) ); // Disconnect advertiser if advertiser or tag change. - add_action( 'update_option_pinterest_for_woocommerce', array( $this, 'maybe_disconnect_advertiser' ), 10, 2 ); + // add_action( 'update_option_pinterest_for_woocommerce', array( $this, 'maybe_disconnect_advertiser' ), 10, 2 );. // Init marketing notifications. add_action( Heartbeat::DAILY, array( $this, 'init_marketing_notifications' ) ); @@ -586,8 +588,8 @@ public function init_api_endpoints() { new Pinterest\API\FeedState(); new Pinterest\API\FeedIssues(); new Pinterest\API\Tags(); - new Pinterest\API\HealthCheck(); - new Pinterest\API\Options(); + new Pinterest\API\Health(); + new Pinterest\API\Settings(); new Pinterest\API\SyncSettings(); new Pinterest\API\UserInteraction(); } @@ -648,83 +650,49 @@ public static function save_token_data( $token ) { * @since x.x.x * * @param array $connection_info_data The array containing the connection info data. - * @return array + * @return bool True if the data was saved successfully. */ - public static function save_connection_info_data( $connection_info_data ) { + public static function save_connection_info_data( array $connection_info_data ): bool { return self::save_data( 'connection_info_data', $connection_info_data ); } + /** + * Saves the integration data. + * + * @param array $integration_data The array containing the integration data. + * @return bool True if the data was saved successfully. + */ + public static function save_integration_data( array $integration_data ): bool { + return self::save_data( 'integration_data', $integration_data ); + } /** * Disconnect by clearing the Token and any other data that we should gather from scratch. * * @since 1.0.0 * - * @return boolean True if disconnection was successful. + * @return bool True if disconnection was successful. * - * @throws \Exception PHP Exception. + * @throws Exception PHP Exception. */ - public static function disconnect() { + public static function disconnect(): bool { /* * If there is no business connected, disconnecting merchant will throw error. * Just need to clean account data in these cases. */ if ( ! self::is_business_connected() ) { - self::flush_options(); - // At this point we're disconnected. return true; } try { // Disconnect merchant from Pinterest. - $result = Pinterest\API\Base::disconnect_merchant(); - - if ( 'success' !== $result['status'] ) { - throw new \Exception( esc_html__( 'Response error on disconnect merchant.', 'pinterest-for-woocommerce' ), 400 ); - } - - // Disconnect the advertiser from Pinterest. - $connected_advertiser = self::get_setting( 'tracking_advertiser', null ); - $connected_tag = self::get_setting( 'tracking_tag', null ); - - if ( $connected_advertiser && $connected_tag ) { - - try { - - Pinterest\API\AdvertiserConnect::disconnect_advertiser( $connected_advertiser, $connected_tag ); - - } catch ( \Exception $th ) { - - Pinterest\Logger::log( esc_html__( 'There was an error disconnecting the Advertiser.', 'pinterest-for-woocommerce' ) ); - self::flush_options(); - throw new \Exception( esc_html__( 'There was an error disconnecting the Advertiser. Please try again.', 'pinterest-for-woocommerce' ), 400 ); - } - } - + self::delete_commerce_integration(); self::flush_options(); - // At this point we're disconnected. return true; - } catch ( PinterestApiException $e ) { - $code = $e->get_pinterest_code(); - - if ( PinterestApiException::MERCHANT_NOT_FOUND === $code ) { - Pinterest\Logger::log( esc_html__( 'Trying to disconnect while the merchant (id) was not found.', 'pinterest-for-woocommerce' ) ); - - /* - * This is an abnormal state of the application. Caused probably by issues during the connection process. - * It looks like the best course of actions is to flush the options and assume that we are disconnected. - * This way we restore UI connect functionality and allow merchant to retry. - */ - self::flush_options(); - return true; - } - - return false; - - } catch ( \Exception $th ) { + } catch ( Exception $th ) { // There was an error disconnecting merchant. return false; } @@ -840,6 +808,8 @@ public static function get_middleware_url( $context = 'login', $args = array() ) $state = http_build_query( $state_params ); + // phpcs:ignore Squiz.Commenting.InlineComment.InvalidEndChar + // nosemgrep: audit.php.wp.security.xss.query-arg return self::get_connection_proxy_url() . 'connect/' . PINTEREST_FOR_WOOCOMMERCE_WOO_CONNECT_SERVICE . '?' . $state; } @@ -858,6 +828,158 @@ public function maybe_inject_verification_code() { } } + /** + * Connects WC to Pinterest. + * + * @since x.x.x + * + * @return array { + * Integration data returned by Pinterest. + * + * @type string $id ID of the integration (string all digits). + * @type string $external_business_id External business ID for the integration. + * @type string $connected_merchant_id Connected merchant ID for the integration. + * @type string $connected_user_id Connected user ID for the integration. + * @type string $connected_advertiser_id Connected advertiser ID for the integration. + * @type string $connected_lba_id Connected LBA ID for the integration. + * @type string $connected_tag_id Connected tag ID for the integration. + * @type int $partner_access_token_expiry Partner access token expiry for the integration. + * @type int $partner_refresh_token_expiry Partner refresh token expiry for the integration. + * @type string $scopes Scopes for the integration. + * @type int $created_timestamp Created timestamp for the integration. + * @type int $updated_timestamp Updated timestamp for the integration. + * @type string $additional_id_1 Additional ID 1 for the integration. + * @type string $partner_metadata Partner metadata for the integration. + * } + * @throws PinterestApiException In case of 404, 409 and 500 errors from Pinterest. + */ + public static function create_commerce_integration(): array { + $external_business_id = self::generate_external_business_id(); + $connection_data = self::get_data( 'connection_info_data', true ); + + $response = Pinterest\API\APIV5::make_request( + 'integrations/commerce', + 'POST', + array( + 'external_business_id' => $external_business_id, + 'connected_merchant_id' => $connection_data['merchant_id'] ?? '', + 'connected_advertiser_id' => $connection_data['advertiser_id'] ?? '', + 'connected_tag_id' => $connection_data['tag_id'] ?? '', + ) + ); + + /* + * In case of successful response we save our integration data into a database. + * Data we save includes but not limited to: + * external business id, + * id, + * connected_user_id, + * etc. + */ + self::save_integration_data( $response ); + + self::save_setting( 'tracking_advertiser', $response['connected_advertiser_id'] ); + self::save_setting( 'tracking_tag', $response['connected_tag_id'] ); + + return $response; + } + + /** + * Updates WC integration parameters with Pinterest. + * + * @since x.x.x + * + * @param string $external_business_id External business ID for the integration. + * @param array $data { + * Integration data to update with Pinterest. + * + * @type string $external_business_id External business ID for the integration. + * @type string $connected_merchant_id Connected merchant ID for the integration. + * @type string $connected_advertiser_id Connected advertiser ID for the integration. + * @type string $connected_lba_id Connected LBA ID for the integration. + * @type string $connected_tag_id Connected tag ID for the integration. + * @type string $partner_access_token Partner access token for the integration. + * @type string $partner_refresh_token Partner refresh token for the integration. + * @type string $partner_primary_email Partner primary email for the integration. + * @type int $partner_access_token_expiry Partner access token expiry for the integration. + * @type int $partner_refresh_token_expiry Partner refresh token expiry for the integration. + * @type string $scopes Scopes for the integration. + * @type string $additional_id_1 Additional ID 1 for the integration. + * @type string $partner_metadata Partner metadata for the integration. + * } + * + * @return array { + * Integration data returned by Pinterest. + * + * @type string $id ID of the integration (string all digits). + * @type string $external_business_id External business ID for the integration. + * @type string $connected_merchant_id Connected merchant ID for the integration. + * @type string $connected_user_id Connected user ID for the integration. + * @type string $connected_advertiser_id Connected advertiser ID for the integration. + * @type string $connected_lba_id Connected LBA ID for the integration. + * @type string $connected_tag_id Connected tag ID for the integration. + * @type int $partner_access_token_expiry Partner access token expiry for the integration. + * @type int $partner_refresh_token_expiry Partner refresh token expiry for the integration. + * @type string $scopes Scopes for the integration. + * @type int $created_timestamp Created timestamp for the integration. + * @type int $updated_timestamp Updated timestamp for the integration. + * @type string $additional_id_1 Additional ID 1 for the integration. + * @type string $partner_metadata Partner metadata for the integration. + * } + * @throws PinterestApiException In case of 404, 409 and 500 errors from Pinterest. + */ + public static function update_commerce_integration( string $external_business_id, array $data ): array { + return Pinterest\API\APIV5::make_request( + "integrations/commerce/{$external_business_id}", + 'PATCH', + $data + ); + } + + /** + * Disconnects WC from Pinterest. + * + * @since x.x.x + * + * @return bool + * @throws PinterestApiException In case of 500 unexpected error from Pinterest. + */ + public static function delete_commerce_integration(): bool { + $external_business_id = self::get_data( 'integration_data' )['external_business_id']; + + Pinterest\API\APIV5::make_request( + "integrations/commerce/{$external_business_id}", + 'DELETE' + ); + + return true; + } + + /** + * Used to generate external business id to pass it Pinterest when creating a connection between WC and Pinterest. + * + * @since x.x.x + * + * @return string + */ + public static function generate_external_business_id(): string { + /** + * Filters the shop's external business id. + * + * This is passed to Pinterest when connecting. + * Should be non-empty and without special characters, + * otherwise the ID will be obtained from the site URL as fallback. + * + * @since x.x.x + * + * @param string $id the shop's external business id. + */ + $id = sanitize_key( (string) apply_filters( 'wc_pinterest_external_business_id', get_bloginfo( 'name' ) ) ); + if ( empty( $id ) ) { + $id = sanitize_key( str_replace( array( 'http', 'https', 'www' ), '', get_bloginfo( 'url' ) ) ); + } + return uniqid( sprintf( '%s-', $id ), false ); + } /** * Fetches the account_data parameters from Pinterest's API @@ -870,49 +992,64 @@ public function maybe_inject_verification_code() { * @throws Exception PHP Exception. */ public static function update_account_data() { - try { + $integration_data = self::get_data( 'integration_data' ); + $account_data = Pinterest\API\APIV5::get_account_info(); + + $data = array( + 'username' => $account_data['username'] ?? '', + 'full_name' => '', + 'id' => $integration_data['id'] ?? '', + 'image_medium_url' => $account_data['profile_image'] ?? '', + // Partner is a user who is a business account not a pinner ('BUSINESS', 'PINNER' account types). + 'is_partner' => 'BUSINESS' === ( $account_data['account_type'] ?? '' ), + ); - $account_data = Pinterest\API\Base::get_account_info(); - - if ( 'success' === $account_data['status'] ) { - - $data = array_intersect_key( - (array) $account_data['data'], - array( - 'verified_user_websites' => '', - 'is_any_website_verified' => '', - 'username' => '', - 'full_name' => '', - 'id' => '', - 'image_medium_url' => '', - 'is_partner' => '', - ) - ); - - /* - * For now we assume that the billing is not setup and credits are not redeemed. - * We will be able to check that only when the advertiser will be connected. - * The billing is tied to advertiser. - */ - $data['is_billing_setup'] = false; - $data['coupon_redeem_info'] = array( 'redeem_status' => false ); + $verified_websites = array_reduce( + Pinterest\API\APIV5::get_user_websites()['items'] ?? array(), + function( $carry, $item ) { + if ( 'verified' === $item['status'] ) { + $carry[] = $item['website']; + } + return $carry; + }, + array() + ); - Pinterest_For_Woocommerce()::save_setting( 'account_data', $data ); - return $data; - } + $data += array( + // Array of verified website domain names. + 'verified_user_websites' => $verified_websites, + // Indicates if any of the verified websites is verified true or false. + 'is_any_website_verified' => 0 < count( $verified_websites ), + ); - self::get_linked_businesses( true ); + /* + * For now we assume that the billing is not setup and credits are not redeemed. + * We will be able to check that only when the advertiser will be connected. + * The billing is tied to advertiser. + */ + $data += array( + 'is_billing_setup' => false, + 'coupon_redeem_info' => array( 'redeem_status' => false ), + ); + Pinterest_For_Woocommerce()::save_setting( 'account_data', $data ); + return $data; } catch ( Throwable $th ) { - self::disconnect(); - throw new Exception( esc_html__( 'There was an error getting the account data.', 'pinterest-for-woocommerce' ) ); } + } - return array(); - + /** + * Updates linked businesses. + * + * @since x.x.x + * + * @return void + */ + public static function update_linked_businesses() { + self::get_linked_businesses( true ); } /** @@ -1056,19 +1193,29 @@ public static function check_if_coupon_was_redeemed() { /** * Fetches a fresh copy (if needed or explicitly requested), of the authenticated user's linked business accounts. * - * @param boolean $force_refresh Wether to refresh the data from the API. + * @param bool $force_refresh Whether to refresh the data from the API. * * @return array */ - public static function get_linked_businesses( $force_refresh = false ) { - + public static function get_linked_businesses( bool $force_refresh = false ): array { $linked_businesses = ! $force_refresh ? Pinterest_For_Woocommerce()::get_data( 'linked_businesses' ) : null; - if ( null === $linked_businesses ) { - $linked_businesses = self::update_linked_businesses(); + $account_data = Pinterest_For_Woocommerce()::get_setting( 'account_data' ); + $fetch_linked_businesses = ! empty( $account_data ) && array_key_exists( 'is_partner', $account_data ) && ! $account_data['is_partner']; + + $fetched_businesses = $fetch_linked_businesses ? Pinterest\API\APIV5::get_linked_businesses() : array(); + + if ( ! empty( $fetched_businesses ) && 'success' === $fetched_businesses['status'] ) { + $linked_businesses = $fetched_businesses['data']; + } + + $linked_businesses = $linked_businesses ?? array(); + + self::save_data( 'linked_businesses', $linked_businesses ); } - $linked_businesses = array_map( + /* + * $linked_businesses = array_map( function ( $business ) { return array( 'value' => $business->id, @@ -1077,33 +1224,7 @@ function ( $business ) { }, $linked_businesses ); - - return $linked_businesses; - } - - - /** - * Grabs a fresh copy of businesses from the API saves & returns them. - * - * @return array - */ - public static function update_linked_businesses() { - - $account_data = Pinterest_For_Woocommerce()::get_setting( 'account_data' ); - $fetch_linked_businesses = - ! empty( $account_data ) && - array_key_exists( 'is_partner', $account_data ) && - ! $account_data['is_partner']; - - $fetched_businesses = $fetch_linked_businesses ? Pinterest\API\Base::get_linked_businesses() : array(); - - if ( ! empty( $fetched_businesses ) && 'success' === $fetched_businesses['status'] ) { - $linked_businesses = $fetched_businesses['data']; - } - - $linked_businesses = $linked_businesses ?? array(); - - self::save_data( 'linked_businesses', $linked_businesses ); + */ return $linked_businesses; } @@ -1115,7 +1236,7 @@ public static function update_linked_businesses() { */ public static function get_account_id() { $account_data = Pinterest_For_Woocommerce()::get_setting( 'account_data' ); - return isset( $account_data['id'] ) ? $account_data['id'] : false; + return $account_data['id'] ?? false; } /** @@ -1213,14 +1334,15 @@ public static function is_business_connected() { /** - * Checks whether we have verified our domain, by checking account_data as + * Checks whether we have verified our current domain, by checking account_data as * returned by Pinterest. * - * @return boolean + * @return bool */ - public static function is_domain_verified() { + public static function is_domain_verified(): bool { $account_data = self::get_setting( 'account_data' ); - return isset( $account_data['is_any_website_verified'] ) ? (bool) $account_data['is_any_website_verified'] : false; + $verified_domains = $account_data['verified_user_websites'] ?? array(); + return in_array( wp_parse_url( get_home_url() )['host'] ?? '', $verified_domains ); } /** diff --git a/i18n/languages/pinterest-for-woocommerce.pot b/i18n/languages/pinterest-for-woocommerce.pot index f96d10ce4..07aab0238 100644 --- a/i18n/languages/pinterest-for-woocommerce.pot +++ b/i18n/languages/pinterest-for-woocommerce.pot @@ -1,15 +1,15 @@ -# Copyright (C) 2022 WooCommerce +# Copyright (C) 2023 WooCommerce # This file is distributed under the GPL-2.0+. msgid "" msgstr "" -"Project-Id-Version: Pinterest for WooCommerce 1.2.2\n" +"Project-Id-Version: Pinterest for WooCommerce 1.2.19\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/pinterest-for-woocommerce\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2022-10-11T13:35:03+00:00\n" +"POT-Creation-Date: 2023-06-14T19:14:21+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.6.0\n" "X-Domain: pinterest-for-woocommerce\n" @@ -34,119 +34,133 @@ msgstr "" msgid "https://woocommerce.com" msgstr "" -#: class-pinterest-for-woocommerce.php:143 +#: class-pinterest-for-woocommerce.php:153 msgid "Cloning this class is forbidden." msgstr "" -#: class-pinterest-for-woocommerce.php:152 +#: class-pinterest-for-woocommerce.php:162 msgid "Unserializing instances of this class is forbidden." msgstr "" -#: class-pinterest-for-woocommerce.php:160 +#: class-pinterest-for-woocommerce.php:170 msgid "Only a single instance of this class is allowed. Use singleton." msgstr "" #. Translators: The minimum PHP version -#: class-pinterest-for-woocommerce.php:326 +#: class-pinterest-for-woocommerce.php:351 msgid "Pinterest for WooCommerce requires a minimum PHP version of %s." msgstr "" #. Translators: The minimum WP version -#: class-pinterest-for-woocommerce.php:331 +#: class-pinterest-for-woocommerce.php:356 msgid "Pinterest for WooCommerce requires a minimum WordPress version of %s." msgstr "" #. Translators: The minimum WC version -#: class-pinterest-for-woocommerce.php:336 +#: class-pinterest-for-woocommerce.php:361 msgid "Pinterest for WooCommerce requires a minimum WooCommerce version of %s." msgstr "" -#: class-pinterest-for-woocommerce.php:340 +#: class-pinterest-for-woocommerce.php:365 msgid "Pinterest for WooCommerce requires WooCommerce Admin to be enabled." msgstr "" #. Translators: The minimum Action Scheduler version -#: class-pinterest-for-woocommerce.php:345 +#: class-pinterest-for-woocommerce.php:370 msgid "Pinterest for WooCommerce requires a minimum Action Scheduler package of %s. It can be caused by old version of the WooCommerce extensions." msgstr "" #. Translators: The error description -#: class-pinterest-for-woocommerce.php:595 +#: class-pinterest-for-woocommerce.php:623 msgid "Could not decrypt the Pinterest API access token. Try reconnecting to Pinterest. [%s]" msgstr "" -#: class-pinterest-for-woocommerce.php:646 -msgid "Response error on disconnect merchant." -msgstr "" - -#: class-pinterest-for-woocommerce.php:661 -msgid "There was an error disconnecting the Advertiser." -msgstr "" - -#: class-pinterest-for-woocommerce.php:663 -#: class-pinterest-for-woocommerce.php:745 +#: class-pinterest-for-woocommerce.php:753 msgid "There was an error disconnecting the Advertiser. Please try again." msgstr "" -#: class-pinterest-for-woocommerce.php:675 -msgid "Trying to disconnect while the merchant (id) was not found." +#: class-pinterest-for-woocommerce.php:1040 +msgid "There was an error getting the account data." msgstr "" -#: class-pinterest-for-woocommerce.php:963 +#: class-pinterest-for-woocommerce.php:1280 msgid "Pinterest for WooCommerce verification page" msgstr "" -#: includes/admin/class-pinterest-for-woocommerce-admin.php:63 -#: includes/admin/class-pinterest-for-woocommerce-admin.php:67 -#: includes/admin/class-pinterest-for-woocommerce-admin.php:94 -#: includes/admin/class-pinterest-for-woocommerce-admin.php:98 -#: includes/admin/class-pinterest-for-woocommerce-admin.php:216 -#: includes/admin/class-pinterest-for-woocommerce-admin.php:223 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:64 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:68 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:95 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:99 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:217 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:224 #: views/attributes/variations-form.php:32 #: assets/source/setup-guide/index.js:52 msgid "Pinterest" msgstr "" -#: includes/admin/class-pinterest-for-woocommerce-admin.php:78 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:79 #: assets/source/components/navigation-classic/main-tab-nav.js:16 msgid "Catalog" msgstr "" -#: includes/admin/class-pinterest-for-woocommerce-admin.php:109 -#: includes/admin/class-pinterest-for-woocommerce-admin.php:157 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:110 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:158 msgid "Setup Pinterest" msgstr "" -#: includes/admin/class-pinterest-for-woocommerce-admin.php:126 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:127 #: assets/source/components/navigation-classic/main-tab-nav.js:26 #: assets/source/setup-guide/index.js:82 msgid "Connection" msgstr "" -#: includes/admin/class-pinterest-for-woocommerce-admin.php:139 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:140 #: assets/source/components/navigation-classic/main-tab-nav.js:21 #: assets/source/setup-guide/index.js:95 msgid "Settings" msgstr "" -#: includes/admin/class-pinterest-for-woocommerce-admin.php:169 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:170 msgid "Landing page" msgstr "" -#: includes/admin/class-pinterest-for-woocommerce-admin.php:492 +#: includes/admin/class-pinterest-for-woocommerce-admin.php:488 msgid "Cheatin' huh?" msgstr "" #. translators: 1: composer command. 2: plugin directory -#: pinterest-for-woocommerce.php:67 -#: pinterest-for-woocommerce.php:85 +#: pinterest-for-woocommerce.php:79 +#: pinterest-for-woocommerce.php:97 msgid "Your installation of the Pinterest for WooCommerce plugin is incomplete. Please run %1$s within the %2$s directory." msgstr "" +#: src/AdCredits.php:98 +#: src/AdCredits.php:237 +#: src/Billing.php:134 +msgid "Advertiser connected but the connection id is missing." +msgstr "" + +#: src/AdCredits.php:112 +msgid "There is no available data for the requested offer code." +msgstr "" + +#. translators: API error message +#: src/AdCredits.php:200 +msgid "Could not fetch ads campaign status due to: %s" +msgstr "" + #: src/Admin/Product/Attributes/AttributesForm.php:128 msgid "Default" msgstr "" +#: src/Admin/Product/Attributes/AttributesTab.php:188 +msgid "Simple product" +msgstr "" + +#: src/Admin/Product/Attributes/AttributesTab.php:189 +msgid "Variable product" +msgstr "" + #: src/Admin/Product/Attributes/Input/ConditionInput.php:27 msgid "Condition" msgstr "" @@ -163,52 +177,51 @@ msgstr "" msgid "Categorization of the product based on the standardized Google Product Taxonomy." msgstr "" -#: src/API/AdvertiserConnect.php:55 -msgid "Missing advertiser or tag parameters." +#: src/Admin/Tasks/Onboarding.php:35 +msgid "Get your products in front of engaged shoppers with Pinterest for WooCommerce" msgstr "" -#. Translators: The error description as returned from the API -#: src/API/AdvertiserConnect.php:74 -msgid "Could not connect advertiser with Pinterest. [%s]" +#: src/Admin/Tasks/Onboarding.php:53 +msgid "20 minutes" msgstr "" -#: src/API/AdvertiserConnect.php:94 -msgid "The advertiser could not be connected to Pinterest." +#: src/API/AdvertiserConnect.php:61 +msgid "Missing advertiser or tag parameters." msgstr "" -#: src/API/AdvertiserConnect.php:98 -msgid "Incorrect advertiser ID." +#. Translators: The error description as returned from the API +#: src/API/AdvertiserConnect.php:88 +msgid "Could not connect advertiser with Pinterest. [%s]" msgstr "" -#: src/API/AdvertiserConnect.php:131 -#: src/API/AdvertiserConnect.php:137 +#: src/API/AdvertiserConnect.php:171 +#: src/API/AdvertiserConnect.php:185 msgid "The advertiser could not be disconnected from Pinterest." msgstr "" -#: src/API/Advertisers.php:55 -msgid "No advertiser exists." +#. Translators: The error description as returned from the API +#: src/API/Advertisers.php:75 +msgid "Could not fetch advertisers for Pinterest account ID. [%s]" msgstr "" -#: src/API/Advertisers.php:59 -#: src/API/Tags.php:58 -msgid "Response error" +#: src/API/Auth.php:72 +msgid "Something went wrong with your attempt to authorize this App. Please try again." msgstr "" -#. Translators: The error description as returned from the API -#: src/API/Advertisers.php:72 -msgid "Could not fetch advertisers for Pinterest account ID. [%s]" +#: src/API/Auth.php:99 +msgid "Token data missing, please try again later." msgstr "" -#: src/API/Auth.php:74 -msgid "Something went wrong with your attempt to authorize this App. Please try again." +#: src/API/Auth.php:104 +msgid "Connection information missing, please try again later." msgstr "" -#: src/API/Auth.php:97 -msgid "Empty response, please try again later." +#: src/API/Auth.php:122 +msgid "There was an error getting the account data. Please try again later." msgstr "" #. Translators: 1: Request method 2: Request endpoint 3: Response status code 4: Response message 5: Pinterest code -#: src/API/Base.php:131 +#: src/API/Base.php:115 msgid "" "%1$s Request: %2$s\n" "Status Code: %3$s\n" @@ -217,24 +230,24 @@ msgid "" msgstr "" #. Translators: 1: Request method 2: Request endpoint 3: Response status code 4: Response message -#: src/API/Base.php:153 +#: src/API/Base.php:137 msgid "" "%1$s Request: %2$s\n" "Status Code: %3$s\n" "API response: %4$s" msgstr "" -#: src/API/Base.php:243 +#: src/API/Base.php:301 msgid "Reconnect to your Pinterest account" msgstr "" -#: src/API/Base.php:298 +#: src/API/Base.php:356 msgid "Empty body" msgstr "" -#: src/API/Base.php:369 -#: src/API/Base.php:457 -#: src/Merchants.php:133 +#: src/API/Base.php:427 +#: src/API/Base.php:836 +#: src/Merchants.php:139 msgid "Auto-created by Pinterest for WooCommerce" msgstr "" @@ -243,277 +256,309 @@ msgstr "" msgid "Could not fetch linked business accounts for Pinterest account ID. [%s]" msgstr "" -#: src/API/FeedIssues.php:86 +#: src/API/FeedIssues.php:87 msgid "Error downloading feed issues file from Pinterest." msgstr "" #. Translators: The error description as returned from the API -#: src/API/FeedIssues.php:112 +#: src/API/FeedIssues.php:113 msgid "Could not get current feed's issues. [%s]" msgstr "" -#: src/API/FeedIssues.php:130 +#: src/API/FeedIssues.php:131 msgid "Invalid product" msgstr "" -#: src/API/FeedIssues.php:137 +#: src/API/FeedIssues.php:138 msgid "(Variation)" msgstr "" -#: src/API/FeedIssues.php:286 +#: src/API/FeedIssues.php:284 +msgid "No feed ID provided." +msgstr "" + +#: src/API/FeedIssues.php:291 msgid "Could not get feed report from Pinterest." msgstr "" -#: src/API/FeedState.php:126 -#: src/API/FeedState.php:231 +#: src/API/FeedState.php:128 +#: src/API/FeedState.php:233 msgid "XML feed" msgstr "" -#: src/API/FeedState.php:128 +#: src/API/FeedState.php:130 msgid "Product sync is disabled." msgstr "" #. Translators: The error description as returned from the API -#: src/API/FeedState.php:146 +#: src/API/FeedState.php:148 msgid "Error getting feed's state. [%s]" msgstr "" -#: src/API/FeedState.php:170 +#: src/API/FeedState.php:172 msgid "Feed generation in progress." msgstr "" #. translators: 1: Time string, 2: number of products, 3: opening anchor tag, 4: closing anchor tag -#: src/API/FeedState.php:174 +#: src/API/FeedState.php:176 msgid "Last activity: %1$s ago - Wrote %2$s product to %3$sfeed file%4$s." msgid_plural "Last activity: %1$s ago - Wrote %2$s products to %3$sfeed file%4$s." msgstr[0] "" msgstr[1] "" -#: src/API/FeedState.php:190 +#: src/API/FeedState.php:192 msgid "Up to date" msgstr "" #. translators: 1: Time string, 2: total number of products, 3: opening anchor tag, 4: closing anchor tag -#: src/API/FeedState.php:194 +#: src/API/FeedState.php:196 msgid "Successfully generated %1$s ago - Wrote %2$s product to %3$sfeed file%4$s" msgid_plural "Successfully generated %1$s ago - Wrote %2$s products to %3$sfeed file%4$s" msgstr[0] "" msgstr[1] "" -#: src/API/FeedState.php:210 +#: src/API/FeedState.php:212 msgid "Feed generation will start shortly." msgstr "" -#: src/API/FeedState.php:215 +#: src/API/FeedState.php:217 msgid "Feed configuration will start shortly." msgstr "" -#: src/API/FeedState.php:220 -#: src/API/FeedState.php:272 -#: src/Feeds.php:41 -#: src/Feeds.php:86 +#: src/API/FeedState.php:222 +#: src/API/FeedState.php:274 +#: src/Feeds.php:42 +#: src/Feeds.php:84 msgid "Could not get feed info." msgstr "" #. Translators: %1$s Time string, %2$s error message -#: src/API/FeedState.php:223 +#: src/API/FeedState.php:225 msgid "Last activity: %1$s ago - %2$s" msgstr "" -#: src/API/FeedState.php:260 -#: src/API/FeedState.php:309 +#: src/API/FeedState.php:262 +#: src/API/FeedState.php:311 msgid "Product feed not yet configured on Pinterest." msgstr "" -#: src/API/FeedState.php:266 +#: src/API/FeedState.php:268 msgid "Could not get merchant info." msgstr "" -#: src/API/FeedState.php:276 +#: src/API/FeedState.php:278 msgid "Product feed not active." msgstr "" -#: src/API/FeedState.php:284 +#: src/API/FeedState.php:286 msgid "Product feed configured for ingestion on Pinterest" msgstr "" #. Translators: %1$s The URL of the product feed, %2$s Time string -#: src/API/FeedState.php:290 +#: src/API/FeedState.php:292 msgid "Pinterest will fetch your product feed every %2$s" msgstr "" -#: src/API/FeedState.php:301 +#: src/API/FeedState.php:303 msgid "Product feed pending approval on Pinterest." msgstr "" -#: src/API/FeedState.php:305 +#: src/API/FeedState.php:307 msgid "Product feed declined by Pinterest" msgstr "" -#: src/API/FeedState.php:317 +#: src/API/FeedState.php:319 msgid "Remote feed setup" msgstr "" -#: src/API/FeedState.php:358 +#: src/API/FeedState.php:360 msgid "Pinterest tag" msgstr "" -#: src/API/FeedState.php:360 +#: src/API/FeedState.php:362 +#: src/API/FeedState.php:393 msgid "Potential conflicting plugins" msgstr "" -#: src/API/FeedState.php:382 +#: src/API/FeedState.php:391 +msgid "Pinterest Rich Pins" +msgstr "" + +#: src/API/FeedState.php:415 msgid "Feed is not registered with Pinterest." msgstr "" -#: src/API/FeedState.php:394 +#: src/API/FeedState.php:427 msgid "Response error when trying to get feed report from Pinterest." msgstr "" -#: src/API/FeedState.php:398 +#: src/API/FeedState.php:431 msgid "Response error. Feed report contains no feed workflow." msgstr "" -#: src/API/FeedState.php:414 +#: src/API/FeedState.php:447 msgid "Automatically pulled by Pinterest" msgstr "" #. Translators: %1$s Time string, %2$s number of products -#: src/API/FeedState.php:417 -#: src/API/FeedState.php:428 +#: src/API/FeedState.php:450 +#: src/API/FeedState.php:461 msgid "Last pulled: %1$s ago, containing %2$d products" msgstr "" -#: src/API/FeedState.php:425 +#: src/API/FeedState.php:458 msgid "Processing" msgstr "" -#: src/API/FeedState.php:436 +#: src/API/FeedState.php:469 msgid "Feed is under review." msgstr "" -#: src/API/FeedState.php:441 +#: src/API/FeedState.php:474 msgid "The feed is queued for processing." msgstr "" -#: src/API/FeedState.php:447 +#: src/API/FeedState.php:480 msgid "Feed ingestion failed." msgstr "" #. Translators: %1$s Time difference string -#: src/API/FeedState.php:450 +#: src/API/FeedState.php:483 msgid "Last attempt: %1$s ago" msgstr "" -#: src/API/FeedState.php:460 +#: src/API/FeedState.php:493 msgid "Unknown status in workflow." msgstr "" #. Translators: The status text returned by the API. -#: src/API/FeedState.php:463 +#: src/API/FeedState.php:496 msgid "API returned an unknown status: %1$s" msgstr "" -#: src/API/FeedState.php:486 +#: src/API/FeedState.php:519 msgid "Remote sync status" msgstr "" #. Translators: The error message as returned by the Pinterest API -#: src/API/FeedState.php:555 +#: src/API/FeedState.php:588 msgid "Pinterest returned: %1$s" msgstr "" -#: src/API/HealthCheck.php:60 +#: src/API/Health.php:69 msgid "Could not get approval status from Pinterest." msgstr "" #. Translators: The error description as returned from the API -#: src/API/HealthCheck.php:74 +#: src/API/Health.php:83 msgid "Could not fetch account status. [%s]" msgstr "" -#: src/API/Options.php:61 +#: src/API/Settings.php:68 msgid "Missing option parameters." msgstr "" -#: src/API/Options.php:67 +#: src/API/Settings.php:74 msgid "There was an error saving the settings." msgstr "" -#: src/API/Tags.php:52 +#: src/API/Tags.php:50 msgid "Advertiser missing" msgstr "" -#: src/API/Tags.php:70 +#: src/API/Tags.php:56 +#: src/PinterestSyncSettings.php:111 +msgid "Response error" +msgstr "" + +#: src/API/Tags.php:65 msgid "Could not create a tag. Please check the logs for additional information." msgstr "" #. Translators: The error description as returned from the API -#: src/API/Tags.php:79 +#: src/API/Tags.php:80 msgid "No tracking tag available. [%s]" msgstr "" +#: src/API/UserInteraction.php:92 +msgid "Unrecognized interaction parameter" +msgstr "" + #: src/Crypto.php:95 #: src/Crypto.php:139 msgid "Could not decrypt key value. Try reconnecting to Pinterest." msgstr "" +#. translators: error message with file path +#: src/FeedFileOperations.php:104 +msgid "Could not open temporary file %s for writing" +msgstr "" + +#. translators: error message with file path +#: src/FeedFileOperations.php:114 +msgid "Temporary file: %s is not writeable." +msgstr "" + +#. translators: 1: temporary file name 2: final file name +#: src/FeedFileOperations.php:135 +msgid "Could not rename %1$s to %2$s" +msgstr "" + #. translators: time in the format hours:minutes:seconds -#: src/FeedGenerator.php:97 +#: src/FeedGenerator.php:119 msgid "Feed scheduled to run at %s." msgstr "" -#: src/FeedGenerator.php:119 +#: src/FeedGenerator.php:141 msgid "Feed generation queued." msgstr "" -#: src/FeedGenerator.php:130 +#: src/FeedGenerator.php:152 msgid "Feed generation start. Preparing temporary files." msgstr "" -#: src/FeedGenerator.php:154 +#: src/FeedGenerator.php:198 msgid "Feed generation end. Moving files to the final destination." msgstr "" -#: src/FeedGenerator.php:165 +#: src/FeedGenerator.php:213 msgid "Feed generated successfully." msgstr "" #. translators: number of products -#: src/FeedGenerator.php:302 +#: src/FeedGenerator.php:301 msgid "Feed batch generated. Wrote %s products to the feed file." msgstr "" -#. translators: error message with file path -#: src/FeedGenerator.php:410 -msgid "Could not open temporary file %s for writing" +#: src/FeedGenerator.php:572 +msgid "Aborting the feed generation after too many retries." msgstr "" -#. translators: error message with file path -#: src/FeedGenerator.php:420 -msgid "Temporary file: %s is not writeable." +#. Translators: The batch number. +#: src/FeedGenerator.php:582 +msgid "There was an error running the batch #%s, it will be rescheduled to run again." msgstr "" -#. translators: 1: temporary file name 2: final file name -#: src/FeedGenerator.php:459 -msgid "Could not rename %1$s to %2$s" +#: src/FeedGenerator.php:659 +msgid "There was not possible to re-schedule the action, no args available." msgstr "" -#: src/FeedRegistration.php:95 +#: src/FeedRegistration.php:96 msgid "Could not register feed." msgstr "" -#: src/Feeds.php:45 -#: src/Feeds.php:90 +#: src/Feeds.php:46 +#: src/Feeds.php:88 msgid "Wrong feed info." msgstr "" -#: src/Feeds.php:57 +#: src/Feeds.php:58 msgid "No feed found with the requested ID." msgstr "" -#: src/Feeds.php:102 -msgid "No feed found with the requested location." +#. translators: %s is the locale code. +#: src/LocaleMapper.php:105 +msgid "No matching Pinterest API locale found for %s" msgstr "" #: src/Merchants.php:71 @@ -525,7 +570,7 @@ msgid "There was an error trying to get the merchant object." msgstr "" #: src/Merchants.php:84 -#: src/Merchants.php:149 +#: src/Merchants.php:172 msgid "Response error when trying to create a merchant or update the existing one." msgstr "" @@ -537,6 +582,10 @@ msgstr "" msgid "No merchant returned in the advertiser's response." msgstr "" +#: src/Merchants.php:155 +msgid "There was a previous error trying to create or update merchant." +msgstr "" + #: src/Notes/Collection/AbstractCompleteOnboarding.php:72 msgid "Complete setup" msgstr "" @@ -581,14 +630,22 @@ msgstr "" msgid "Enable Sync" msgstr "" +#: src/PinterestSyncSettings.php:83 +msgid "Missing method to sync the setting." +msgstr "" + +#: src/PinterestSyncSettings.php:103 +msgid "Tracking advertiser or tag missing" +msgstr "" + #. translators: plugin version. -#: src/PluginUpdate.php:141 +#: src/PluginUpdate.php:157 msgid "Plugin updated to version: %s." msgstr "" -#. translators: 1: plugin version, 2: error message. -#: src/PluginUpdate.php:163 -msgid "Plugin update to version %1$s error: %2$s" +#. translators: 1: plugin version, 2: failed procedure, 3: error message. +#: src/PluginUpdate.php:180 +msgid "Plugin update to version %1$s. Procedure: %2$s. Error: %3$s" msgstr "" #: src/Product/Attributes/Condition.php:48 @@ -623,6 +680,11 @@ msgstr "" msgid "Visit the settings page to enable it." msgstr "" +#. Translators: 1: Conflicting plugins, 2: Plugins Admin page opening tag, 3: Pinterest settings opening tag, 4: Closing anchor tag +#: src/RichPins.php:293 +msgid "The following installed plugin(s) can potentially cause problems with Rich Pins: %1$s. %2$sRemove conflicting plugins%4$s or %3$smanage Rich Pins settings%4$s." +msgstr "" + #. translators: 1: error message. #: src/Shipping.php:88 msgid "" @@ -631,17 +693,17 @@ msgid "" msgstr "" #. Translators: The error description -#: src/Tracking.php:553 +#: src/Tracking.php:617 msgid "Advertiser connected successfully" msgstr "" #. Translators: The error description -#: src/Tracking.php:558 +#: src/Tracking.php:622 msgid "Could not connect the advertiser. Try to connect from the connection tab. [%s]" msgstr "" #. Translators: 1: Conflicting plugins, 2: Plugins Admin page opening tag, 3: Pinterest settings opening tag, 4: Closing anchor tag -#: src/Tracking.php:620 +#: src/Tracking.php:684 msgid "The following installed plugin(s) can potentially cause problems with tracking: %1$s. %2$sRemove conflicting plugins%4$s or %3$smanage tracking settings%4$s." msgstr "" @@ -657,6 +719,75 @@ msgstr "" msgid "Search for a category…" msgstr "" +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:25 +msgid "You are eligible for $125 of Pinterest ad credits. To claim the credits, you would need to add your billing details and spend $15 on Pinterest ads." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:39 +msgid "You are eligible for $125 of Pinterest ad credits. To claim the credits, head over to the Pinterest ads manager and " +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:44 +msgid "spend $15 on Pinterest ads." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:86 +msgid "You are one step away from claiming $125 of Pinterest ad credits." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:92 +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingModal.js:43 +msgid "You have successfully set up your Pinterest integration! Your product catalog is being synced and reviewed. This could take up to 2 days." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:99 +msgid "*Ad credits may take up to 24 hours to be credited to account." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:107 +msgid "Got it" +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:112 +msgid "Do this later" +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingAdsModal.js:126 +msgid "Add billing details" +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js:38 +msgid "Advertiser already has a redeemed offer." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js:46 +msgid "The merchant already redeemed the offer on another advertiser." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js:54 +msgid "Unable to claim Pinterest ads credits as the offer has expired." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js:62 +msgid "Unable to claim Pinterest ads credits as the offer code is not available for your country." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingErrorModal.js:70 +msgid "Offer code can only be redeemed by an advertiser with a credit card billing profile." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingModal.js:37 +msgid "You have successfully set up your Pinterest integration." +msgstr "" + +#: assets/source/catalog-sync/components/OnboardingModals/OnboardingModal.js:51 +msgid "View catalog" +msgstr "" + +#: assets/source/catalog-sync/sections/AdCreditsNotice.js:89 +msgid "Spend $15 to get $125 in Pinterest ad credits. To claim the credits, add your billing details." +msgstr "" + #: assets/source/catalog-sync/sections/SyncIssuesTable.js:23 msgid "Type" msgstr "" @@ -677,12 +808,17 @@ msgstr "" msgid "Issues" msgstr "" -#: assets/source/catalog-sync/sections/SyncState.js:45 +#. translators: %s credits value with currency formatted using wc_price +#: assets/source/catalog-sync/sections/SyncState.js:50 +msgid "You have %s of free ad credits left to use" +msgstr "" + +#: assets/source/catalog-sync/sections/SyncState.js:61 msgid "Overview" msgstr "" -#: assets/source/catalog-sync/sections/SyncState.js:54 -msgid "Set up and manage ads to increase your reach with Pinterest ads manager" +#: assets/source/catalog-sync/sections/SyncState.js:75 +msgid "Create ads to increase your reach with the Pinterest ads manager" msgstr "" #: assets/source/catalog-sync/sections/SyncStateSummary.js:13 @@ -721,7 +857,7 @@ msgstr "" msgid "The integration is only available to approved stores participating in the beta program." msgstr "" -#: assets/source/components/prelaunch-notice/index.js:49 +#: assets/source/components/prelaunch-notice/index.js:48 msgid "Click here for more information." msgstr "" @@ -730,7 +866,7 @@ msgid "Select a business account" msgstr "" #: assets/source/setup-guide/app/components/Account/BusinessAccountSelection.js:90 -#: assets/source/setup-guide/app/components/Account/Connection.js:245 +#: assets/source/setup-guide/app/components/Account/Connection.js:244 msgid "Connect" msgstr "" @@ -746,214 +882,342 @@ msgstr "" msgid "Create business account" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:115 +#: assets/source/setup-guide/app/components/Account/Connection.js:114 msgid "Are you sure?" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:122 +#: assets/source/setup-guide/app/components/Account/Connection.js:121 msgid "Are you sure you want to disconnect this account?" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:133 +#: assets/source/setup-guide/app/components/Account/Connection.js:132 msgid "Yes, I'm sure" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:139 +#: assets/source/setup-guide/app/components/Account/Connection.js:138 msgid "Cancel" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:168 +#: assets/source/setup-guide/app/components/Account/Connection.js:167 msgid "There was a problem while trying to disconnect." msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:192 +#: assets/source/setup-guide/app/components/Account/Connection.js:191 msgid "Business account" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:196 +#: assets/source/setup-guide/app/components/Account/Connection.js:195 msgid "Personal account" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:214 +#: assets/source/setup-guide/app/components/Account/Connection.js:213 msgid "Disconnect" msgstr "" -#: assets/source/setup-guide/app/components/Account/Connection.js:225 +#: assets/source/setup-guide/app/components/Account/Connection.js:224 msgid "Connect your Pinterest Account" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:20 -msgid "Merchant is an affiliate or resale marketplace" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:66 +msgid "Couldn’t retrieve the health status of your account." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:24 -msgid "Merchant does not meet our policy on prohibited products" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:85 +msgid "The feed is being configured. Depending on the number of products this may take a while as the feed needs to be fully generated before it is been sent to Pinterest for registration. You can check the status of the generation process in the Catalog tab." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:28 -msgid "Merchant offers services rather than products" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:93 +msgid "Please hold on tight as your account is pending approval from Pinterest. This may take up to 5 business days." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:32 -msgid "Merchant's domain age does not meet minimum requirement" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:101 +#: assets/source/setup-guide/app/components/HealthCheck/index.js:128 +msgid "Your merchant account is disapproved." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:36 -msgid "Merchant domain mismatched with merchant account" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:105 +msgid "If you have a valid reason for appealing a merchant review decision (such as having corrected the violations that resulted in the disapproval), you can submit an appeal." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:40 -msgid "Merchant's URL is broken or requires registration" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:113 +msgid "Submit an appeal" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:44 -msgid "Merchant's URL is incomplete or inaccessible" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:132 +msgid "Please hold on tight as there is an Appeal pending for your Pinterest account." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:48 -msgid "Merchant's shipping policy is unclear or unavailable" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:140 +msgid "Unable to upload catalog." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:52 -msgid "Merchant's returns policy is unclear or unavailable" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:144 +msgid "It looks like your Pinterest business account is connected to another e-commerce platform. Only one platform can be linked to a business account. To upload your catalog, disconnect your business account from the other platform and try again." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:56 -msgid "Merchant's information is incomplete or plagiarized" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:152 +msgid "Unable to register feed." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:60 -msgid "Merchant's products are out of stock" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:156 +msgid "It looks like your WordPress language settings are not supported by Pinterest." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:64 -msgid "Merchant's website includes banner or pop-up ads" +#: assets/source/setup-guide/app/components/HealthCheck/index.js:172 +msgid "Could not fetch account status." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:68 -msgid "Merchant's products do not meet image quality requirements" +#: assets/source/setup-guide/app/components/SaveSettingsButton.js:75 +#: assets/source/setup-guide/app/steps/SetupTracking.js:301 +msgid "The advertiser was connected successfully." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:72 -msgid "Merchant's product images include watermarks" +#: assets/source/setup-guide/app/components/SaveSettingsButton.js:93 +msgid "Your settings have been saved successfully." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:76 -msgid "Merchant's products are always on sale" +#: assets/source/setup-guide/app/components/SaveSettingsButton.js:101 +#: assets/source/setup-guide/app/steps/AdvancedSettings.js:36 +#: assets/source/setup-guide/app/steps/SetupPins.js:60 +#: assets/source/setup-guide/app/steps/SetupProductSync.js:36 +#: assets/source/setup-guide/app/steps/SetupTracking.js:236 +msgid "There was a problem saving your settings." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:80 -msgid "Merchant's products refer to outdated content" +#: assets/source/setup-guide/app/components/SaveSettingsButton.js:113 +msgid "Saving settings…" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:84 -msgid "Merchant's website uses generic product descriptions" +#: assets/source/setup-guide/app/components/SaveSettingsButton.js:114 +msgid "Save changes" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:88 -msgid "Merchant's website displays several pop-up messages" +#: assets/source/setup-guide/app/components/SyncSettings/index.js:42 +msgid "Settings successfully synced with Pinterest Ads Manager." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:92 -msgid "Merchant's product images are unavailable or mismatched" +#: assets/source/setup-guide/app/components/SyncSettings/index.js:53 +msgid "Failed to sync settings with Pinterest Ads Manager." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:96 -msgid "Resale marketplaces are not allowed" +#: assets/source/setup-guide/app/components/SyncSettings/index.js:91 +msgid "Syncing settings" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:100 -msgid "Affiliate links are not allowed" +#: assets/source/setup-guide/app/components/SyncSettings/index.js:99 +msgid "Sync to get latest settings from Pinterest Ads Manager" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:104 -msgid "Account does not meet the website requirements for verification" +#: assets/source/setup-guide/app/components/SyncSettings/index.js:106 +msgid "Sync" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:108 -msgid "Account does not meet the product requirements for verification" +#: assets/source/setup-guide/app/components/TermsAndConditionsModal.js:37 +msgid "Pinterest Terms & Conditions" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:112 -msgid "Account does not meet the brand reputation criteria for verification" +#: assets/source/setup-guide/app/components/TermsAndConditionsModal.js:47 +msgid "To be eligible and redeem the $125 ad credit from Pinterest, you must complete the setup of Pinterest for WooCommerce, set up your billing with Pinterest Ads manager, and spend $15 with Pinterest ads. Credits may take up to 24 hours to be credited to the user." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:150 -msgid "Couldn’t retrieve the health status of your account." +#: assets/source/setup-guide/app/components/TermsAndConditionsModal.js:53 +msgid "Each user is only eligible to receive the credits once. Ad credits may vary by country and is subject to availability." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:169 -msgid "The feed is being configured. Depending on the number of products this may take a while as the feed needs to be fully generated before it is been sent to Pinterest for registration. You can check the status of the generation process in the Catalog tab." +#: assets/source/setup-guide/app/components/TermsAndConditionsModal.js:59 +msgid "The following terms and conditions apply:" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:177 -msgid "Please hold on tight as your account is pending approval from Pinterest. This may take up to 5 business days." +#: assets/source/setup-guide/app/components/TermsAndConditionsModal.js:65 +msgid "Business Terms of Service" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:185 -#: assets/source/setup-guide/app/components/HealthCheck/index.js:212 -msgid "Your merchant account is disapproved." +#: assets/source/setup-guide/app/components/TermsAndConditionsModal.js:83 +msgid "Privacy Policy" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:189 -msgid "If you have a valid reason for appealing a merchant review decision (such as having corrected the violations that resulted in the disapproval), you can submit an appeal." +#: assets/source/setup-guide/app/components/TermsAndConditionsModal.js:101 +msgid "Pinterest Advertising Services Agreement" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:197 -msgid "Submit an appeal" +#: assets/source/setup-guide/app/components/ThirdPartyTagsNotice.js:12 +msgid "Potential conflicting plugins detected." msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:216 -msgid "Please hold on tight as there is an Appeal pending for your Pinterest account." +#: assets/source/setup-guide/app/components/TopBar.js:15 +msgid "Get started with Pinterest for WooCommerce" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:224 -msgid "Unable to upload catalog." +#: assets/source/setup-guide/app/components/UnsupportedCountryNotice/index.js:45 +msgid "Your store’s country is . This country is currently not supported by Pinterest for WooCommerce. However, you can still choose to list your products in supported countries, if you are able to sell your products to customers there. Change your store’s country here. Read more about supported countries" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:228 -msgid "It looks like your Pinterest business account is connected to another e-commerce platform. Only one platform can be linked to a business account. To upload your catalog, disconnect your business account from the other platform and try again." +#: assets/source/setup-guide/app/constants.js:39 +msgid "Merchant is an affiliate or resale marketplace" msgstr "" -#: assets/source/setup-guide/app/components/HealthCheck/index.js:244 -msgid "Could not fetch account status." +#: assets/source/setup-guide/app/constants.js:43 +msgid "Merchant is a resale marketplace" msgstr "" -#: assets/source/setup-guide/app/components/SaveSettingsButton.js:70 -#: assets/source/setup-guide/app/steps/SetupTracking.js:298 -msgid "The advertiser was connected successfully." +#: assets/source/setup-guide/app/constants.js:47 +msgid "Merchant is an affiliate marketplace or marketer" msgstr "" -#: assets/source/setup-guide/app/components/SaveSettingsButton.js:80 -msgid "Your settings have been saved successfully." +#: assets/source/setup-guide/app/constants.js:51 +msgid "Merchant is a wholesale seller" msgstr "" -#: assets/source/setup-guide/app/components/SaveSettingsButton.js:88 -#: assets/source/setup-guide/app/steps/AdvancedSettings.js:36 -#: assets/source/setup-guide/app/steps/SetupPins.js:47 -#: assets/source/setup-guide/app/steps/SetupProductSync.js:36 -#: assets/source/setup-guide/app/steps/SetupTracking.js:234 -msgid "There was a problem saving your settings." +#: assets/source/setup-guide/app/constants.js:55 +msgid "Merchant does not meet our policy on prohibited products" msgstr "" -#: assets/source/setup-guide/app/components/SaveSettingsButton.js:100 -msgid "Saving settings…" +#: assets/source/setup-guide/app/constants.js:59 +msgid "Merchant offers services rather than products" msgstr "" -#: assets/source/setup-guide/app/components/SaveSettingsButton.js:101 -msgid "Save changes" +#: assets/source/setup-guide/app/constants.js:63 +msgid "Merchant's domain age does not meet minimum requirement" msgstr "" -#: assets/source/setup-guide/app/components/ThirdPartyTagsNotice.js:12 -msgid "Potential conflicting plugins detected." +#: assets/source/setup-guide/app/constants.js:67 +msgid "Merchant domain mismatched with merchant account" msgstr "" -#: assets/source/setup-guide/app/components/TopBar.js:15 -msgid "Get started with Pinterest for WooCommerce" +#: assets/source/setup-guide/app/constants.js:71 +#: assets/source/setup-guide/app/constants.js:75 +msgid "Merchant's shipping policy is unclear or unavailable" msgstr "" -#: assets/source/setup-guide/app/components/UnsupportedCountryNotice/index.js:45 -msgid "Your store’s country is . This country is currently not supported by Pinterest for WooCommerce. However, you can still choose to list your products in supported countries, if you are able to sell your products to customers there. Change your store’s country here. Read more about supported countries" +#: assets/source/setup-guide/app/constants.js:79 +#: assets/source/setup-guide/app/constants.js:83 +msgid "Merchant's returns policy is unclear or unavailable" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:87 +msgid "Merchant's URL is broken or requires registration" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:91 +msgid "Merchant's domain URL is broken" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:95 +msgid "Merchant's domain requires registration to view products" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:99 +msgid "Merchant's URL is incomplete" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:103 +msgid "Merchant's domain does not meet brand information requirements" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:107 +msgid "There is no 'About Us' page or no social information in your website" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:111 +msgid "There is no contact information in your website" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:115 +msgid "Merchant's products are out of stock" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:119 +msgid "Merchant's website includes banner or pop-up ads" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:123 +#: assets/source/setup-guide/app/constants.js:127 +msgid "Merchant's products do not meet image quality requirements" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:131 +msgid "Merchant's product images include watermarks" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:135 +msgid "Merchant's products are always on sale" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:139 +msgid "Merchant's products refer to outdated content" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:143 +msgid "Merchant's website uses generic product descriptions" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:147 +msgid "Merchant's product descriptions and categories do not meet requirements" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:151 +msgid "Merchant's website displays several pop-up messages" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:155 +#: assets/source/setup-guide/app/constants.js:232 +msgid "Merchant does not meet minimum website quality requirements" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:159 +msgid "Merchant's social media links are broken" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:163 +msgid "We're unable to include you as a merchant at this time. We determined your account doesn't comply with our guidelines. Your full catalog has been removed from Pinterest." +msgstr "" + +#: assets/source/setup-guide/app/constants.js:167 +msgid "Merchant's product images are unavailable or mismatched" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:172 +msgid "We recently updated our merchant guidelines and have found that your account is currently not in compliance with our guidelines. Merchants who do not comply with our guidelines will not be able to distribute or promote product Pins from their catalog on Pinterest. If you’d like to appeal this decision, review our guidelines for more detailed information on how you can get your products on Pinterest." +msgstr "" + +#: assets/source/setup-guide/app/constants.js:192 +msgid "Resale marketplaces are not allowed" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:196 +msgid "Affiliate links are not allowed" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:200 +msgid "Account does not meet the website requirements for verification" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:204 +msgid "Account does not meet the product requirements for verification" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:208 +msgid "Account does not meet the brand reputation criteria for verification" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:212 +msgid "The template of the website is incomplete" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:216 +msgid "Merchant does not meet community guidelines" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:220 +msgid "Merchant has exceeded number of reported ads" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:224 +msgid "Merchant has exceeded number of user reports" +msgstr "" + +#: assets/source/setup-guide/app/constants.js:228 +msgid "Merchant does not meet minimum product requirements" msgstr "" #: assets/source/setup-guide/app/steps/AdvancedSettings.js:49 @@ -977,145 +1241,180 @@ msgid "Erase Plugin Data" msgstr "" #. translators: %s: error reason returned by Pinterest when verifying website claim fail. -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:51 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:48 msgid "We were unable to verify this domain. %s" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:134 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:136 msgid "Couldn’t verify your domain." msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:151 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:153 msgid "Start verification" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:155 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:157 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:197 msgid "Verifying…" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:156 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:158 msgid "Try again" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:157 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:159 msgid "Verified" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:175 -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:188 -#: assets/source/setup-guide/app/views/WizardApp.js:77 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:180 +#: assets/source/setup-guide/app/steps/SetupTracking.js:273 +msgid "Try Again" +msgstr "" + +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:181 +#: assets/source/setup-guide/app/steps/SetupTracking.js:274 +msgid "Complete Setup" +msgstr "" + +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:206 +msgid "Connected successfully." +msgstr "" + +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:221 +#: assets/source/setup-guide/app/steps/SetupTracking.js:320 +msgid "There was a problem connecting the advertiser." +msgstr "" + +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:233 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:246 +#: assets/source/setup-guide/app/views/WizardApp.js:75 msgid "Claim your website" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:179 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:237 msgid "Step Two" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:192 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:250 msgid "Verified domain" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:212 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:270 msgid "Verify your domain to claim your website" msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:218 +#: assets/source/setup-guide/app/steps/ClaimWebsite.js:276 msgid "This will allow access to analytics for the Pins you publish from your site, the analytics on Pins that other people create from your site, and let people know where they can find more of your content." msgstr "" -#: assets/source/setup-guide/app/steps/ClaimWebsite.js:247 -#: assets/source/setup-guide/app/steps/SetupAccount.js:243 -#: assets/source/setup-guide/app/steps/SetupTracking.js:270 -msgid "Continue" +#: assets/source/setup-guide/app/steps/components/AdsCreditsPromo.js:55 +msgid "As a new Pinterest customer, you can get $125 in free ad credits when you successfully set up Pinterest for WooCommerce and spend $15 on Pinterest Ads. Pinterest Terms and conditions apply." msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:94 +#: assets/source/setup-guide/app/steps/SetupAccount.js:97 msgid "Couldn’t retrieve your linked business accounts." msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:106 -#: assets/source/setup-guide/app/views/WizardApp.js:62 +#: assets/source/setup-guide/app/steps/SetupAccount.js:109 +#: assets/source/setup-guide/app/views/WizardApp.js:60 msgid "Set up your business account" msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:110 +#: assets/source/setup-guide/app/steps/SetupAccount.js:113 msgid "Step One" msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:119 +#: assets/source/setup-guide/app/steps/SetupAccount.js:122 msgid "Pinterest business account" msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:123 +#: assets/source/setup-guide/app/steps/SetupAccount.js:126 msgid "Linked account" msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:129 +#: assets/source/setup-guide/app/steps/SetupAccount.js:132 msgid "Set up a free Pinterest business account to get access to analytics on your Pins and the ability to run ads. This requires agreeing to our advertising guidelines and following our merchant guidelines." msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:204 +#: assets/source/setup-guide/app/steps/SetupAccount.js:205 msgid "Or, create a new Pinterest account" msgstr "" -#: assets/source/setup-guide/app/steps/SetupAccount.js:231 +#: assets/source/setup-guide/app/steps/SetupAccount.js:232 msgid "Or, convert your personal account" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:60 +#: assets/source/setup-guide/app/steps/SetupAccount.js:244 +#: assets/source/setup-guide/app/steps/SetupTracking.js:272 +msgid "Continue" +msgstr "" + +#: assets/source/setup-guide/app/steps/SetupPins.js:73 msgid "Publish Pins and Rich Pins" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:64 +#: assets/source/setup-guide/app/steps/SetupPins.js:77 msgid "Rich Pins are a type of organic Pin that automatically sync information from your website to your Pins. You can identify Rich Pins by the extra information above and below the image on closeup and the bold title in your feed. If something changes on the original website, the Rich Pin updates to reflect that change." msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:80 +#: assets/source/setup-guide/app/steps/SetupPins.js:93 msgid "Tracking" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:86 +#: assets/source/setup-guide/app/steps/SetupPins.js:99 msgid "Track conversions" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:92 +#: assets/source/setup-guide/app/steps/SetupPins.js:105 msgid "Gather analytics for Pins you publish and Pins users create from your site." msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:109 +#: assets/source/setup-guide/app/steps/SetupPins.js:122 msgid "Enhanced Match support" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:115 -msgid "Matches conversion data with the person responsible for the conversion and lets you track cross-device checkouts. Requires Track Conversion option to be enabled." +#: assets/source/setup-guide/app/steps/SetupPins.js:129 +msgid "Matches conversion data with the person responsible for the conversion and lets you track cross-device checkouts. Requires Track Conversion option to be enabled. See more" +msgstr "" + +#: assets/source/setup-guide/app/steps/SetupPins.js:175 +msgid "Automatic Enhanced Match support" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:143 +#: assets/source/setup-guide/app/steps/SetupPins.js:181 +msgid "Uses hashed information that your customers have already provided to your business to help match more of your website visitors and conversions to people on Pinterest. Enabling it may improve the performance of your campaigns and can help increase the size of your Pinterest tag audiences." +msgstr "" + +#: assets/source/setup-guide/app/steps/SetupPins.js:205 +msgid "Manage information shared on Pinterest Ads Manager " +msgstr "" + +#: assets/source/setup-guide/app/steps/SetupPins.js:232 msgid "Rich Pins" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:149 +#: assets/source/setup-guide/app/steps/SetupPins.js:238 msgid "Add Rich Pins for Products" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:155 +#: assets/source/setup-guide/app/steps/SetupPins.js:244 msgid "Automatically create and update rich pins on Pinterest for all synced products." msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:172 +#: assets/source/setup-guide/app/steps/SetupPins.js:261 msgid "Add Rich Pins for Posts" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:178 +#: assets/source/setup-guide/app/steps/SetupPins.js:267 msgid "Automatically create and update rich pins on Pinterest for posts." msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:198 -#: assets/source/setup-guide/app/steps/SetupPins.js:204 +#: assets/source/setup-guide/app/steps/SetupPins.js:287 +#: assets/source/setup-guide/app/steps/SetupPins.js:293 msgid "Save to Pinterest" msgstr "" -#: assets/source/setup-guide/app/steps/SetupPins.js:210 +#: assets/source/setup-guide/app/steps/SetupPins.js:299 msgid "Adds a ‘Save’ button on images allowing customers to save things straight from your website to Pinterest." msgstr "" @@ -1131,148 +1430,143 @@ msgstr "" msgid "Enable Product Sync" msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:127 +#: assets/source/setup-guide/app/steps/SetupTracking.js:129 msgid "Couldn’t retrieve your advertisers." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:185 +#: assets/source/setup-guide/app/steps/SetupTracking.js:187 msgid "Couldn’t retrieve your tags." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:271 -msgid "Try Again" -msgstr "" - -#: assets/source/setup-guide/app/steps/SetupTracking.js:272 -msgid "Complete Setup" -msgstr "" - -#: assets/source/setup-guide/app/steps/SetupTracking.js:317 -msgid "There was a problem connecting the advertiser." -msgstr "" - -#: assets/source/setup-guide/app/steps/SetupTracking.js:329 -#: assets/source/setup-guide/app/steps/SetupTracking.js:346 -#: assets/source/setup-guide/app/views/WizardApp.js:83 +#: assets/source/setup-guide/app/steps/SetupTracking.js:332 +#: assets/source/setup-guide/app/steps/SetupTracking.js:349 msgid "Track conversions with the Pinterest tag" msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:333 +#: assets/source/setup-guide/app/steps/SetupTracking.js:336 msgid "Step Three" msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:342 +#: assets/source/setup-guide/app/steps/SetupTracking.js:345 msgid "Select your advertiser and tag" msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:353 -msgid "The Pinterest tag is a piece of JavaScript code you put on your website to gather conversion insights and build audiences to target based on actions people have taken on your site." +#: assets/source/setup-guide/app/steps/SetupTracking.js:357 +msgid "The Pinterest tag is a piece of JavaScript code you put on your website to gather conversion insights and build audiences to target based on actions people have taken on your site." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:359 -msgid "Using conversion tags means you agree to our" +#: assets/source/setup-guide/app/steps/SetupTracking.js:380 +msgid "Using conversion tags means you agree to our Ad Guidelines and Ad Data Terms." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:373 -msgid "Ad Guidelines" +#: assets/source/setup-guide/app/steps/SetupTracking.js:416 +msgid "Automatic Enhanced Match is enabled by default to match more of your website visitors and conversions to people on Pinterest. You can manage this in Settings." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:378 -msgid "and" -msgstr "" - -#: assets/source/setup-guide/app/steps/SetupTracking.js:389 -msgid "Ad Data Terms" -msgstr "" - -#: assets/source/setup-guide/app/steps/SetupTracking.js:415 +#: assets/source/setup-guide/app/steps/SetupTracking.js:451 msgid "Advertiser" msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:439 +#: assets/source/setup-guide/app/steps/SetupTracking.js:475 msgid "Select the advertiser for which you would like to install a tracking snippet." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:452 +#: assets/source/setup-guide/app/steps/SetupTracking.js:488 msgid "Tracking Tag" msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:481 +#: assets/source/setup-guide/app/steps/SetupTracking.js:516 msgid "Select the tracking tag to use." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:498 +#: assets/source/setup-guide/app/steps/SetupTracking.js:533 msgid "In order to proceed you need to read and accept the contents of the Pinterest Advertising Agreement." msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:506 +#: assets/source/setup-guide/app/steps/SetupTracking.js:541 msgid "I accept the Pinterest Advertising Agreement" msgstr "" -#: assets/source/setup-guide/app/steps/SetupTracking.js:553 +#: assets/source/setup-guide/app/steps/SetupTracking.js:587 msgid "An error occurred while attempting to fetch Advertisers & Tags from Pinterest. Please try again." msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:69 +#: assets/source/setup-guide/app/views/LandingPageApp.js:75 msgid "Get your products in front of more than 400M people on Pinterest" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:76 +#: assets/source/setup-guide/app/views/LandingPageApp.js:82 msgid "Pinterest is a visual discovery engine people use to find inspiration for their lives! More than 400 million people have saved more than 300 billion Pins, making it easier to turn inspiration into their next purchase." msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:84 +#: assets/source/setup-guide/app/views/LandingPageApp.js:90 msgid "Get started" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:90 +#: assets/source/setup-guide/app/views/LandingPageApp.js:96 msgid "By clicking ‘Get started’, you agree to our Terms of Service." msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:134 +#: assets/source/setup-guide/app/views/LandingPageApp.js:174 +msgid "Try Pinterest for WooCommerce and get $125 in ad credits!" +msgstr "" + +#: assets/source/setup-guide/app/views/LandingPageApp.js:181 +msgid "To help you get started with Pinterest Ads, new Pinterest customers can get $125 in ad credits when they have successfully set up Pinterest for WooCommerce and spend $15 on Pinterest Ads. Pinterest Terms and conditions apply." +msgstr "" + +#: assets/source/setup-guide/app/views/LandingPageApp.js:217 msgid "Sync your catalog" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:138 +#: assets/source/setup-guide/app/views/LandingPageApp.js:221 msgid "Connect your store to seamlessly sync your product catalog with Pinterest and create rich pins for each item. Your pins are kept up to date with daily automatic updates." msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:148 +#: assets/source/setup-guide/app/views/LandingPageApp.js:231 msgid "Increase organic reach" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:152 +#: assets/source/setup-guide/app/views/LandingPageApp.js:235 msgid "Pinterest users can easily discover, save and buy products from your website without any advertising spend from you. Track your performance with the Pinterest tag." msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:162 +#: assets/source/setup-guide/app/views/LandingPageApp.js:245 msgid "Create a storefront on Pinterest" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:166 +#: assets/source/setup-guide/app/views/LandingPageApp.js:249 msgid "Syncing your catalog creates a Shop tab on your Pinterest profile which allows Pinterest users to easily discover your products." msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:190 +#: assets/source/setup-guide/app/views/LandingPageApp.js:273 msgid "Frequently asked questions" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:197 +#: assets/source/setup-guide/app/views/LandingPageApp.js:280 msgid "Why am I getting an “Account not connected” error message?" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:201 +#: assets/source/setup-guide/app/views/LandingPageApp.js:284 msgid "Your password might have changed recently. Click Reconnect Pinterest Account and follow the instructions on screen to restore the connection." msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:208 +#: assets/source/setup-guide/app/views/LandingPageApp.js:291 msgid "I have more than one Pinterest Advertiser account. Can I connect my WooCommerce store to multiple Pinterest Advertiser accounts?" msgstr "" -#: assets/source/setup-guide/app/views/LandingPageApp.js:212 +#: assets/source/setup-guide/app/views/LandingPageApp.js:295 msgid "Only one Pinterest advertiser account can be linked to each WooCommerce store. If you want to connect a different Pinterest advertiser account you will need to either Disconnect the existing Pinterest Advertiser account from your current WooCommerce store and connect a different Pinterest Advertiser account, or Create another WooCommerce store and connect the additional Pinterest Advertiser account." msgstr "" +#: assets/source/setup-guide/app/views/LandingPageApp.js:302 +msgid "How do I redeem the $125 ad credit from Pinterest?" +msgstr "" + +#: assets/source/setup-guide/app/views/LandingPageApp.js:306 +msgid "To be eligible and redeem the $125 ad credit from Pinterest, you must complete the setup of Pinterest for WooCommerce, set up your billing with Pinterest Ads manager, and spend $15 with Pinterest ads. Ad credits may vary by country and is subject to availability. Credits may take up to 24 hours to be credited to the user. Each user is only eligible to receive the ad credits once." +msgstr "" + #: assets/source/setup-guide/index.js:47 msgid "Marketing" msgstr "" @@ -1284,15 +1578,3 @@ msgstr "" #: assets/source/setup-guide/index.js:108 msgid "Products Catalog" msgstr "" - -#: assets/source/setup-task/index.js:19 -msgid "Setup Pinterest for WooCommerce" -msgstr "" - -#: assets/source/setup-task/index.js:28 -msgid "Connect your store to Pinterest to sync products and track conversions." -msgstr "" - -#: assets/source/setup-task/index.js:32 -msgid "5 minutes" -msgstr "" diff --git a/package-lock.json b/package-lock.json index e7e4eb1e2..b7d06234f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23925,9 +23925,9 @@ "dev": true }, "prettier": { - "version": "npm:wp-prettier@2.2.1-beta-1", - "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz", - "integrity": "sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw==", + "version": "npm:wp-prettier@2.8.5", + "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.8.5.tgz", + "integrity": "sha512-gkphzYtVksWV6D7/V530bTehKkhrABUru/Gy4reOLOHJoKH4i9lcE1SxqU2VDxC3gCOx/Nk9alZmWk6xL/IBCw==", "dev": true }, "prettier-linter-helpers": { diff --git a/src/API/APIV5.php b/src/API/APIV5.php index 548e62dc4..21654be25 100644 --- a/src/API/APIV5.php +++ b/src/API/APIV5.php @@ -9,6 +9,10 @@ namespace Automattic\WooCommerce\Pinterest\API; +use Automattic\WooCommerce\Pinterest\PinterestApiException; +use Automattic\WooCommerce\Pinterest\PinterestApiException as ApiException; +use Exception; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -18,7 +22,7 @@ */ class APIV5 extends Base { - const API_DOMAIN = 'https://api.pinterest.com/v5'; + const API_DOMAIN = 'https://api.pinterest.com/v5'; /** * Prepare request @@ -32,16 +36,274 @@ class APIV5 extends Base { */ public static function prepare_request( $endpoint, $method = 'POST', $payload = array(), $api = '' ) { - $request = array( - 'url' => self::API_DOMAIN . "/{$endpoint}", - 'method' => $method, - 'args' => $payload, - 'headers' => array( + return array( + 'url' => static::API_DOMAIN . "/{$endpoint}", + 'method' => $method, + 'args' => ! empty( $payload ) ? wp_json_encode( $payload ) : array(), + 'headers' => array( 'Pinterest-Woocommerce-Version' => PINTEREST_FOR_WOOCOMMERCE_VERSION, + 'Content-Type' => 'application/json', ), + 'data_format' => 'body', ); + } + + /** + * Returns basic user information. + * + * @since x.x.x + * + * @return mixed|array { + * User account object. + * + * @type string $account_type Type of account. Enum: "PINNER" "BUSINESS". + * @type string $profile_image + * @type string $website_url + * @type string $username + * @type string $business_name + * @type int $board_count User account board count. + * @type int $pin_count User account pin count. This includes both created and saved pins. + * @type int $follower_count User account follower count. + * @type int $following_count User account following count. + * @type int $monthly_views User account monthly views. + * } + * @throws ApiException Throws 403 and 500 exceptions. + */ + public static function get_account_info() { + return self::make_request( 'user_account', 'GET' ); + } + + + /** + * Returns the list of the user's websites. + * + * @since x.x.x + * + * @return array { + * User account websites. + * + * @type array[] $items { + * @type string $website Website with path or domain only + * @type string $status Status of the verification process + * @type string $verified_at UTC timestamp when the verification happened - sometimes missing + * } + * @type string $bookmark + * } + * @throws ApiException Throws 403 and 500 exceptions. + */ + public static function get_user_websites() { + return self::make_request( 'user_account/websites', 'GET' ); + } + + /** + * Returns the list of linked businesses. + * + * @since x.x.x + * + * @return mixed|array[] { + * Linked businesses list. + * + * @type string $username + * @type string $image_small_url + * @type string $image_medium_url + * @type string $image_large_url + * @type string $image_xlarge_url + * } + * @throws ApiException Throws 500 exception in case of unexpected error. + */ + public static function get_linked_businesses() { + return self::make_request( 'user_account/businesses', 'GET' ); + } - return $request; + /** + * Get the advertiser object from the Pinterest API for the given User ID. + * + * @since x.x.x + * + * @param string $pinterest_user The Pinterest User ID. + * + * @return mixed + */ + public static function get_advertisers( $pinterest_user = null ) { + return self::make_request( 'ad_accounts', 'GET' ); } + /** + * Get the advertiser's tracking tags. + * + * @param string $ad_account_id the advertiser_id to request the tags for. + * + * @return array { + * Tag objects list. + * + * @type array[] $items { + * Tag object. + * + * @type string $ad_account_id Ad account ID. + * @type string $code_snippet Tag code snippet. + * @type ?string $enhanced_match_status The enhanced match status of the tag. + * @type string $id Tag ID. + * @type ?int $last_fired_time_ms Time for the last event fired. + * + * @type string $name Conversion tag name. + * @type string $status Entity status. + * @type string $version Version number. + * @type array $configs { + * Tag Enhanced Match configuration. + * + * @type ?bool $aem_enabled Whether Automatic Enhanced Match email is enabled. + * @type ?int $md_frequency Metadata ingestion frequency. + * @type ?bool $aem_fnln_enabled Whether Automatic Enhanced Match first name and last name is enabled. + * @type ?bool $aem_ph_enabled Whether Automatic Enhanced Match phone is enabled. + * @type ?bool $aem_ge_enabled Whether Automatic Enhanced Match gender is enabled. + * @type ?bool $aem_db_enabled Whether Automatic Enhanced Match birthdate is enabled. + * @type ?bool $aem_loc_enabled Whether Automatic Enhanced Match location is enabled. + * } + * } + * } + * + * @throws ApiException|Exception Throws 500 exception. + */ + public static function get_advertiser_tags( $ad_account_id ) { + return self::make_request( "ad_accounts/{$ad_account_id}/conversion_tags", 'GET' ); + } + + /** + * Get the advertiser's tracking tag config and details. + * + * @link https://developers.pinterest.com/docs/api/v5/#operation/conversion_tags/get + * + * @since x.x.x + * + * @param string $ad_account_id Ad account ID. + * @param string $conversion_tag_id Conversion tag ID. + * + * @return mixed|array { + * Tag object. + * + * @type string $ad_account_id Ad account ID. + * @type string $code_snippet Tag code snippet. + * @type string $enhanced_match_status Enum: "UNKNOWN" "NOT_VALIDATED" "VALIDATING_IN_PROGRESS" "VALIDATION_COMPLETE" null + * The enhanced match status of the tag + * @type string $id Tag ID. + * @type int $last_fired_time_ms Time for the last event fired. + * @type string $name Conversion tag name. + * @type string $status Enum: "ACTIVE" "PAUSED" "ARCHIVED" + * Entity status + * @type string $version Version number. + * @type array $configs { + * Tag Enhanced Match configuration. + * + * @type bool $aem_enabled Whether Automatic Enhanced Match email is enabled. + * @type int $md_frequency Metadata ingestion frequency. + * @type bool $aem_fnln_enabled Whether Automatic Enhanced Match name is enabled. + * @type bool $aem_ph_enabled Whether Automatic Enhanced Match phone is enabled. + * @type bool $aem_ge_enabled Whether Automatic Enhanced Match gender is enabled. + * @type bool $aem_db_enabled Whether Automatic Enhanced Match birthdate is enabled. + * @type bool $aem_loc_enabled Whether Automatic Enhanced Match location is enabled. + * } + * } + * @throws ApiException Throws 500 exception in case of unexpected error. + */ + public static function get_advertiser_tag( $ad_account_id, $conversion_tag_id ) { + return self::make_request( "ad_accounts/{$ad_account_id}/conversion_tags/{$conversion_tag_id}", 'GET' ); + } + + /** + * Create a tag for the given advertiser. + * + * @link https://developers.pinterest.com/docs/api/v5/#operation/conversion_tags/create + * + * @since x.x.x + * + * @param string $ad_account_id the advertiser_id to create a tag for. + * + * @return array { + * Tag object. + * + * @type string $ad_account_id Ad account ID. + * @type string $code_snippet Tag code snippet. + * @type ?string $enhanced_match_status The enhanced match status of the tag. + * @type string $id Tag ID. + * @type ?int $last_fired_time_ms Time for the last event fired. + * @type string $name Conversion tag name. + * @type string $status Entity status. + * @type string $version Version number. + * @type array $configs { + * Tag configuration. + * + * @type ?bool $aem_enabled Whether Automatic Enhanced Match email is enabled. + * @type ?int $md_frequency Metadata ingestion frequency. + * @type ?bool $aem_fnln_enabled Whether Automatic Enhanced Match name is enabled. + * @type ?bool $aem_ph_enabled Whether Automatic Enhanced Match phone is enabled. + * @type ?bool $aem_ge_enabled Whether Automatic Enhanced Match gender is enabled. + * @type ?bool $aem_db_enabled Whether Automatic Enhanced Match birthdate is enabled. + * @type ?bool $aem_loc_enabled Whether Automatic Enhanced Match location is enabled. + * } + * } + * @throws ApiException|Exception Throws 500 exception. + */ + public static function create_tag( $ad_account_id ) { + $tag_name = self::get_tag_name(); + return self::make_request( + "ad_accounts/{$ad_account_id}/conversion_tags", + 'POST', + array( + 'name' => $tag_name, + 'aem_enabled' => true, + 'md_frequency' => 1, + 'aem_fnln_enabled' => true, + 'aem_ph_enabled' => true, + 'aem_ge_enabled' => true, + 'aem_db_enabled' => true, + 'ae_loc_enabled' => true, + ) + ); + } + + /** + * Returns Pinterest user verification code for website verification. + * + * @since x.x.x + * + * @return array { + * Data needed to verify a website. + * + * @type string $verification_code Code to check against the user claiming the website. + * @type string $dns_txt_record DNS TXT record to check against for the website to be claimed. + * @type string $metatag META tag the verification process searches for the website to be claimed. + * @type string $filename File expected to find on the website being claimed. + * @type string $file_content A full html file to upload to the website in order for it to be claimed. + * } + * @throws PinterestApiException If the request fails with 403 or 500 status. + */ + public static function domain_verification_data(): array { + return self::make_request( 'user_account/websites/verification', 'GET' ); + } + + /** + * Sends domain verification request to Pinterest. + * + * @since x.x.x + * + * @param string $domain Domain to verify. + * @return array { + * Data returned by Pinterest after the verification request. + * + * @type string $website Website with path or domain only. + * @type string $status Status of the verification process. + * @type string $verified_at UTC timestamp when the verification happened - sometimes missing. + * } + * @throws PinterestApiException If the request fails with 500 status. + */ + public static function domain_metatag_verification_request( string $domain ): array { + return self::make_request( + 'user_account/websites', + 'POST', + array( + 'website' => $domain, + 'verification_method' => 'METATAG', + ) + ); + } } diff --git a/src/API/AdvertiserConnect.php b/src/API/AdvertiserConnect.php index dbcc024c1..795453bcf 100644 --- a/src/API/AdvertiserConnect.php +++ b/src/API/AdvertiserConnect.php @@ -11,6 +11,9 @@ use Automattic\WooCommerce\Pinterest\AdCredits; use Automattic\WooCommerce\Pinterest\Billing; use Automattic\WooCommerce\Pinterest\Utilities\Utilities; +use Exception; +use Pinterest_For_Woocommerce; +use Throwable; use \WP_REST_Server; use \WP_REST_Request; use \WP_Error; @@ -44,7 +47,7 @@ public function __construct() { * * @return array|WP_Error * - * @throws \Exception PHP Exception. + * @throws Exception PHP Exception. */ public function connect_advertiser( WP_REST_Request $request ) { @@ -55,12 +58,16 @@ public function connect_advertiser( WP_REST_Request $request ) { $enable_aem = $request->has_param( 'enable_aem' ) ? $request->get_param( 'enable_aem' ) : false; if ( ! $advertiser_id || ! $tag_id ) { - throw new \Exception( esc_html__( 'Missing advertiser or tag parameters.', 'pinterest-for-woocommerce' ), 400 ); + throw new Exception( esc_html__( 'Missing advertiser or tag parameters.', 'pinterest-for-woocommerce' ), 400 ); } - if ( $enable_aem ) { - self::enable_aem_tag( $tag_id ); - } + /* + * @NOTE: Automatic Enhanced Match is enabled by default (at least Pinterest said so). + * if ( $enable_aem ) { + * @TODO: We do not have an API v5 analog for this call. Commenting it out temporarily. + * self::enable_aem_tag( $tag_id ); + * } + */ $is_connected = Pinterest_For_Woocommerce()::get_data( 'is_advertiser_connected' ); @@ -72,10 +79,10 @@ public function connect_advertiser( WP_REST_Request $request ) { ); } - // Connect new advertiser and tag. + // Update integration with new advertiser and a tag. return self::connect_advertiser_and_tag( $advertiser_id, $tag_id ); - } catch ( \Throwable $th ) { + } catch ( Throwable $th ) { /* Translators: The error description as returned from the API */ $error_message = sprintf( esc_html__( 'Could not connect advertiser with Pinterest. [%s]', 'pinterest-for-woocommerce' ), $th->getMessage() ); @@ -91,24 +98,34 @@ public function connect_advertiser( WP_REST_Request $request ) { * @param string $advertiser_id The ID of the advertiser. * @param string $tag_id The ID of the tag. * - * @throws \Exception PHP Exception. + * @return array { + * Updates Pinterest integration with the new advertiser and tag. + * + * @type string $connected The ID of the connected advertiser. + * @type bool $reconnected Whether the advertiser was reconnected. + * } + * @throws Exception PHP Exception. */ - public static function connect_advertiser_and_tag( $advertiser_id, $tag_id ) { + public static function connect_advertiser_and_tag( string $advertiser_id, string $tag_id ): array { - $response = Base::connect_advertiser( $advertiser_id, $tag_id ); + $integration_data = Pinterest_For_Woocommerce::get_data( 'integration_data' ); + $external_business_id = $integration_data['external_business_id'] ?? ''; - if ( 'success' !== $response['status'] ) { - throw new \Exception( esc_html__( 'The advertiser could not be connected to Pinterest.', 'pinterest-for-woocommerce' ), 400 ); - } + $data = array( + 'connected_advertiser_id' => $advertiser_id, + 'connected_tag_id' => $tag_id, + ); - if ( $advertiser_id !== $response['data']->advertiser_id ) { - throw new \Exception( esc_html__( 'Incorrect advertiser ID.', 'pinterest-for-woocommerce' ), 400 ); + try { + $response = Pinterest_For_Woocommerce::update_commerce_integration( $external_business_id, $data ); + } catch ( Throwable $th ) { + throw new Exception( $th->getMessage(), 400 ); } - Pinterest_For_Woocommerce()::save_data( 'is_advertiser_connected', true ); + Pinterest_For_Woocommerce::save_data( 'is_advertiser_connected', true ); // At this stage we can check if the connected advertiser has billing setup. - $has_billing = Pinterest_For_Woocommerce()::add_billing_setup_info_to_account_data(); + $has_billing = Pinterest_For_Woocommerce::add_billing_setup_info_to_account_data(); /* * If the advertiser does not have a correct billing lets check for the setup frequently for the next hour. @@ -130,7 +147,7 @@ public static function connect_advertiser_and_tag( $advertiser_id, $tag_id ) { UserInteraction::flush_options(); return array( - 'connected' => $response['data']->advertiser_id, + 'connected' => $response['connected_advertiser_id'], 'reconnected' => true, ); } diff --git a/src/API/Advertisers.php b/src/API/Advertisers.php index 05d04782e..c6e593a42 100644 --- a/src/API/Advertisers.php +++ b/src/API/Advertisers.php @@ -8,6 +8,9 @@ namespace Automattic\WooCommerce\Pinterest\API; +use Exception; +use Throwable; +use WP_Error; use \WP_REST_Server; use \WP_REST_Request; @@ -40,38 +43,37 @@ public function __construct() { * * @return array|WP_Error * - * @throws \Exception PHP Exception. + * @throws Exception PHP Exception. */ public function get_advertisers( WP_REST_Request $request ) { - try { - - $advertisers = Base::get_advertisers(); - - $terms_agreed = $request->has_param( 'terms_agreed' ) ? (int) $request->get_param( 'terms_agreed' ) : false; - - if ( 'success' !== $advertisers['status'] && 1000 === $advertisers['code'] ) { - // User needs to take manual action in Pinterest dashboard. - throw new \Exception( esc_html__( 'No advertiser exists.', 'pinterest-for-woocommerce' ), 1000 ); - } - - if ( 'success' !== $advertisers['status'] ) { - throw new \Exception( esc_html__( 'Response error', 'pinterest-for-woocommerce' ), 400 ); - } - - if ( empty( $advertisers['data'] ) && ! empty( $terms_agreed ) ) { - $advertiser = Base::create_advertiser( $terms_agreed ); - $advertisers['data'] = 'success' === $advertiser['status'] ? array( $advertiser['data'] ) : array(); - } - - return array( 'advertisers' => $advertisers['data'] ); - - } catch ( \Throwable $th ) { - + $advertisers = APIV5::get_advertisers(); + + /* + * With the new Pinterest on-boarding popup we do not need to create and advertiser. We will always have it. + * + * $terms_agreed = $request->has_param( 'terms_agreed' ) ? (int) $request->get_param( 'terms_agreed' ) : false; + * if ( empty( $advertisers['items'] ) && ! empty( $terms_agreed ) ) { + * $advertiser = Base::create_advertiser( $terms_agreed ); + * $advertisers['data'] = 'success' === $advertiser['status'] ? array( $advertiser['data'] ) : array(); + * } + */ + + return array( + 'advertisers' => array_map( + function ( $item ) { + return array( + 'id' => $item['id'], + 'name' => $item['name'], + ); + }, + $advertisers['items'] + ), + ); + } catch ( Throwable $th ) { /* Translators: The error description as returned from the API */ $error_message = sprintf( esc_html__( 'Could not fetch advertisers for Pinterest account ID. [%s]', 'pinterest-for-woocommerce' ), $th->getMessage() ); - - return new \WP_Error( \PINTEREST_FOR_WOOCOMMERCE_PREFIX . '_advertisers_error', $error_message, array( 'status' => $th->getCode() ) ); + return new WP_Error( \PINTEREST_FOR_WOOCOMMERCE_PREFIX . '_advertisers_error', $error_message, array( 'status' => $th->getCode() ) ); } } } diff --git a/src/API/Auth.php b/src/API/Auth.php index 6c914f12c..6e030a8b3 100644 --- a/src/API/Auth.php +++ b/src/API/Auth.php @@ -10,7 +10,8 @@ use Automattic\WooCommerce\Pinterest\Logger as Logger; use Throwable; -use \WP_REST_Request; +use WP_HTTP_Response; +use WP_REST_Request; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -122,6 +123,7 @@ public function connect_callback( WP_REST_Request $request ) { $this->log_error_and_redirect( $request, $error ); } + wp_safe_redirect( $this->get_redirect_url( $request->get_param( 'view' ) ) ); exit; } @@ -158,11 +160,11 @@ private function get_redirect_url( $view = null, $has_error = false ) { $query_args['view'] = sanitize_key( $view ); } - return esc_url( - add_query_arg( - $query_args, - admin_url( 'admin.php' ) - ) + // phpcs:ignore Squiz.Commenting.InlineComment.InvalidEndChar + // nosemgrep: audit.php.wp.security.xss.query-arg + return add_query_arg( + $query_args, + admin_url( 'admin.php' ) ); } } diff --git a/src/API/AuthDisconnect.php b/src/API/AuthDisconnect.php index 1eaa97196..c6b5edfad 100644 --- a/src/API/AuthDisconnect.php +++ b/src/API/AuthDisconnect.php @@ -8,7 +8,8 @@ namespace Automattic\WooCommerce\Pinterest\API; -use \WP_REST_Request; +use Pinterest_For_Woocommerce; +use WP_Error; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -41,7 +42,7 @@ public function __construct() { */ public function handle_disconnect() { return array( - 'disconnected' => Pinterest_For_Woocommerce()::disconnect(), + 'disconnected' => Pinterest_For_Woocommerce::disconnect(), ); } } diff --git a/src/API/Base.php b/src/API/Base.php index db64f591d..e3ad18e75 100644 --- a/src/API/Base.php +++ b/src/API/Base.php @@ -11,6 +11,7 @@ use Automattic\WooCommerce\Pinterest as Pinterest; use Automattic\WooCommerce\Pinterest\Logger as Logger; +use Automattic\WooCommerce\Pinterest\PinterestApiException; use Automattic\WooCommerce\Pinterest\PinterestApiException as ApiException; use \Exception; @@ -77,8 +78,8 @@ public static function instance() { * * @return array * - * @throws ApiException PHP exception. - * @throws Exception PHP exception. + * @throws PinterestApiException Pinterest API exception in case of API error in response. + * @throws Exception PHP exception. */ public static function make_request( $endpoint, $method = 'POST', $payload = array(), $api = '', $cache_expiry = false ) { @@ -92,8 +93,8 @@ public static function make_request( $endpoint, $method = 'POST', $payload = arr try { - $request = self::prepare_request( $endpoint, $method, $payload, $api ); - $response = self::handle_request( $request ); + $request = static::prepare_request( $endpoint, $method, $payload, $api ); + $response = static::handle_request( $request ); if ( ! empty( $cache_expiry ) ) { $cache_key = self::get_cache_key( $endpoint, $method, $payload, $api ); @@ -163,7 +164,7 @@ public static function prepare_request( $endpoint, $method = 'POST', $payload = $api_version = 'ads/' === $api ? self::API_ADS_VERSION : self::API_VERSION; $request = array( - 'url' => self::API_DOMAIN . "/{$api}v{$api_version}/{$endpoint}", + 'url' => static::API_DOMAIN . "/{$api}v{$api_version}/{$endpoint}", 'method' => $method, 'args' => $payload, 'headers' => array( @@ -248,7 +249,7 @@ public static function invalidate_cached_response( $endpoint, $method, $payload, * @throws Exception PHP exception. * @throws ApiException PHP exception. */ - public static function handle_request( $request ) { + protected static function handle_request( $request ) { $request = wp_parse_args( $request, @@ -260,14 +261,9 @@ public static function handle_request( $request ) { ) ); - $body = ''; - try { - - self::get_token(); - if ( $request['auth_header'] ) { - $request['headers']['Authorization'] = 'Bearer ' . self::$token['access_token']; + $request['headers']['Authorization'] = 'Bearer ' . self::get_token()['access_token']; } $request_args = array( @@ -360,7 +356,7 @@ protected static function parse_response( $response ) { throw new Exception( __( 'Empty body', 'pinterest-for-woocommerce' ), 204 ); } - return (array) json_decode( $response['body'] ); + return json_decode( $response['body'], true ); } @@ -529,7 +525,7 @@ public static function get_advertiser_tag( $advertiser_id, $tag_id ) { */ public static function create_tag( $advertiser_id ) { - $tag_name = apply_filters( 'pinterest_for_woocommerce_default_tag_name', esc_html__( 'Auto-created by Pinterest for WooCommerce', 'pinterest-for-woocommerce' ) ); + $tag_name = static::get_tag_name(); return self::make_request( "advertisers/{$advertiser_id}/conversion_tags", @@ -626,7 +622,7 @@ public static function update_or_create_merchant( $args ) { public static function update_merchant_feed( $merchant_id, $feed_id, $args ) { return self::make_request( - esc_url( add_query_arg( $args, 'commerce/product_pin_merchants/' . $merchant_id . '/feed/' . $feed_id . '/' ) ), + add_query_arg( $args, 'commerce/product_pin_merchants/' . $merchant_id . '/feed/' . $feed_id . '/' ), 'PUT' ); } @@ -819,4 +815,25 @@ public static function get_list_of_ads_supported_countries() { $request_url = 'advertisers/countries'; return self::make_request( $request_url, 'GET', array(), 'ads', DAY_IN_SECONDS ); } + + /** + * Generates a tag name. + * + * @since x.x.x + * + * @return string The tag name. + */ + protected static function get_tag_name(): string { + /** + * Filters the default tag name. + * + * @since Unknown + * + * @param string $tag_name The default tag name. + */ + return apply_filters( + 'pinterest_for_woocommerce_default_tag_name', + esc_html__( 'Auto-created by Pinterest for WooCommerce', 'pinterest-for-woocommerce' ) + ); + } } diff --git a/src/API/DomainVerification.php b/src/API/DomainVerification.php index 258a458f1..2613bad84 100644 --- a/src/API/DomainVerification.php +++ b/src/API/DomainVerification.php @@ -10,8 +10,11 @@ use Automattic\WooCommerce\Pinterest\Logger as Logger; -use \WP_REST_Server; -use \WP_REST_Request; +use Automattic\WooCommerce\Pinterest\PinterestApiException; +use Exception; +use Pinterest_For_Woocommerce; +use WP_Error; +use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -35,7 +38,7 @@ class DomainVerification extends VendorAPI { public function __construct() { $this->base = 'domain_verification'; - $this->endpoint_callback = 'handle_verification'; + $this->endpoint_callback = 'maybe_handle_verification'; $this->methods = WP_REST_Server::EDITABLE; $this->register_routes(); @@ -44,14 +47,40 @@ public function __construct() { /** * Handle domain verification by triggering the realtime verification process - * using the Pinterst API. + * using the Pinterest API. + * + * @since x.x.x * * @return mixed * - * @throws \Exception PHP Exception. + * @throws Exception PHP Exception. */ - public function handle_verification() { - return self::trigger_domain_verification(); + public function maybe_handle_verification() { + try { + $result = array(); + $account_data = Pinterest_For_Woocommerce()::get_setting( 'account_data', true ); + if ( ! Pinterest_For_Woocommerce::is_domain_verified() ) { + $domain_verification_data = APIV5::domain_verification_data(); + Pinterest_For_Woocommerce()::save_data( 'verification_data', $domain_verification_data ); + $parsed_website = wp_parse_url( get_home_url() ); + $result = APIV5::domain_metatag_verification_request( $parsed_website['host'] . $parsed_website['path'] ); + if ( in_array( $result['status'], array( 'success', 'already_verified_by_user' ) ) ) { + $account_data['verified_user_websites'][] = $result['website']; + $account_data['is_any_website_verified'] = 0 < count( $account_data['verified_user_websites'] ); + Pinterest_For_Woocommerce()::save_setting( 'account_data', $account_data ); + } + } + return array_merge( $result, array( 'account_data' => $account_data ) ); + } catch ( PinterestApiException $th ) { + return new WP_Error( + 'pinterest-for-woocommerce_verification_error', + $th->getMessage(), + array( + 'status' => $th->getCode(), + 'pinterest_code' => method_exists( $th, 'get_pinterest_code' ) ? $th->get_pinterest_code() : 0, + ) + ); + } } diff --git a/src/API/HealthCheck.php b/src/API/Health.php similarity index 94% rename from src/API/HealthCheck.php rename to src/API/Health.php index 423d077bc..5fc4e446d 100644 --- a/src/API/HealthCheck.php +++ b/src/API/Health.php @@ -9,7 +9,7 @@ namespace Automattic\WooCommerce\Pinterest\API; use Automattic\WooCommerce\Pinterest as Pinterest; -use \WP_REST_Server; +use WP_REST_Server; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -18,7 +18,7 @@ /** * Endpoint used to check the Health status of the connected Merchant object. */ -class HealthCheck extends VendorAPI { +class Health extends VendorAPI { /** * Initialize class @@ -41,6 +41,10 @@ public function __construct() { */ public function health_check() { + return array( + 'status' => 'approved', + ); + try { $response = array(); @@ -73,7 +77,7 @@ public function health_check() { return $response; - } catch ( \Throwable $th ) { + } catch ( Throwable $th ) { /* Translators: The error description as returned from the API */ $error_message = sprintf( __( 'Could not fetch account status. [%s]', 'pinterest-for-woocommerce' ), $th->getMessage() ); diff --git a/src/API/Options.php b/src/API/Settings.php similarity index 80% rename from src/API/Options.php rename to src/API/Settings.php index 117f6f99b..b2883930a 100644 --- a/src/API/Options.php +++ b/src/API/Settings.php @@ -8,6 +8,7 @@ namespace Automattic\WooCommerce\Pinterest\API; +use Pinterest_For_Woocommerce; use \WP_Error; use \WP_REST_Server; use \WP_REST_Request; @@ -17,9 +18,9 @@ } /** - * Endpoint handling Options. + * Endpoint handling settings updates. */ -class Options extends VendorAPI { +class Settings extends VendorAPI { /** * Initialize class @@ -44,8 +45,13 @@ public function __construct() { */ public function get_settings() { Pinterest_For_Woocommerce()::maybe_check_billing_setup(); + $settings = Pinterest_For_Woocommerce()::get_settings( true ); + if ( empty( $settings['account_data']['id'] ) ) { + $integration_data = Pinterest_For_Woocommerce::get_data( 'integration_data' ); + $settings['account_data']['id'] = $integration_data['connected_user_id'] ?? ''; + } return array( - PINTEREST_FOR_WOOCOMMERCE_OPTION_NAME => Pinterest_For_Woocommerce()::get_settings( true ), + PINTEREST_FOR_WOOCOMMERCE_OPTION_NAME => $settings, ); } diff --git a/src/API/Tags.php b/src/API/Tags.php index 3e1ddba34..7c10260e8 100644 --- a/src/API/Tags.php +++ b/src/API/Tags.php @@ -8,6 +8,8 @@ namespace Automattic\WooCommerce\Pinterest\API; +use Exception; +use Throwable; use \WP_REST_Server; use \WP_REST_Request; @@ -42,44 +44,41 @@ public function __construct() { * @throws \Exception PHP Exception. */ public function get_tags( WP_REST_Request $request ) { - try { - - $tags = array(); - $advertiser_id = $request->get_param( 'advrtsr_id' ); - - if ( ! $advertiser_id ) { - throw new \Exception( esc_html__( 'Advertiser missing', 'pinterest-for-woocommerce' ), 400 ); + $ad_account_id = $request->get_param( 'advrtsr_id' ); + if ( ! $ad_account_id ) { + throw new Exception( esc_html__( 'Advertiser missing', 'pinterest-for-woocommerce' ), 400 ); } - $response = Base::get_advertiser_tags( $advertiser_id ); - - if ( 'success' !== $response['status'] ) { - throw new \Exception( esc_html__( 'Response error', 'pinterest-for-woocommerce' ), 400 ); + try { + $tags = APIV5::get_advertiser_tags( $ad_account_id ); + } catch ( Throwable $th ) { + throw new Exception( esc_html__( 'Response error', 'pinterest-for-woocommerce' ), 400 ); } - $tags = (array) $response['data']; - + $tags = $tags['items'] ?? array(); if ( empty( $tags ) ) { - // No tag created yet. Lets create one. - $tag = Base::create_tag( $advertiser_id ); - - if ( 'success' === $tag['status'] ) { - $tags[ $tag['data']->id ] = $tag['data']; - } else { - throw new \Exception( esc_html__( 'Could not create a tag. Please check the logs for additional information.', 'pinterest-for-woocommerce' ), 400 ); + try { + $tag = APIV5::create_tag( $ad_account_id ); + $tags = [ $tag ]; + } catch ( Throwable $th ) { + throw new Exception( esc_html__( 'Could not create a tag. Please check the logs for additional information.', 'pinterest-for-woocommerce' ), 400 ); } } - return $tags; - - } catch ( \Throwable $th ) { - + return array_map( + function( $tag ) { + return array( + 'id' => $tag['id'], + 'name' => $tag['name'], + ); + }, + $tags + ); + } catch ( Throwable $th ) { /* Translators: The error description as returned from the API */ $error_message = sprintf( esc_html__( 'No tracking tag available. [%s]', 'pinterest-for-woocommerce' ), $th->getMessage() ); - return new \WP_Error( \PINTEREST_FOR_WOOCOMMERCE_PREFIX . '_tags_error', $error_message, array( 'status' => $th->getCode() ) ); - } } } diff --git a/src/Logger.php b/src/Logger.php index d5536e70c..8a60a0ec5 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -8,6 +8,8 @@ namespace Automattic\WooCommerce\Pinterest; +use WP_Error; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -86,8 +88,8 @@ public static function log_request( $url, $args, $level = 'debug' ) { /** * Helper for Logging API responses. * - * @param array|\WP_Error $response The body of the response. - * @param string $level The default level/context of the message to be logged. + * @param array|WP_Error $response The body of the response. + * @param string $level The default level/context of the message to be logged. * * @return void */ @@ -96,7 +98,16 @@ public static function log_response( $response, $level = 'debug' ) { $level = 'error'; $data = $response->get_error_code() . ': ' . $response->get_error_message(); } else { - $data = $response['http_response']->get_response_object()->raw; + // Collecting response data. + $status = wp_remote_retrieve_response_code( $response ); + $message = wp_remote_retrieve_response_message( $response ); + $body = wp_remote_retrieve_body( $response ); + $headers = wp_remote_retrieve_headers( $response ); + if ( is_object( $headers ) ) { + $headers = $headers->getAll(); + } + + $data = 'Status: ' . $status . ' ' . $message . "\n\n" . 'Headers: ' . wp_json_encode( $headers ) . "\n\n" . 'Body: ' . $body; } self::log( 'Response: ' . "\n\n" . $data . "\n", $level ); diff --git a/src/PinterestSyncSettings.php b/src/PinterestSyncSettings.php index a3c38925a..6553d93c2 100644 --- a/src/PinterestSyncSettings.php +++ b/src/PinterestSyncSettings.php @@ -8,9 +8,8 @@ namespace Automattic\WooCommerce\Pinterest; -use \Exception; -use Automattic\WooCommerce\Pinterest\API\Base; -use Automattic\WooCommerce\Pinterest\Logger; +use Automattic\WooCommerce\Pinterest\API\APIV5; +use Exception; use DateTime; if ( ! defined( 'ABSPATH' ) ) { @@ -56,7 +55,6 @@ public static function sync_settings() { } $formatted_synced_time = new DateTime(); - $formatted_synced_time = $formatted_synced_time->format( 'j M Y, h:i:s a' ); Pinterest_For_Woocommerce()::save_setting( 'last_synced_settings', $formatted_synced_time ); @@ -97,9 +95,7 @@ private static function sync_setting( $setting ) { * @throws Exception PHP Exception. */ private static function automatic_enhanced_match_support() { - try { - $advertiser_id = Pinterest_For_WooCommerce()::get_setting( 'tracking_advertiser' ); $tag_id = Pinterest_For_WooCommerce()::get_setting( 'tracking_tag' ); @@ -107,22 +103,14 @@ private static function automatic_enhanced_match_support() { throw new Exception( esc_html__( 'Tracking advertiser or tag missing', 'pinterest-for-woocommerce' ), 400 ); } - $response = Base::get_advertiser_tag( $advertiser_id, $tag_id ); - - if ( 'success' !== $response['status'] ) { - throw new Exception( esc_html__( 'Response error', 'pinterest-for-woocommerce' ), 400 ); - } - - $automatic_enhanced_match_support = $response['data']->configs->aem_enabled; - + $response = APIV5::get_advertiser_tag( $advertiser_id, $tag_id ); + $automatic_enhanced_match_support = $response['configs']['aem_enabled'] ?? false; Pinterest_For_Woocommerce()::save_setting( 'automatic_enhanced_match_support', $automatic_enhanced_match_support ); - } catch ( Exception $th ) { - Logger::log( $th->getMessage(), 'error' ); + throw new Exception( esc_html__( 'Response error', 'pinterest-for-woocommerce' ), 400 ); } return Pinterest_For_Woocommerce()::get_setting( 'automatic_enhanced_match_support' ); } - } diff --git a/tests/E2e/PinterestConnectE2eTest.php b/tests/E2e/PinterestConnectE2eTest.php new file mode 100644 index 000000000..fbaa59832 --- /dev/null +++ b/tests/E2e/PinterestConnectE2eTest.php @@ -0,0 +1,229 @@ + 'def50200fd71a25bca8b0732a0449818bb58774bc022a75b6a954d233aa8fc06f31fbed01e816cc6d2518a24ecd1018878d1568341b5412a52b33ef4b3627b296d76a6fdd53a9541dfe57bc8a304a837779b78c42b44516c473948941c928d68c9277db225931e3bfd87bfd36212d49018650d167c4fffc1600b27d76bd5debb6733aef64ff4c1de40740f10417f9c98145a12d994bad15b750cf0dbb48ac2ce78e8cec610707e3a087df58c90445ed4fb5acafbb6c60a07f4', + 'refresh_token' => 'def50200c637373b990d843d5be0b37415610a077359085993d42b7ef032ae368ab2a28e02e90438b5a34fcd92f62d938f65a17de11574743126cddefb30a662440881fe9197911940fddea93b3e0d32e34e4bd348bb74586d4980d304dc6a4b9cc577bc4533f32df239fb91044f4c17dd72842d2900a6aea8518939ddebc4073d11f2c61b92430272451c71a878e70b651dce2214657bd0b333c9517b2694168eb8431fa6c7d82d15720ffea853ea052382cbbcf2f4d2826851dcae7e1c3295433bd9cd586a21a6642ebbb6d5158f8c2a5f8c4f62c8b524ffbb5f13a90df9078feaf1ddda4627b1398eab80a3b57efac3de38cbafd162496f7908dd0cdc48752c4ef26848788fec7e7e48adacadf3144894ea6a1166dacda3649885709aacf049de326bef0f366dd776908b95024a1ef6c0b320ef091a104a127e1b2a5c7600e5b07af918ded1705989431e1ad84fac85c4da4ffc5f29cb3c6753b51eebf3b577bf73422c6d5750fcbb25b83e593c9e5ae03ddbbc5a59214b9e4ec89a93cd7e621a44889dd5e68e03e755b40bb9b1402ad3d327b16ebcc3f92e487929fdb22d9ed5a3a28400f04d17f492592488651c71edb5b93a7c87ee0043ad21a96989dd66cde55dd6ebbd0ba6e23040e809674cdb71bdb68d95183925e2702ab3188815bdddd49a4d76403f3190a8b2011a1e6440e2032589ff6f7d060ec20f6b0c651d77e9c9927cee4a20e4423f3a622b7134fe770f7cd9231569eb252e4f4a252f2e973d5a1e7feb8171fea9d00f62257937885ff193dd28e2fc1b3fbd9da3764a5c06e4ef51654d59fd8e4bb3155d443a4995c83c997c8965587322207957149636b0c1eeccd3d1f5e427ed8896cca3c5', + 'token_type' => 'bearer', + 'expires_in' => 2592000, + 'refresh_token_expires_in' => 31536000, + 'scope' => 'ads:read ads:write catalogs:read catalogs:write pins:read pins:write user_accounts:read user_accounts:write', + 'refresh_date' => 1685694065, + ); + Pinterest_For_Woocommerce()::save_token_data( $token_data ); + $info_data = array ( + 'advertiser_id' => '549765662491', + 'tag_id' => '2613286171854', + 'merchant_id' => '1479839719476', + 'clientHash' => 'MTQ4NjE3MzpkNWJjNTM4ZmVhMTZhYzIwMmZiNDZhMTFjMGNkZGVmNzFhOWU1YWY0', + ); + Pinterest_For_Woocommerce()::save_connection_info_data( $info_data ); + + // API Request filters to stub Pinterest API requests. + $this->create_commerce_integration_request_stub(); + $this->get_account_info_request_stub(); + $this->get_user_websites_request_stub(); + $this->get_linked_businesses_request_stub(); + + add_action( 'pinterest_for_woocommerce_token_saved', array( Pinterest_For_Woocommerce::class, 'set_default_settings' ) ); + add_action( 'pinterest_for_woocommerce_token_saved', array( Pinterest_For_Woocommerce::class, 'create_commerce_integration' ) ); + add_action( 'pinterest_for_woocommerce_token_saved', array( Pinterest_For_Woocommerce::class, 'update_account_data' ) ); + add_action( 'pinterest_for_woocommerce_token_saved', array( Pinterest_For_Woocommerce::class, 'update_linked_businesses' ) ); + + do_action( 'pinterest_for_woocommerce_token_saved' ); + + $settings = Pinterest_For_Woocommerce::get_settings( true ); + + $this->assertEquals( + array( + 'account_data' => array( + 'id' => '6491114367052267731', + 'username' => 'dmytromaksiuta1', + 'full_name' => '', + 'image_medium_url' => 'https://i.pinimg.com/600x600_R/42/f5/36/42f5364f737aff4749a8e9046510828f.jpg', + 'is_any_website_verified' => true, + 'is_billing_setup' => false, + 'is_partner' => true, + 'coupon_redeem_info' => array( + 'redeem_status' => false, + ), + 'verified_user_websites' => array( + 'wordpress.dima.works', + 'pinterest.dima.works' + ), + ), + 'track_conversions' => true, + 'enhanced_match_support' => true, + 'automatic_enhanced_match_support' => true, + 'save_to_pinterest' => true, + 'rich_pins_on_posts' => true, + 'rich_pins_on_products' => true, + 'product_sync_enabled' => true, + 'enable_debug_logging' => false, + 'erase_plugin_data' => false, + 'tracking_advertiser' => '549765662491', + 'tracking_tag' => '2613286171854', + ), + $settings + ); + } + + private function create_commerce_integration_request_stub() { + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + if ( 'https://api.pinterest.com/v5/integrations/commerce' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array ( + 'id' => '6491114367052267731', + 'external_business_id' => 'wordpresspinterest-6479a6713160b', + 'connected_merchant_id' => '1479839719476', + 'connected_user_id' => '1144266355231574943', + 'connected_advertiser_id' => '549765662491', + 'connected_tag_id' => '2613286171854', + 'connected_lba_id' => '', + 'partner_access_token_expiry' => 0, + 'partner_refresh_token_expiry' => 0, + 'scopes' => '', + 'created_timestamp' => 1685694065000, + 'updated_timestamp' => 1685694065000, + 'additional_id_1' => '', + 'partner_metadata' => '', + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + + return $response; + }, + 10, + 3 + ); + } + + private function get_account_info_request_stub() { + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + if ( 'https://api.pinterest.com/v5/user_account' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'username' => 'dmytromaksiuta1', + 'profile_image' => 'https://i.pinimg.com/600x600_R/42/f5/36/42f5364f737aff4749a8e9046510828f.jpg', + 'account_type' => 'BUSINESS', + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + + return $response; + }, + 10, + 3 + ); + } + + private function get_user_websites_request_stub() { + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + if ( 'https://api.pinterest.com/v5/user_account/websites' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'items' => array( + array( + 'status' => 'verified', + 'website' => 'wordpress.dima.works', + ), + array( + 'status' => 'verified', + 'website' => 'pinterest.dima.works', + ) + ), + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + + return $response; + }, + 10, + 3 + ); + } + + private function get_linked_businesses_request_stub() { + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + if ( 'https://api.pinterest.com/v5/user_account/businesses' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + array( + 'username' => 'dmytromaksiuta1', + 'image_small_url' => 'https://www.example.com/dj23454f53dfk2324.jpg', + 'image_medium_url' => 'https://www.example.com/dj23454f53dfk2324.jpg', + 'image_large_url' => 'https://www.example.com/dj23454f53dfk2324.jpg', + 'image_xlarge_url' => 'https://www.example.com/dj23454f53dfk2324.jpg' + ), + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + + return $response; + }, + 10, + 3 + ); + } +} diff --git a/tests/Unit/Api/APIV5Test.php b/tests/Unit/Api/APIV5Test.php new file mode 100644 index 000000000..d14b241b2 --- /dev/null +++ b/tests/Unit/Api/APIV5Test.php @@ -0,0 +1,117 @@ + array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'ad_account_id' => 'aai-1234567890', + 'code_snippet' => '', + 'id' => '9876543210123456789', + 'last_fired_time_ms' => 123456789, + 'name' => 'Some tag name 42', + 'status' => 'ACTIVE', + 'version' => 'v1', + 'configs' => array( + 'aem_enabled' => true, + 'md_frequency' => 1, + 'aem_fnln_enabled' => true, + 'aem_ph_enabled' => true, + 'aem_ge_enabled' => true, + 'aem_db_enabled' => true, + 'ae_loc_enabled' => true, + ), + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $output = APIV5::create_tag( 'aai-1234567890' ); + + $this->assertEquals( + 'Some tag name 42', + $output['name'] + ); + + // Check if enhanced match is enabled. + $this->assertEquals( + array( + 'aem_enabled' => true, + 'md_frequency' => 1, + 'aem_fnln_enabled' => true, + 'aem_ph_enabled' => true, + 'aem_ge_enabled' => true, + 'aem_db_enabled' => true, + 'ae_loc_enabled' => true, + ), + $output['configs'] + ); + } + + public function test_create_tag_returns_unexpected_error() { + $this->expectException( PinterestApiException::class ); + $this->expectExceptionCode( 500 ); + $this->expectExceptionMessage( 'Any other message from Pinterest which falls under Unexpected error.' ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'code' => 0, + 'message' => 'Any other message from Pinterest which falls under Unexpected error.', + ) + ), + 'response' => array( + 'code' => 500, + 'message' => 'Unexpected error', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + APIV5::create_tag( 'aai-1234567890' ); + } +} diff --git a/tests/Unit/Api/AdvertiserConnectTest.php b/tests/Unit/Api/AdvertiserConnectTest.php new file mode 100644 index 000000000..b55ddf6e9 --- /dev/null +++ b/tests/Unit/Api/AdvertiserConnectTest.php @@ -0,0 +1,103 @@ + array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'id' => '987654321234567890', + 'external_business_id' => 'cbi-1234567890', + 'connected_merchant_id' => 'cmi-1234567890', + 'connected_user_id' => 'cui-1234567890', + 'connected_advertiser_id' => 'ai-1234567890', + 'connected_lba_id' => 'cli-1234567890', + 'connected_tag_id' => 'ti-1234567890', + 'partner_access_token_expiry' => 1621350033000, + 'partner_refresh_token_expiry' => 1621350033000, + 'scopes' => 's-c-o-p-e-s', + 'created_timestamp' => 1621350033000, + 'updated_timestamp' => 1621350033300, + 'additional_id_1' => 'ai1-1234567890', + 'partner_metadata' => 'partner-meta-data', + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $output = AdvertiserConnect::connect_advertiser_and_tag( + 'ai-1234567890', + 'ti-1234567890' + ); + + $this->assertEquals( + array( + 'connected' => 'ai-1234567890', + 'reconnected' => true, + ), + $output + ); + } + + public function test_connect_advertiser_and_tag_returns_error() { + $this->expectException( Exception::class ); + $this->expectExceptionCode( 400 ); + $this->expectExceptionMessage( 'Sorry! We could not find your integration.' ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'code' => 4180, + 'message' => 'Sorry! We could not find your integration.', + ) + ), + 'response' => array( + 'code' => 404, + 'message' => 'Not Found', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + AdvertiserConnect::connect_advertiser_and_tag( + 'ai-1234567890', + 'ti-1234567890' + ); + } +} diff --git a/tests/Unit/Api/AdvertisersTest.php b/tests/Unit/Api/AdvertisersTest.php new file mode 100644 index 000000000..32b1ef4a6 --- /dev/null +++ b/tests/Unit/Api/AdvertisersTest.php @@ -0,0 +1,126 @@ +get_routes(); + $this->assertArrayHasKey( '/pinterest/v1/tagowners', $routes ); + } + + /** + * Tests if advertisers endpoints rejects access. + * + * @return void + */ + public function test_advertisers_endpoint_rejects_access() { + // 1. No authentication. + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tagowners' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); + + // 2. No authorisation. + $user = $this->factory->user->create( array( 'role' => 'guest' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tagowners' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 403, $response->get_status() ); + } + + /** + * Tests advertisers endpoint returns the list of advertisers. + * + * @return void + */ + public function test_advertisers_endpoint_returns_the_list_of_advertisers() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + add_filter( + 'pre_http_request', + function ( $response, $args, $url ) { + if ( 'https://api.pinterest.com/v5/ad_accounts' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'items' => array( + array( + 'id' => '1234567890', + 'name' => 'Test Advertiser 0', + 'owner' => array( + 'username' => 'username_007', + ), + 'country' => 'US', + 'currency' => 'USD', + 'permissions' => 'some-permissions', + ), + array( + 'id' => '1234567891', + 'name' => 'Test Advertiser 1', + 'owner' => array( + 'username' => 'username_008', + ), + 'country' => 'US', + 'currency' => 'USD', + 'permissions' => 'some-permissions', + ), + ), + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + return $response; + }, + 10, + 3 + ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tagowners' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( + array( + 'advertisers' => array( + array( + 'id' => '1234567890', + 'name' => 'Test Advertiser 0', + ), + array( + 'id' => '1234567891', + 'name' => 'Test Advertiser 1', + ), + ), + ), + $response->get_data() + ); + } +} diff --git a/tests/Unit/Api/DomainVerificationTest.php b/tests/Unit/Api/DomainVerificationTest.php new file mode 100644 index 000000000..90981b087 --- /dev/null +++ b/tests/Unit/Api/DomainVerificationTest.php @@ -0,0 +1,160 @@ +get_routes(); + $this->assertArrayHasKey( '/pinterest/v1/domain_verification', $routes ); + } + + /** + * Tests if the domain verification endpoint rejects access. + * + * @return void + */ + public function test_domain_verification_endpoint_rejects_access() { + // 1. No authentication. + $request = new WP_REST_Request( 'POST', '/pinterest/v1/domain_verification' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); + + // 2. No authorisation. + $user = $this->factory->user->create( array( 'role' => 'guest' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'POST', '/pinterest/v1/domain_verification' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 403, $response->get_status() ); + } + + /** + * Tests if the domain verification endpoint returns domain verification status and saves appropriate settings. + * + * @return void + */ + public function test_domain_verification_endpoint_verifies_a_domain() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + // Partial mock of account_data data. + Pinterest_For_Woocommerce()::save_setting( + 'account_data', + array( + 'username' => 'dmytromaksiuta1', + 'id' => '1234567890123456789', + 'is_partner' => true, + 'is_billing_setup' => false, + 'verified_user_websites' => array(), + 'is_any_website_verified' => false, + ) + ); + + add_filter( + 'home_url', + function () { + return 'https://mysite.test/'; + } + ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + if ( 'https://api.pinterest.com/v5/user_account/websites/verification' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'verification_code' => 'e1edcc1a43976c646367e9c6c9a9b7b6', + 'dns_txt_record' => 'pinterest-site-verification=e1edcc1a43976c646367e9c6c9a9b7b6', + 'metatag' => '', + 'filename' => 'pinterest-e1edc.html', + 'file_content' => 'string', + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + if ( 'https://api.pinterest.com/v5/user_account/websites' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'website' => 'mysite.test', + 'status' => 'success', + 'verified_at' => '2022-12-14T21:03:01.602000' + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + return $response; + }, + 10, + 3 + ); + + $request = new WP_REST_Request( 'POST', '/pinterest/v1/domain_verification' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( + array( + 'website' => 'mysite.test', + 'status' => 'success', + 'verified_at' => '2022-12-14T21:03:01.602000', + 'account_data' => array( + 'username' => 'dmytromaksiuta1', + 'id' => '1234567890123456789', + 'is_partner' => true, + 'is_billing_setup' => false, + 'verified_user_websites' => array( 'mysite.test' ), + 'is_any_website_verified' => true, + ) + ), + $response->get_data() + ); + $this->assertEquals( + array( + 'username' => 'dmytromaksiuta1', + 'id' => '1234567890123456789', + 'is_partner' => true, + 'is_billing_setup' => false, + 'verified_user_websites' => array( 'mysite.test' ), + 'is_any_website_verified' => true, + ), + Pinterest_For_Woocommerce()::get_setting( 'account_data', true ) + ); + } +} diff --git a/tests/Unit/Api/HealthTest.php b/tests/Unit/Api/HealthTest.php new file mode 100644 index 000000000..a6fcddaad --- /dev/null +++ b/tests/Unit/Api/HealthTest.php @@ -0,0 +1,68 @@ +get_routes(); + $this->assertArrayHasKey( '/pinterest/v1/health', $routes ); + } + + /** + * Tests if the health endpoint rejects access. + * + * @return void + */ + public function test_tags_endpoint_rejects_access() { + // 1. No authentication. + $request = new WP_REST_Request( 'GET', '/pinterest/v1/health' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); + + // 2. No authorisation. + $user = $this->factory->user->create( array( 'role' => 'guest' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/health' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 403, $response->get_status() ); + } + + /** + * Tests if the health endpoint returns health status. + * + * @return void + */ + public function test_tags_endpoint_returns_advertiser_missing() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/health' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( + array( + 'status' => 'approved', + ), + $response->get_data() + ); + } +} diff --git a/tests/Unit/Api/SettingsTest.php b/tests/Unit/Api/SettingsTest.php new file mode 100644 index 000000000..2fe159d98 --- /dev/null +++ b/tests/Unit/Api/SettingsTest.php @@ -0,0 +1,258 @@ +get_routes(); + $this->assertArrayHasKey( '/pinterest/v1/settings', $routes ); + } + + /** + * Tests if the get/set settings endpoints reject access. + * + * @return void + */ + public function test_settings_endpoint_rejects_access() { + // 1. No authentication. + $request = new WP_REST_Request( 'GET', '/pinterest/v1/settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); + + $request = new WP_REST_Request( 'POST', '/pinterest/v1/settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); + + // 2. No authorisation. + $user = $this->factory->user->create( array( 'role' => 'guest' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 403, $response->get_status() ); + + $request = new WP_REST_Request( 'POST', '/pinterest/v1/settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 403, $response->get_status() ); + } + + /** + * Tests get settings endpoint returns settings stored inside wp_options. + * + * @return void + */ + public function test_get_settings_endpoint_returns_settings_stored_inside_options() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + Pinterest_For_Woocommerce::save_settings( + array ( + 'automatic_enhanced_match_support' => true, + 'last_synced_settings' => '31 May 2023, 03:52:53 pm', + 'track_conversions' => true, + 'enhanced_match_support' => true, + 'save_to_pinterest' => true, + 'rich_pins_on_posts' => true, + 'rich_pins_on_products' => true, + 'product_sync_enabled' => true, + 'enable_debug_logging' => true, + 'erase_plugin_data' => false, + 'ads_campaign_is_active' => true, + 'did_redirect_to_onboarding' => false, + 'account_data' => array ( + 'username' => 'dmytromaksiuta1', + 'full_name' => '', + 'id' => '8842280425079444297', + 'image_medium_url' => 'https://i.pinimg.com/600x600_R/42/f5/36/42f5364f737aff4749a8e9046510828f.jpg', + 'is_partner' => true, + 'verified_user_websites' => array ( 'pinterest.dima.works', 'wordpress.dima.works' ), + 'is_any_website_verified' => true, + 'is_billing_setup' => false, + 'coupon_redeem_info' => array ( + 'redeem_status' => false, + ), + 'available_discounts' => false, + ), + 'tracking_advertiser' => '549765662491', + 'tracking_tag' => '2613286171854', + ) + ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( + array( + 'pinterest_for_woocommerce' => Pinterest_For_Woocommerce::get_settings( true ), + ), + $response->get_data() + ); + } + + /** + * Tests set settings endpoint successfully sets settings. + * + * @return void + */ + public function test_set_settings_endpoint_successfully_sets_settings() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + Pinterest_For_Woocommerce::save_settings( array() ); + + $request = new WP_REST_Request( 'POST', '/pinterest/v1/settings' ); + $request->set_body_params( + array( + 'pinterest_for_woocommerce' => array( + 'automatic_enhanced_match_support' => true, + 'last_synced_settings' => '31 May 2023, 03:52:53 pm', + 'track_conversions' => true, + 'enhanced_match_support' => true, + 'save_to_pinterest' => true, + 'rich_pins_on_posts' => true, + 'rich_pins_on_products' => true, + 'product_sync_enabled' => true, + 'enable_debug_logging' => true, + 'erase_plugin_data' => false, + 'ads_campaign_is_active' => true, + 'did_redirect_to_onboarding' => false, + 'account_data' => array( + 'username' => 'dmytromaksiuta1', + 'full_name' => '', + 'id' => '8842280425079444297', + 'image_medium_url' => 'https://i.pinimg.com/600x600_R/42/f5/36/42f5364f737aff4749a8e9046510828f.jpg', + 'is_partner' => true, + 'verified_user_websites' => array( 'pinterest.dima.works', 'wordpress.dima.works' ), + 'is_any_website_verified' => true, + 'is_billing_setup' => false, + 'coupon_redeem_info' => array( + 'redeem_status' => false, + ), + 'available_discounts' => false, + ), + 'tracking_advertiser' => '549765662491', + 'tracking_tag' => '2613286171854', + ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( + array( + 'pinterest_for_woocommerce' => true, + ), + $response->get_data() + ); + } + + /** + * Tests set settings endpoint returns error if wrong data key is passed. + * + * @return void + */ + public function test_set_settings_endpoint_returns_error_if_data_is_missing() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + Pinterest_For_Woocommerce::save_settings( array() ); + + $request = new WP_REST_Request( 'POST', '/pinterest/v1/settings' ); + $request->set_body_params( + array( + 'some_other_parameter_name' => array(), + ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( + 'Missing option parameters.', + $response->get_data()['message'] ?? '' + ); + } + + /** + * Tests set settings endpoint returns error if save fails. + * + * @return void + */ + public function test_set_settings_endpoint_returns_error_if_save_fails() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + // Make update_option fail. + add_filter( + 'pre_update_option', + function () { + return false; + } + ); + + Pinterest_For_Woocommerce::save_settings( array() ); + + $request = new WP_REST_Request( 'POST', '/pinterest/v1/settings' ); + $request->set_body_params( + array( + 'pinterest_for_woocommerce' => array( + 'automatic_enhanced_match_support' => true, + 'last_synced_settings' => '31 May 2023, 03:52:53 pm', + 'track_conversions' => true, + 'enhanced_match_support' => true, + 'save_to_pinterest' => true, + 'rich_pins_on_posts' => true, + 'rich_pins_on_products' => true, + 'product_sync_enabled' => true, + 'enable_debug_logging' => true, + 'erase_plugin_data' => false, + 'ads_campaign_is_active' => true, + 'did_redirect_to_onboarding' => false, + 'account_data' => array( + 'username' => 'dmytromaksiuta1', + 'full_name' => '', + 'id' => '8842280425079444297', + 'image_medium_url' => 'https://i.pinimg.com/600x600_R/42/f5/36/42f5364f737aff4749a8e9046510828f.jpg', + 'is_partner' => true, + 'verified_user_websites' => array( 'pinterest.dima.works', 'wordpress.dima.works' ), + 'is_any_website_verified' => true, + 'is_billing_setup' => false, + 'coupon_redeem_info' => array( + 'redeem_status' => false, + ), + 'available_discounts' => false, + ), + 'tracking_advertiser' => '549765662491', + 'tracking_tag' => '2613286171854', + ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 500, $response->get_status() ); + $this->assertEquals( + 'There was an error saving the settings.', + $response->get_data()['message'] ?? '' + ); + } +} diff --git a/tests/Unit/Api/SyncSettingsTest.php b/tests/Unit/Api/SyncSettingsTest.php new file mode 100644 index 000000000..198ca46bd --- /dev/null +++ b/tests/Unit/Api/SyncSettingsTest.php @@ -0,0 +1,108 @@ +get_routes(); + $this->assertArrayHasKey( '/pinterest/v1/sync_settings', $routes ); + } + + /** + * Tests if the sync settings endpoints reject access. + * + * @return void + */ + public function test_sync_settings_endpoint_rejects_access() { + // 1. No authentication. + $request = new WP_REST_Request( 'GET', '/pinterest/v1/sync_settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); + + // 2. No authorisation. + $user = $this->factory->user->create( array( 'role' => 'guest' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/sync_settings' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 403, $response->get_status() ); + } + + /** + * Tests endpoint returns settings synced with Pinterest side. + * + * @return void + */ + public function test_sync_settings_endpoint_returns_settings_synced_with_pinterest() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + Pinterest_For_Woocommerce::save_setting( 'automatic_enhanced_match_support', false ); + Pinterest_For_WooCommerce::save_setting( 'tracking_advertiser', 'ai-123456789' ); + Pinterest_For_WooCommerce::save_setting( 'tracking_tag', 'ti-123456789' ); + + add_filter( + 'pre_http_request', + function ( $response, $args, $url ) { + if ( 'https://api.pinterest.com/v5/ad_accounts/ai-123456789/conversion_tags/ti-123456789' === $url ) { + $response = array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'configs' => array( + 'aem_enabled' => true, + ), + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + } + return $response; + }, + 10, + 3 + ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/sync_settings' ); + $response = rest_get_server()->dispatch( $request ); + + [ + 'success' => $success, + 'synced_settings' => $synced_settings, + ] = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertTrue( $success ); + $this->assertTrue( $synced_settings['automatic_enhanced_match_support'] ); + $this->assertTrue( Pinterest_For_WooCommerce::get_setting( 'automatic_enhanced_match_support' ) ); + $this->assertEquals( + $response->get_data()['synced_settings']['last_synced_settings'], + Pinterest_For_WooCommerce::get_setting( 'last_synced_settings' ) + ); + } +} diff --git a/tests/Unit/Api/TagsTest.php b/tests/Unit/Api/TagsTest.php new file mode 100644 index 000000000..cb705eb25 --- /dev/null +++ b/tests/Unit/Api/TagsTest.php @@ -0,0 +1,384 @@ +get_routes(); + $this->assertArrayHasKey( '/pinterest/v1/tags', $routes ); + } + + /** + * Tests if the tags endpoint rejects access. + * + * @return void + */ + public function test_tags_endpoint_rejects_access() { + // 1. No authentication. + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tags' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 401, $response->get_status() ); + + // 2. No authorisation. + $user = $this->factory->user->create( array( 'role' => 'guest' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tags' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 403, $response->get_status() ); + } + + /** + * Tests if the tags endpoint returns an error when the advertiser is missing. + * + * @return void + */ + public function test_tags_endpoint_returns_advertiser_missing() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tags' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( + 'pinterest-for-woocommerce_tags_error', + $response->get_data()['code'] ?? '' + ); + $this->assertEquals( + 'No tracking tag available. [Advertiser missing]', + $response->get_data()['message'] ?? '' + ); + } + + /** + * Tests if the tags endpoint returns a list of tags available. + * + * @return void + */ + public function test_tags_endpoint_returns_tags_list() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + $enhanced_match = array( + 'aem_enabled' => false, + 'md_frequency' => 1, + 'aem_fnln_enabled' => false, + 'aem_ph_enabled' => false, + 'aem_ge_enabled' => false, + 'aem_db_enabled' => false, + 'aem_loc_enabled' => false, + ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) use ( $enhanced_match) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'items' => array( + array( + 'ad_account_id' => 'ai-123456789', + 'code_snippet' => '', + 'enhanced_match_status' => 'VALIDATION_COMPLETE', + 'id' => 'tag-id-1', + 'last_fired_time_ms' => 0, + 'name' => 'Test Tag 1', + 'status' => 'ACTIVE', + 'version' => 2, + 'configs' => $enhanced_match, + ), + array( + 'ad_account_id' => 'ai-123456789', + 'code_snippet' => '', + 'enhanced_match_status' => 'VALIDATION_COMPLETE', + 'id' => 'tag-id-2', + 'last_fired_time_ms' => 0, + 'name' => 'Test Tag 2', + 'status' => 'ACTIVE', + 'version' => 2, + 'configs' => $enhanced_match, + ), + array( + 'ad_account_id' => 'ai-123456789', + 'code_snippet' => '', + 'enhanced_match_status' => 'VALIDATION_COMPLETE', + 'id' => 'tag-id-3', + 'last_fired_time_ms' => 0, + 'name' => 'Test Tag 3', + 'status' => 'ACTIVE', + 'version' => 2, + 'configs' => $enhanced_match, + ), + array( + 'ad_account_id' => 'ai-123456789', + 'code_snippet' => '', + 'enhanced_match_status' => 'VALIDATION_COMPLETE', + 'id' => 'tag-id-4', + 'last_fired_time_ms' => 0, + 'name' => 'Test Tag 4', + 'status' => 'ACTIVE', + 'version' => 2, + 'configs' => $enhanced_match, + ), + ), + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tags' ); + $request->set_query_params( + array( + 'advrtsr_id' => 'ai-123456789', + ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( + array( + array( + 'id' => 'tag-id-1', + 'name' => 'Test Tag 1', + ), + array( + 'id' => 'tag-id-2', + 'name' => 'Test Tag 2', + ), + array( + 'id' => 'tag-id-3', + 'name' => 'Test Tag 3', + ), + array( + 'id' => 'tag-id-4', + 'name' => 'Test Tag 4', + ), + ), + $response->get_data() + ); + } + + /** + * Tests if the tags endpoint returns a newly created tag. + * + * @return void + */ + public function test_tags_endpoint_returns_newly_created_tag() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + $body = array(); + + // 1. GET tags list. + if ( 'GET' === $parsed_args['method'] ) { + $body = array( + 'items' => array(), + ); + } + + // 2. POST new tag. + if ( 'POST' === $parsed_args['method'] ) { + $body = array( + 'ad_account_id' => 'ai-123456789', + 'code_snippet' => '', + 'enhanced_match_status' => 'VALIDATION_COMPLETE', + 'id' => 'tag-id-new', + 'last_fired_time_ms' => 0, + 'name' => 'Test Tag New', + 'status' => 'ACTIVE', + 'version' => 2, + 'configs' => array( + 'aem_enabled' => false, + 'md_frequency' => 1, + 'aem_fnln_enabled' => false, + 'aem_ph_enabled' => false, + 'aem_ge_enabled' => false, + 'aem_db_enabled' => false, + 'aem_loc_enabled' => false, + ), + ); + } + + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( $body ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tags' ); + $request->set_query_params( + array( + 'advrtsr_id' => 'ai-123456789', + ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( + array( + array( + 'id' => 'tag-id-new', + 'name' => 'Test Tag New', + ) + ), + $response->get_data() + ); + } + + /** + * Tests if the tags endpoint returns a tags list error. + * + * @return void + */ + public function test_tags_endpoint_returns_tags_list_error() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'code' => 0, + 'message' => 'string', + ) + ), + 'response' => array( + 'code' => 500, + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tags' ); + $request->set_query_params( + array( + 'advrtsr_id' => 'ai-123456789', + ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( + 'No tracking tag available. [Response error]', + $response->get_data()['message'] ?? '' + ); + } + + /** + * Tests if the tags endpoint returns a tag create error. + * + * @return void + */ + public function test_tags_endpoint_returns_tag_create_error() { + $user = $this->factory->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $user ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + $body = array(); + $code = 200; + $message = 'OK'; + + // 1. GET tags list. + if ( 'GET' === $parsed_args['method'] ) { + $body = array( + 'items' => array(), + ); + } + + // 2. POST new tag. + if ( 'POST' === $parsed_args['method'] ) { + $code = 500; + $message = 'Unexpected error'; + $body = array( + 'code' => 0, + 'message' => 'string', + ); + } + + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( $body ), + 'response' => array( + 'code' => $code, + 'message' => $message, + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $request = new WP_REST_Request( 'GET', '/pinterest/v1/tags' ); + $request->set_query_params( + array( + 'advrtsr_id' => 'ai-123456789', + ) + ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertEquals( 400, $response->get_status() ); + $this->assertEquals( + 'No tracking tag available. [Could not create a tag. Please check the logs for additional information.]', + $response->get_data()['message'] ?? '' + ); + } +} diff --git a/tests/Unit/PinterestForWoocommerceTest.php b/tests/Unit/PinterestForWoocommerceTest.php index 2747bd021..2331c4c60 100644 --- a/tests/Unit/PinterestForWoocommerceTest.php +++ b/tests/Unit/PinterestForWoocommerceTest.php @@ -1,8 +1,41 @@ assertEquals( + array( + 'track_conversions' => true, + 'enhanced_match_support' => true, + 'automatic_enhanced_match_support' => true, + 'save_to_pinterest' => true, + 'rich_pins_on_posts' => true, + 'rich_pins_on_products' => true, + 'product_sync_enabled' => true, + 'enable_debug_logging' => false, + 'erase_plugin_data' => false, + ), + $settings + ); + } /** * Test of the plugin has refresh token action initialised. @@ -13,4 +46,227 @@ public function test_pinterest_api_access_token_scheduled_for_refresh() { Pinterest_For_Woocommerce(); $this->assertEquals( 10, has_action( 'init', [ RefreshToken::class, 'schedule_event' ] ) ); } + + public function test_update_commerce_integration_returns_successful_response() { + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array_merge( + array( + 'id' => '9876543210123456789', + 'connected_user_id' => 'cud-123456789', + 'created_timestamp' => 123456789, + 'updated_timestamp' => 987654321, + ), + array_filter( + json_decode( $parsed_args['body'], true ), + function ( $key ) { + return ! in_array( + $key, + array( 'partner_access_token', 'partner_refresh_token', 'partner_primary_email' ) + ); + }, + ARRAY_FILTER_USE_KEY + ) + ) + ), + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $external_business_id = 'ebi-123456789'; + $data = array( + 'external_business_id' => 'ebi-123456789', + 'connected_merchant_id' => 'cmd-123456789', + 'connected_advertiser_id' => 'cai-123456789', + 'connected_lba_id' => 'cli-123456789', + 'connected_tag_id' => 'cti-123456789', + 'partner_access_token' => 'pat-123456789', + 'partner_refresh_token' => 'prt-123456789', + 'partner_primary_email' => 'ppe-123456789', + 'partner_access_token_expiry' => 9876543210, + 'partner_refresh_token_expiry' => 9876543210, + 'scopes' => 's-c-o-p-e-s', + 'additional_id_1' => 'ai1-123456789', + 'partner_metadata' => 'partner-meta-data', + ); + $response = Pinterest_For_Woocommerce::update_commerce_integration( $external_business_id, $data ); + + $this->assertEquals( + array( + 'id' => '9876543210123456789', + 'external_business_id' => 'ebi-123456789', + 'connected_merchant_id' => 'cmd-123456789', + 'connected_user_id' => 'cud-123456789', + 'connected_advertiser_id' => 'cai-123456789', + 'connected_lba_id' => 'cli-123456789', + 'connected_tag_id' => 'cti-123456789', + 'partner_access_token_expiry' => 9876543210, + 'partner_refresh_token_expiry' => 9876543210, + 'scopes' => 's-c-o-p-e-s', + 'created_timestamp' => 123456789, + 'updated_timestamp' => 987654321, + 'additional_id_1' => 'ai1-123456789', + 'partner_metadata' => 'partner-meta-data', + ), + $response + ); + } + + public function test_update_commerce_integration_returns_integration_not_found() { + $this->expectException( PinterestApiException::class ); + $this->expectExceptionCode( 404 ); + $this->expectExceptionMessage( 'Sorry! We could not find your integration.' ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'code' => 4180, + 'message' => 'Sorry! We could not find your integration.', + ) + ), + 'response' => array( + 'code' => 404, + 'message' => 'Not Found', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $data = array( + 'external_business_id' => 'ebi-123456789', + 'connected_merchant_id' => 'cmd-123456789', + 'connected_advertiser_id' => 'cai-123456789', + 'connected_lba_id' => 'cli-123456789', + 'connected_tag_id' => 'cti-123456789', + 'partner_access_token' => 'pat-123456789', + 'partner_refresh_token' => 'prt-123456789', + 'partner_primary_email' => 'ppe-123456789', + 'partner_access_token_expiry' => 9876543210, + 'partner_refresh_token_expiry' => 9876543210, + 'scopes' => 's-c-o-p-e-s', + 'additional_id_1' => 'ai1-123456789', + 'partner_metadata' => 'partner-meta-data', + ); + Pinterest_For_Woocommerce::update_commerce_integration( 'ebi-123456789', $data ); + } + + public function test_update_commerce_integration_returns_cant_access_this_integration_metadata() { + $this->expectException( PinterestApiException::class ); + $this->expectExceptionCode( 409 ); + $this->expectExceptionMessage( 'Can\'t access this integration metadata.' ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'code' => 4182, + 'message' => 'Can\'t access this integration metadata.', + ) + ), + 'response' => array( + 'code' => 409, + 'message' => 'Conflict', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $data = array( + 'external_business_id' => 'ebi-123456789', + 'connected_merchant_id' => 'cmd-123456789', + 'connected_advertiser_id' => 'cai-123456789', + 'connected_lba_id' => 'cli-123456789', + 'connected_tag_id' => 'cti-123456789', + 'partner_access_token' => 'pat-123456789', + 'partner_refresh_token' => 'prt-123456789', + 'partner_primary_email' => 'ppe-123456789', + 'partner_access_token_expiry' => 9876543210, + 'partner_refresh_token_expiry' => 9876543210, + 'scopes' => 's-c-o-p-e-s', + 'additional_id_1' => 'ai1-123456789', + 'partner_metadata' => 'partner-meta-data', + ); + Pinterest_For_Woocommerce::update_commerce_integration( 'ebi-123456789', $data ); + } + + public function test_update_commerce_integration_returns_unexpected_error() { + $this->expectException( PinterestApiException::class ); + $this->expectExceptionCode( 500 ); + $this->expectExceptionMessage( 'Any other message from Pinterest which falls under Unexpected error.' ); + + add_filter( + 'pre_http_request', + function ( $response, $parsed_args, $url ) { + return array( + 'headers' => array( + 'content-type' => 'application/json', + ), + 'body' => json_encode( + array( + 'code' => 0, + 'message' => 'Any other message from Pinterest which falls under Unexpected error.', + ) + ), + 'response' => array( + 'code' => 500, + 'message' => 'Unexpected error', + ), + 'cookies' => array(), + 'filename' => '', + ); + }, + 10, + 3 + ); + + $data = array( + 'external_business_id' => 'ebi-123456789', + 'connected_merchant_id' => 'cmd-123456789', + 'connected_advertiser_id' => 'cai-123456789', + 'connected_lba_id' => 'cli-123456789', + 'connected_tag_id' => 'cti-123456789', + 'partner_access_token' => 'pat-123456789', + 'partner_refresh_token' => 'prt-123456789', + 'partner_primary_email' => 'ppe-123456789', + 'partner_access_token_expiry' => 9876543210, + 'partner_refresh_token_expiry' => 9876543210, + 'scopes' => 's-c-o-p-e-s', + 'additional_id_1' => 'ai1-123456789', + 'partner_metadata' => 'partner-meta-data', + ); + Pinterest_For_Woocommerce::update_commerce_integration( 'ebi-123456789', $data ); + } } diff --git a/tests/Unit/TasksTest.php b/tests/Unit/TasksTest.php index 492ecbbfc..2d26b67a7 100644 --- a/tests/Unit/TasksTest.php +++ b/tests/Unit/TasksTest.php @@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Pinterest\Tests\Unit; +use Pinterest_For_Woocommerce; use \WP_UnitTestCase; use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskLists; use Automattic\WooCommerce\Pinterest\Admin\Tasks\Onboarding; @@ -49,6 +50,13 @@ public function test_add_onboarding_task_added() { * Tasks to assert the completion of the Onboarding task. */ public function test_onboarding_task_completed() { + add_filter( + 'home_url', + function() { + return 'https://somedomain.com/'; + } + ); + // Assert that the task is not completed. $task = TaskLists::get_task( 'setup-pinterest' ); $is_complete = $task->is_complete(); @@ -58,18 +66,19 @@ public function test_onboarding_task_completed() { // Setup complete data. $account_data = array( 'is_any_website_verified' => true, + 'verified_user_websites' => array( 'somedomain.com' ), 'is_partner' => true, ); - \Pinterest_For_Woocommerce::save_setting( 'account_data', $account_data ); - \Pinterest_For_Woocommerce::save_token_data( + Pinterest_For_Woocommerce::save_setting( 'account_data', $account_data ); + Pinterest_For_Woocommerce::save_token_data( array( 'access_token' => 'some-fake-access-token', ) ); - \Pinterest_For_Woocommerce::save_setting( 'tracking_tag', true ); + Pinterest_For_Woocommerce::save_setting( 'tracking_tag', true ); // Assert that the task is completed. $is_complete = $task->is_complete(); $this->assertTrue( $is_complete, 'Cannot assert task completion.' ); } -} \ No newline at end of file +}