diff --git a/.github/workflows/pull-request-post-merge-processing.yml b/.github/workflows/pull-request-post-merge-processing.yml index 68f649a6a45..42bd8aa47da 100644 --- a/.github/workflows/pull-request-post-merge-processing.yml +++ b/.github/workflows/pull-request-post-merge-processing.yml @@ -4,29 +4,39 @@ on: types: [closed] jobs: - assign-milestone: - name: "Assign milestone to merged pull request" - if: github.event.pull_request.merged == true && ! github.event.pull_request.milestone + process-pull-request-after-merge: + name: "Process a pull request after it's merged" + if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - - name: "Get the milestone changer script" - run: | - curl \ - --silent \ - --fail \ - --header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \ - --header 'User-Agent: GitHub action to set the milestone for a pull request' \ - --header 'Accept: application/vnd.github.v3.raw' \ - --remote-name \ - --location $GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/assign-milestone-to-merged-pr.php - env: - GITHUB_API_URL: ${{ env.GITHUB_API_URL }} - - name: "Install PHP" - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - - name: "Run the milestone changer script" - run: php assign-milestone-to-merged-pr.php - env: - PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Get the action scripts" + run: | + scripts="assign-milestone-to-merged-pr.php add-post-merge-comment.php post-request-shared.php" + for script in $scripts + do + curl \ + --silent \ + --fail \ + --header 'Authorization: bearer ${{ secrets.GITHUB_TOKEN }}' \ + --header 'User-Agent: GitHub action to set the milestone for a pull request' \ + --header 'Accept: application/vnd.github.v3.raw' \ + --output $script \ + --location "$GITHUB_API_URL/repos/${{ github.repository }}/contents/.github/workflows/scripts/$script?ref=${{ github.event.pull_request.base.ref }}" + done + env: + GITHUB_API_URL: ${{ env.GITHUB_API_URL }} + - name: "Install PHP" + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + - name: "Run the script to assign a milestone" + if: "!github.event.pull_request.milestone" + run: php assign-milestone-to-merged-pr.php + env: + PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Run the script to post a comment with next steps hint" + run: php add-post-merge-comment.php + env: + PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scripts/add-post-merge-comment.php b/.github/workflows/scripts/add-post-merge-comment.php new file mode 100644 index 00000000000..fbe3d37bc45 --- /dev/null +++ b/.github/workflows/scripts/add-post-merge-comment.php @@ -0,0 +1,95 @@ + "$keyword { $body }" ); - $context = stream_context_create( - array( - 'http' => array( - 'method' => 'POST', - 'header' => array( - 'Accept: application/json', - 'Content-Type: application/json', - 'User-Agent: GitHub action to set the milestone for a pull request', - 'Authorization: bearer ' . $github_token, - ), - 'content' => json_encode( $data ), - ), - ) - ); - - $result = file_get_contents( $graphql_api_url, false, $context ); - return is_string( $result ) ? json_decode( $result, true ) : $result; -} - // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions diff --git a/.github/workflows/scripts/post-request-shared.php b/.github/workflows/scripts/post-request-shared.php new file mode 100644 index 00000000000..21e9219f2a6 --- /dev/null +++ b/.github/workflows/scripts/post-request-shared.php @@ -0,0 +1,56 @@ + "$keyword { $body }" ); + $context = stream_context_create( + array( + 'http' => array( + 'method' => 'POST', + 'header' => array( + 'Accept: application/json', + 'Content-Type: application/json', + 'User-Agent: GitHub action to set the milestone for a pull request', + 'Authorization: bearer ' . $github_token, + ), + 'content' => json_encode( $data ), + ), + ) + ); + + $result = file_get_contents( $graphql_api_url, false, $context ); + return is_string( $result ) ? json_decode( $result, true ) : $result; +} + +// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions diff --git a/changelog.txt b/changelog.txt index fc6469303eb..dff62394c39 100644 --- a/changelog.txt +++ b/changelog.txt @@ -21,6 +21,7 @@ * Fix - Replace hardcoded frontend JS script versions with WC version to bust cached/staled JS scripts. #30301 * Fix - Variable product showing HTML content while granting access for downloadable product in orders. #30305 * Fix - Replaced wp.passwordStrength deprecated method. #30191 +* Fix - `woocommerce_email_settings` filter being triggered twice. #30404 * Dev - Apply `woocommerce_logout_default_redirect_url` filter to logout for custom endpoint. #29967 * Dev - Added new `woocommerce_email_sent` hook. #30123 diff --git a/i18n/states.php b/i18n/states.php index fd423a1ca62..e26c9c7d7b8 100644 --- a/i18n/states.php +++ b/i18n/states.php @@ -1595,6 +1595,32 @@ return array( 'RSVO' => _x( 'Vojvodina', 'district', 'woocommerce' ), ), 'SE' => array(), + 'UA' => array( // Ukraine. Ref: https://en.wikipedia.org/wiki/Oblasts_of_Ukraine. + 'VN' => __( 'Vinnytsia Oblast', 'woocommerce' ), + 'VL' => __( 'Volyn Oblast', 'woocommerce' ), + 'DP' => __( 'Dnipropetrovsk Oblast', 'woocommerce' ), + 'DT' => __( 'Donetsk Oblast', 'woocommerce' ), + 'ZT' => __( 'Zhytomyr Oblast', 'woocommerce' ), + 'ZK' => __( 'Zakarpattia Oblast', 'woocommerce' ), + 'ZP' => __( 'Zaporizhzhia Oblast', 'woocommerce' ), + 'IF' => __( 'Ivano-Frankivsk Oblast', 'woocommerce' ), + 'KV' => __( 'Kyiv Oblast', 'woocommerce' ), + 'KH' => __( 'Kirovohrad Oblast', 'woocommerce' ), + 'LH' => __( 'Luhansk Oblast', 'woocommerce' ), + 'LV' => __( 'Lviv Oblast', 'woocommerce' ), + 'MY' => __( 'Mykolaiv Oblast', 'woocommerce' ), + 'OD' => __( 'Odessa Oblast', 'woocommerce' ), + 'PL' => __( 'Poltava Oblast', 'woocommerce' ), + 'RV' => __( 'Rivne Oblast', 'woocommerce' ), + 'SM' => __( 'Sumy Oblast', 'woocommerce' ), + 'TP' => __( 'Ternopil Oblast', 'woocommerce' ), + 'KK' => __( 'Kharkiv Oblast', 'woocommerce' ), + 'KS' => __( 'Kherson Oblast', 'woocommerce' ), + 'KM' => __( 'Khmelnytskyi Oblast', 'woocommerce' ), + 'CK' => __( 'Cherkasy Oblast', 'woocommerce' ), + 'CH' => __( 'Chernihiv Oblast', 'woocommerce' ), + 'CV' => __( 'Chernivtsi Oblast', 'woocommerce' ), + ), 'UG' => array( // Uganda districts. Ref: https://en.wikipedia.org/wiki/ISO_3166-2:UG. 'UG314' => __( 'Abim', 'woocommerce' ), 'UG301' => __( 'Adjumani', 'woocommerce' ), diff --git a/includes/admin/meta-boxes/class-wc-meta-box-order-data.php b/includes/admin/meta-boxes/class-wc-meta-box-order-data.php index 39109cfe41d..a2dab284e22 100644 --- a/includes/admin/meta-boxes/class-wc-meta-box-order-data.php +++ b/includes/admin/meta-boxes/class-wc-meta-box-order-data.php @@ -607,6 +607,10 @@ class WC_Meta_Box_Order_Data { $payment_method_title = $methods[ $payment_method ]->get_title(); } + if ( $payment_method == 'other') { + $payment_method_title = esc_html__( 'Other', 'woocommerce' ); + } + $props['payment_method'] = $payment_method; $props['payment_method_title'] = $payment_method_title; } diff --git a/includes/class-wc-customer.php b/includes/class-wc-customer.php index 8a147baf421..43587c5d1e3 100644 --- a/includes/class-wc-customer.php +++ b/includes/class-wc-customer.php @@ -78,6 +78,14 @@ class WC_Customer extends WC_Legacy_Customer { */ protected $calculated_shipping = false; + /** + * This is the name of this object type. + * + * @since 5.6.0 + * @var string + */ + protected $object_type = 'customer'; + /** * Load customer data based on how WC_Customer is called. * @@ -118,16 +126,6 @@ class WC_Customer extends WC_Legacy_Customer { } } - /** - * Prefix for action and filter hooks on data. - * - * @since 3.0.0 - * @return string - */ - protected function get_hook_prefix() { - return 'woocommerce_customer_get_'; - } - /** * Delete a customer and reassign posts.. * diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 190749d7db3..25ca18d6515 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1315,6 +1315,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da AND p.post_type = 'product' ", + 'orderby' => ' + ORDER BY RAND()', 'limits' => ' LIMIT ' . absint( $limit ) . ' ', diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php index 8e473271159..e09b503d582 100644 --- a/includes/wc-product-functions.php +++ b/includes/wc-product-functions.php @@ -492,6 +492,19 @@ add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' ); * @return array */ function wc_get_attachment_image_attributes( $attr ) { + /* + * If the user can manage woocommerce, allow them to + * see the image content. + */ + if ( current_user_can( 'manage_woocommerce' ) ) { + return $attr; + } + + /* + * If the user does not have the right capabilities, + * filter out the image source and replace with placeholder + * image. + */ if ( isset( $attr['src'] ) && strstr( $attr['src'], 'woocommerce_uploads/' ) ) { $attr['src'] = wc_placeholder_img_src(); @@ -511,7 +524,19 @@ add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attri * @return array */ function wc_prepare_attachment_for_js( $response ) { + /* + * If the user can manage woocommerce, allow them to + * see the image content. + */ + if ( current_user_can( 'manage_woocommerce' ) ) { + return $response; + } + /* + * If the user does not have the right capabilities, + * filter out the image source and replace with placeholder + * image. + */ if ( isset( $response['url'] ) && strstr( $response['url'], 'woocommerce_uploads/' ) ) { $response['full']['url'] = wc_placeholder_img_src(); if ( isset( $response['sizes'] ) ) { diff --git a/tests/e2e/core-tests/specs/activate-and-setup/onboarding-tasklist.test.js b/tests/e2e/core-tests/specs/activate-and-setup/onboarding-tasklist.test.js index fefdf853838..4f777265352 100644 --- a/tests/e2e/core-tests/specs/activate-and-setup/onboarding-tasklist.test.js +++ b/tests/e2e/core-tests/specs/activate-and-setup/onboarding-tasklist.test.js @@ -1,5 +1,3 @@ -/* eslint-disable jest/no-export, jest/no-disabled-tests */ - /** * Internal dependencies */ @@ -37,6 +35,10 @@ const runOnboardingFlowTest = () => { await withRestApi.deleteAllShippingZones(); }); + it('can reset shipping classes', async () => { + await withRestApi.deleteAllShippingClasses(); + }) + it('can reset to default settings', async () => { await withRestApi.resetSettingsGroupToDefault('general'); await withRestApi.resetSettingsGroupToDefault('products'); @@ -55,7 +57,7 @@ const runTaskListTest = () => { beforeAll(async () => { await merchant.login(); }); - + it('can setup shipping', async () => { await page.evaluate(() => { document.querySelector('.woocommerce-list__item-title').scrollIntoView(); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 925774a3acc..21ccf6b5228 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -12,6 +12,7 @@ - Added `describeIf()` to conditionally run a test suite - Added `itIf()` to conditionally run a test case. - Added merchant workflows around plugins: `uploadAndActivatePlugin()`, `activatePlugin()`, `deactivatePlugin()`, `deletePlugin()` +- Added `deleteAllShippingClasses()` which permanently deletes all shipping classes using the API # 0.1.5 diff --git a/tests/e2e/utils/README.md b/tests/e2e/utils/README.md index e817711a13e..496022198d0 100644 --- a/tests/e2e/utils/README.md +++ b/tests/e2e/utils/README.md @@ -143,6 +143,7 @@ This package provides support for enabling retries in tests: | `deleteAllCoupons` | | Permanently delete all coupons | | `deleteAllProducts` | | Permanently delete all products | | `deleteAllShippingZones` | | Permanently delete all shipping zones except the default | +| `deleteAllShippingClasses` | Permanently delete all shipping classes | | `deleteCustomerByEmail` | `emailAddress` | Delete customer user account. Posts are reassigned to user ID 1 | | `resetSettingsGroupToDefault` | `settingsGroup` | Reset settings in settings group to default except `select` fields | diff --git a/tests/e2e/utils/src/flows/with-rest-api.js b/tests/e2e/utils/src/flows/with-rest-api.js index 64b40d773ee..029c95f0923 100644 --- a/tests/e2e/utils/src/flows/with-rest-api.js +++ b/tests/e2e/utils/src/flows/with-rest-api.js @@ -4,6 +4,7 @@ import {Coupon, Setting, SimpleProduct} from '@woocommerce/api'; const client = factories.api.withDefaultPermalinks; const onboardingProfileEndpoint = '/wc-admin/onboarding/profile'; const shippingZoneEndpoint = '/wc/v3/shipping/zones'; +const shippingClassesEndpoint = '/wc/v3/products/shipping_classes'; const userEndpoint = '/wp/v2/users'; /** @@ -99,6 +100,20 @@ export const withRestApi = { } } }, + /** + * Use api package to delete shipping classes. + * + * @return {Promise} Promise resolving once shipping classes have been deleted. + */ + deleteAllShippingClasses: async () => { + const shippingClasses = await client.get( shippingClassesEndpoint ); + if ( shippingClasses.data && shippingClasses.data.length ) { + for ( let c = 0; c < shippingClasses.data.length; c++ ) { + const response = await client.delete( shippingClassesEndpoint + `/${shippingClasses.data[c].id}?force=true` ); + expect( response.statusCode ).toBe( 200 ); + } + } + }, /** * Delete a customer account by their email address if the user exists. *