From 05c7f336bc38a84c391b2e45835c22a93c1d5718 Mon Sep 17 00:00:00 2001 From: Gustavo Bordoni Date: Tue, 24 Nov 2020 23:56:36 -0500 Subject: [PATCH 001/120] Use %1$s instead of %s on regen description Resolve #28297 --- includes/customizer/class-wc-shop-customizer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/customizer/class-wc-shop-customizer.php b/includes/customizer/class-wc-shop-customizer.php index dd966b8de39..c2f408e2dc8 100644 --- a/includes/customizer/class-wc-shop-customizer.php +++ b/includes/customizer/class-wc-shop-customizer.php @@ -531,8 +531,8 @@ class WC_Shop_Customizer { /* translators: 1: tools URL 2: regen thumbs url */ $regen_description = sprintf( __( 'After publishing your changes, new image sizes may not be shown until you regenerate thumbnails. You can do this from the tools section in WooCommerce or by using a plugin such as Regenerate Thumbnails.', 'woocommerce' ), admin_url( 'admin.php?page=wc-status&tab=tools' ), 'https://en-gb.wordpress.org/plugins/regenerate-thumbnails/' ); } else { - /* translators: %s: regen thumbs url */ - $regen_description = sprintf( __( 'After publishing your changes, new image sizes may not be shown until you Regenerate Thumbnails.', 'woocommerce' ), 'https://en-gb.wordpress.org/plugins/regenerate-thumbnails/' ); + /* translators: 1: regen thumbs url */ + $regen_description = sprintf( __( 'After publishing your changes, new image sizes may not be shown until you Regenerate Thumbnails.', 'woocommerce' ), 'https://en-gb.wordpress.org/plugins/regenerate-thumbnails/' ); } $wp_customize->add_section( From 70e2a16c6f9475f4ca8c735cf7a2e30918af0912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Wed, 25 Nov 2020 06:48:13 +0100 Subject: [PATCH 002/120] Fix .gitignore --- src/Vendor/.gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Vendor/.gitignore b/src/Vendor/.gitignore index ef7b2901fa6..316569909e1 100644 --- a/src/Vendor/.gitignore +++ b/src/Vendor/.gitignore @@ -1,5 +1,5 @@ # Prevent anyone from accidentally adding code to these directories. -# This will break any PRs that do, revealing ths mistake they made. -README.md +# This will break any PRs that do, revealing the mistake they made. +* !.gitignore !README.md From 4a2943d45219028ce0e8bcdee0c26ccb2d664a6a Mon Sep 17 00:00:00 2001 From: roykho Date: Fri, 4 Dec 2020 13:17:24 -0800 Subject: [PATCH 003/120] Defer nonce creation until displayed by WC Admin closes #27424 --- .../notes/class-wc-notes-run-db-update.php | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/includes/admin/notes/class-wc-notes-run-db-update.php b/includes/admin/notes/class-wc-notes-run-db-update.php index c723d231d06..f59074679d5 100644 --- a/includes/admin/notes/class-wc-notes-run-db-update.php +++ b/includes/admin/notes/class-wc-notes-run-db-update.php @@ -110,10 +110,13 @@ class WC_Notes_Run_Db_Update { */ private static function update_needed_notice( $note_id = null ) { $update_url = html_entity_decode( - wp_nonce_url( - add_query_arg( 'do_update_woocommerce', 'true', wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' ) ), - 'wc_db_update', - 'wc_db_update_nonce' + add_query_arg( + array( + 'do_update_woocommerce' => 'true', + '_nonce_action' => 'wc_db_update', + '_nonce_name' => 'wc_db_update_nonce', + ), + wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' ) ) ); @@ -206,14 +209,13 @@ class WC_Notes_Run_Db_Update { */ private static function update_done_notice( $note_id ) { $hide_notices_url = html_entity_decode( // to convert &s to normal &, otherwise produces invalid link. - wp_nonce_url( - add_query_arg( - 'wc-hide-notice', - 'update', - wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' ) + add_query_arg( + array( + 'wc-hide-notice' => 'update', + '_nonce_action' => 'woocommerce_hide_notices_nonce', + '_nonce_name' => '_wc_notice_nonce', ), - 'woocommerce_hide_notices_nonce', - '_wc_notice_nonce' + wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' ) ) ); From 8944792a6958d4733f5285a021276ff84fb7f9eb Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 9 Dec 2020 09:29:31 +0100 Subject: [PATCH 004/120] Create additional download permissions on product save if needed When a simple product with downloads gets converted into a variable product all the existing download permissions for past orders become invalid. This commit adds an extra verification procedure to the product save code: - Get all the existing download permissions for the product and all the children (variations) - For each download permission for the parent product, if there's a variation that offers the same file for download (same file URL) AND an equivalent download permission doesn't exist (equivalent means same file URL, same order id and same user id), then create it. Additionally, a new WC_Customer_Download_Data_Store::create_from_data method is added. --- .../class-wc-customer-download-data-store.php | 107 +++++++++++++++--- .../class-wc-product-data-store-cpt.php | 107 ++++++++++++++++++ 2 files changed, 197 insertions(+), 17 deletions(-) diff --git a/includes/data-stores/class-wc-customer-download-data-store.php b/includes/data-stores/class-wc-customer-download-data-store.php index 379e5fa1d28..ac9689111ce 100644 --- a/includes/data-stores/class-wc-customer-download-data-store.php +++ b/includes/data-stores/class-wc-customer-download-data-store.php @@ -16,6 +16,38 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store_Interface { + /** + * Names of the database fields for the download permissions table. + */ + const DOWNLOAD_PERMISSION_DB_FIELDS = array( + 'download_id', + 'product_id', + 'user_id', + 'user_email', + 'order_id', + 'order_key', + 'downloads_remaining', + 'access_granted', + 'download_count', + 'access_expires', + ); + + /** + * Create download permission for a user, from an array of data. + * + * @param array $data Data to create the permission for. + * @returns int The database id of the created permission, or false if the permission creation failed. + */ + public function create_from_data( $data ) { + $data = array_intersect_key( $data, array_flip( self::DOWNLOAD_PERMISSION_DB_FIELDS ) ); + + $id = $this->insert_new_download_permission( $data ); + + do_action( 'woocommerce_grant_product_download_access', $data ); + + return $id; + } + /** * Create download permission for a user. * @@ -29,18 +61,41 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store $download->set_access_granted( time() ); } - $data = array( - 'download_id' => $download->get_download_id( 'edit' ), - 'product_id' => $download->get_product_id( 'edit' ), - 'user_id' => $download->get_user_id( 'edit' ), - 'user_email' => $download->get_user_email( 'edit' ), - 'order_id' => $download->get_order_id( 'edit' ), - 'order_key' => $download->get_order_key( 'edit' ), - 'downloads_remaining' => $download->get_downloads_remaining( 'edit' ), - 'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ), - 'download_count' => $download->get_download_count( 'edit' ), - 'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null, - ); + $data = array(); + foreach ( self::DOWNLOAD_PERMISSION_DB_FIELDS as $db_field_name ) { + $value = call_user_func( array( $download, 'get_' . $db_field_name ), 'edit' ); + $data[ $db_field_name ] = $value; + } + + $inserted_id = $this->insert_new_download_permission( $data ); + if ( $inserted_id ) { + $download->set_id( $inserted_id ); + $download->apply_changes(); + } + + do_action( 'woocommerce_grant_product_download_access', $data ); + } + + /** + * Create download permission for a user, from an array of data. + * Assumes that all the keys in the passed data are valid. + * + * @param array $data Data to create the permission for. + * @returns int The database id of the created permission, or false if the permission creation failed. + */ + private function insert_new_download_permission( $data ) { + global $wpdb; + + // Always set a access granted date. + if ( ! isset( $data['access_granted'] ) ) { + $data['access_granted'] = time(); + } + + $data['access_granted'] = $this->adjust_date_for_db( $data['access_granted'] ); + + if ( isset( $data['access_expires'] ) ) { + $data['access_expires'] = $this->adjust_date_for_db( $data['access_expires'] ); + } $format = array( '%s', @@ -61,12 +116,28 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store apply_filters( 'woocommerce_downloadable_file_permission_format', $format, $data ) ); - if ( $result ) { - $download->set_id( $wpdb->insert_id ); - $download->apply_changes(); + return $result ? $wpdb->insert_id : false; + } + + /** + * Adjust a date value to be inserted in the database. + * + * @param mixed $date The date value. Can be a WC_DateTime, a timestamp, or anything else that "date" recognizes. + * @return string The date converted to 'Y-m-d' format. + * @throws Exception The passed value can't be converted to a date. + */ + private function adjust_date_for_db( $date ) { + if ( 'WC_DateTime' === get_class( $date ) ) { + $date = $date->getTimestamp(); } - do_action( 'woocommerce_grant_product_download_access', $data ); + $adjusted_date = date( 'Y-m-d', $date ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date + + if ( $adjusted_date ) { + return $adjusted_date; + } + + throw new Exception( "I don't know how to get a date from a " . is_object( $date ) ? get_class( $date ) : gettype( $date ) ); } /** @@ -128,8 +199,10 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store 'order_id' => $download->get_order_id( 'edit' ), 'order_key' => $download->get_order_key( 'edit' ), 'downloads_remaining' => $download->get_downloads_remaining( 'edit' ), + // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date 'access_granted' => date( 'Y-m-d', $download->get_access_granted( 'edit' )->getTimestamp() ), 'download_count' => $download->get_download_count( 'edit' ), + // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date 'access_expires' => ! is_null( $download->get_access_expires( 'edit' ) ) ? date( 'Y-m-d', $download->get_access_expires( 'edit' )->getTimestamp() ) : null, ); @@ -412,7 +485,7 @@ class WC_Customer_Download_Data_Store implements WC_Customer_Download_Data_Store ) ORDER BY permissions.order_id, permissions.product_id, permissions.permission_id;", $customer_id, - date( 'Y-m-d', current_time( 'timestamp' ) ) + date( 'Y-m-d', current_time( 'timestamp' ) ) // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date ) ); } 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 e9f899d7754..e9e0313a203 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -19,6 +19,23 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Data_Store_Interface, WC_Product_Data_Store_Interface { + /** + * Instance of the downloads data store. + * + * @var WC_Data_Store + */ + private $downloads_data_store; + + /** + * WC_Product_Data_Store_CPT constructor. + */ + public function __construct() { + if ( is_callable( 'parent::__construct' ) ) { + parent::__construct(); + } + $this->downloads_data_store = WC_Data_Store::load( 'customer-download' ); + } + /** * Data stored in meta keys, but not considered "meta". * @@ -264,12 +281,102 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $this->update_version_and_type( $product ); $this->handle_updated_props( $product ); $this->clear_caches( $product ); + $this->maybe_adjust_download_permissions( $product ); $product->apply_changes(); do_action( 'woocommerce_update_product', $product->get_id(), $product ); } + /** + * Create additional download permissions for variations if necessary. + * + * When a simple downloadable product is converted to a variable product, + * existing download permissions are still present in the database but they don't apply anymore. + * This method creates additional download permissions for the variations based on + * the old existing ones for the main product. + * + * The procedure is as follows. For each existing download permission for the parent product, + * check if there's any variation offering the same file for download (the file URL, not name, is checked). + * If that is found, check if an equivalent permission exists (equivalent means for the same file and with + * the same order id and customer id). If no equivalent permission exists, create it. + * + * @param WC_Product $product The product to check permissions for. + */ + private function maybe_adjust_download_permissions( WC_Product $product ) { + $children_ids = $product->get_children(); + if ( ! $children_ids ) { + return; + } + + $parent_downloads = $this->get_download_files_and_permissions( $product ); + if ( ! $parent_downloads ) { + return; + } + + $children_with_downloads = array(); + foreach ( $children_ids as $child_id ) { + $child = wc_get_product( $child_id ); + $children_with_downloads[ $child_id ] = $this->get_download_files_and_permissions( $child ); + } + + foreach ( $parent_downloads['permission_data_by_file_order_user'] as $parent_file_order_and_user => $parent_download_data ) { + foreach ( $children_with_downloads as $child_id => $child_download_data ) { + $file_url = $parent_download_data['file']; + + $must_create_permission = + // The variation offers the same file as the parent for download... + in_array( $file_url, array_keys( $child_download_data['download_ids_by_file_url'] ), true ) && + // ...but no equivalent download permission (same file URL, order id and user id) exists. + ! array_key_exists( $parent_file_order_and_user, $child_download_data['permission_data_by_file_order_user'] ); + + if ( $must_create_permission ) { + // The new child download permission is a copy of the parent's, + // but with the product and download ids changed to match those of the variation. + $new_download_data = $parent_download_data['data']; + $new_download_data['product_id'] = $child_id; + $new_download_data['download_id'] = $child_download_data['download_ids_by_file_url'][ $file_url ]; + $this->downloads_data_store->create_from_data( $new_download_data ); + } + } + } + } + + /** + * Get the existing downloadable files and download permissions for a given product. + * The returned value is an array with two keys: + * + * - download_ids_by_file_url: an associative array of file url => download_id. + * - permission_data_by_file_order_user: an associative array where key is "file_url:customer_id:order_id" and value is the full permission data set. + * + * @param WC_Product $product The product to get the downloadable files and permissions for. + * @return array[] Information about the downloadable files and permissions for the product. + */ + private function get_download_files_and_permissions( WC_Product $product ) { + $result = array( + 'permission_data_by_file_order_user' => array(), + 'download_ids_by_file_url' => array(), + ); + + $downloads = $product->get_downloads(); + foreach ( $downloads as $download ) { + $result['download_ids_by_file_url'][ $download->get_file() ] = $download->get_id(); + } + + $permissions = $this->downloads_data_store->get_downloads( array( 'product_id' => $product->get_id() ) ); + foreach ( $permissions as $permission ) { + $permission_data = (array) $permission->data; + $file = $downloads[ $permission_data['download_id'] ]->get_file(); + $data = array( + 'file' => $file, + 'data' => (array) $permission->data, + ); + $result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data; + } + + return $result; + } + /** * Method to delete a product from the database. * From f0b834d1ec32d0fb864a82db7c70a14d0f5774ba Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Thu, 10 Dec 2020 15:34:48 +0100 Subject: [PATCH 005/120] Move the download permissions adjustment process to a scheduled action. Also, move all the new code from the 'WC_Product_Data_Store_CPT' class to a new separate 'DownloadPermissionsAdjuster' class. --- includes/class-woocommerce.php | 3 + .../class-wc-product-data-store-cpt.php | 112 +------------ src/Container.php | 4 +- ...loadPermissionsAdjusterServiceProvider.php | 31 ++++ .../ProxiesServiceProvider.php | 2 +- src/Internal/DownloadPermissionsAdjuster.php | 154 ++++++++++++++++++ src/Proxies/LegacyProxy.php | 5 + 7 files changed, 202 insertions(+), 109 deletions(-) create mode 100644 src/Internal/DependencyManagement/ServiceProviders/DownloadPermissionsAdjusterServiceProvider.php create mode 100644 src/Internal/DownloadPermissionsAdjuster.php diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 6a03eecbb55..22d6efd223b 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -8,6 +8,7 @@ defined( 'ABSPATH' ) || exit; +use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster; use Automattic\WooCommerce\Proxies\LegacyProxy; /** @@ -202,6 +203,8 @@ final class WooCommerce { add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 ); add_action( 'activated_plugin', array( $this, 'activated_plugin' ) ); add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ) ); + + DownloadPermissionsAdjuster::init_hooks(); } /** 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 e9e0313a203..ee9f65ccb1e 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -6,6 +6,7 @@ */ use Automattic\Jetpack\Constants; +use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster; use Automattic\WooCommerce\Utilities\NumberUtil; if ( ! defined( 'ABSPATH' ) ) { @@ -19,23 +20,6 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Data_Store_Interface, WC_Product_Data_Store_Interface { - /** - * Instance of the downloads data store. - * - * @var WC_Data_Store - */ - private $downloads_data_store; - - /** - * WC_Product_Data_Store_CPT constructor. - */ - public function __construct() { - if ( is_callable( 'parent::__construct' ) ) { - parent::__construct(); - } - $this->downloads_data_store = WC_Data_Store::load( 'customer-download' ); - } - /** * Data stored in meta keys, but not considered "meta". * @@ -281,102 +265,16 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $this->update_version_and_type( $product ); $this->handle_updated_props( $product ); $this->clear_caches( $product ); - $this->maybe_adjust_download_permissions( $product ); + + wc_get_container() + ->get( DownloadPermissionsAdjuster::class ) + ->maybe_schedule_adjust_download_permissions( $product ); $product->apply_changes(); do_action( 'woocommerce_update_product', $product->get_id(), $product ); } - /** - * Create additional download permissions for variations if necessary. - * - * When a simple downloadable product is converted to a variable product, - * existing download permissions are still present in the database but they don't apply anymore. - * This method creates additional download permissions for the variations based on - * the old existing ones for the main product. - * - * The procedure is as follows. For each existing download permission for the parent product, - * check if there's any variation offering the same file for download (the file URL, not name, is checked). - * If that is found, check if an equivalent permission exists (equivalent means for the same file and with - * the same order id and customer id). If no equivalent permission exists, create it. - * - * @param WC_Product $product The product to check permissions for. - */ - private function maybe_adjust_download_permissions( WC_Product $product ) { - $children_ids = $product->get_children(); - if ( ! $children_ids ) { - return; - } - - $parent_downloads = $this->get_download_files_and_permissions( $product ); - if ( ! $parent_downloads ) { - return; - } - - $children_with_downloads = array(); - foreach ( $children_ids as $child_id ) { - $child = wc_get_product( $child_id ); - $children_with_downloads[ $child_id ] = $this->get_download_files_and_permissions( $child ); - } - - foreach ( $parent_downloads['permission_data_by_file_order_user'] as $parent_file_order_and_user => $parent_download_data ) { - foreach ( $children_with_downloads as $child_id => $child_download_data ) { - $file_url = $parent_download_data['file']; - - $must_create_permission = - // The variation offers the same file as the parent for download... - in_array( $file_url, array_keys( $child_download_data['download_ids_by_file_url'] ), true ) && - // ...but no equivalent download permission (same file URL, order id and user id) exists. - ! array_key_exists( $parent_file_order_and_user, $child_download_data['permission_data_by_file_order_user'] ); - - if ( $must_create_permission ) { - // The new child download permission is a copy of the parent's, - // but with the product and download ids changed to match those of the variation. - $new_download_data = $parent_download_data['data']; - $new_download_data['product_id'] = $child_id; - $new_download_data['download_id'] = $child_download_data['download_ids_by_file_url'][ $file_url ]; - $this->downloads_data_store->create_from_data( $new_download_data ); - } - } - } - } - - /** - * Get the existing downloadable files and download permissions for a given product. - * The returned value is an array with two keys: - * - * - download_ids_by_file_url: an associative array of file url => download_id. - * - permission_data_by_file_order_user: an associative array where key is "file_url:customer_id:order_id" and value is the full permission data set. - * - * @param WC_Product $product The product to get the downloadable files and permissions for. - * @return array[] Information about the downloadable files and permissions for the product. - */ - private function get_download_files_and_permissions( WC_Product $product ) { - $result = array( - 'permission_data_by_file_order_user' => array(), - 'download_ids_by_file_url' => array(), - ); - - $downloads = $product->get_downloads(); - foreach ( $downloads as $download ) { - $result['download_ids_by_file_url'][ $download->get_file() ] = $download->get_id(); - } - - $permissions = $this->downloads_data_store->get_downloads( array( 'product_id' => $product->get_id() ) ); - foreach ( $permissions as $permission ) { - $permission_data = (array) $permission->data; - $file = $downloads[ $permission_data['download_id'] ]->get_file(); - $data = array( - 'file' => $file, - 'data' => (array) $permission->data, - ); - $result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data; - } - - return $result; - } - /** * Method to delete a product from the database. * diff --git a/src/Container.php b/src/Container.php index 4291867682b..517239afeb7 100644 --- a/src/Container.php +++ b/src/Container.php @@ -5,8 +5,9 @@ namespace Automattic\WooCommerce; -use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\ExtendedContainer; +use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\DownloadPermissionsAdjusterServiceProvider; +use Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders\ProxiesServiceProvider; /** * PSR11 compliant dependency injection container for WooCommerce. @@ -33,6 +34,7 @@ final class Container implements \Psr\Container\ContainerInterface { */ private $service_providers = array( ProxiesServiceProvider::class, + DownloadPermissionsAdjusterServiceProvider::class, ); /** diff --git a/src/Internal/DependencyManagement/ServiceProviders/DownloadPermissionsAdjusterServiceProvider.php b/src/Internal/DependencyManagement/ServiceProviders/DownloadPermissionsAdjusterServiceProvider.php new file mode 100644 index 00000000000..126e689f783 --- /dev/null +++ b/src/Internal/DependencyManagement/ServiceProviders/DownloadPermissionsAdjusterServiceProvider.php @@ -0,0 +1,31 @@ +share( DownloadPermissionsAdjuster::class ); + } +} diff --git a/src/Internal/DependencyManagement/ServiceProviders/ProxiesServiceProvider.php b/src/Internal/DependencyManagement/ServiceProviders/ProxiesServiceProvider.php index b790397d35c..4e2823d5e05 100644 --- a/src/Internal/DependencyManagement/ServiceProviders/ProxiesServiceProvider.php +++ b/src/Internal/DependencyManagement/ServiceProviders/ProxiesServiceProvider.php @@ -1,6 +1,6 @@ downloads_data_store = WC()->get_instance_of( \WC_Data_Store::class, 'customer-download'); + add_action('adjust_download_permissions', array($this, 'adjust_download_permissions'), 10, 1); + } + + /** + * Initialize the hooks used by this class. + */ + public static function init_hooks() + { + add_action('adjust_download_permissions', array(wc_get_container()->get(self::class), 'adjust_download_permissions'), 10, 1); + } + + /** + * Schedule a download permissions adjustment for a product if necessary. + * This should be executed whenever a product is saved. + * + * @param \WC_Product $product The product to schedule a download permission adjustments for. + */ + public function maybe_schedule_adjust_download_permissions( \WC_Product $product ) { + $children_ids = $product->get_children(); + if (!$children_ids) { + return; + } + + $scheduled_action_args = array( $product->get_id() ); + + $already_scheduled_actions = as_get_scheduled_actions( + array( + 'hook' => 'adjust_download_permissions', + 'args' => $scheduled_action_args, + 'status' => \ActionScheduler_Store::STATUS_PENDING + ) + ); + + if(empty($already_scheduled_actions)) { + as_schedule_single_action(time() + 1, 'adjust_download_permissions', $scheduled_action_args); + } + } + + /** + * Create additional download permissions for variations if necessary. + * + * When a simple downloadable product is converted to a variable product, + * existing download permissions are still present in the database but they don't apply anymore. + * This method creates additional download permissions for the variations based on + * the old existing ones for the main product. + * + * The procedure is as follows. For each existing download permission for the parent product, + * check if there's any variation offering the same file for download (the file URL, not name, is checked). + * If that is found, check if an equivalent permission exists (equivalent means for the same file and with + * the same order id and customer id). If no equivalent permission exists, create it. + * + * @param int $product_id The id of the product to check permissions for. + */ + public function adjust_download_permissions( int $product_id ) { + $product = wc_get_product( $product_id ); + + $children_ids = $product->get_children(); + if (!$children_ids) { + return; + } + + $parent_downloads = $this->get_download_files_and_permissions($product); + if (!$parent_downloads) { + return; + } + + $children_with_downloads = array(); + foreach ( $children_ids as $child_id ) { + $child = wc_get_product( $child_id ); + $children_with_downloads[ $child_id ] = $this->get_download_files_and_permissions( $child ); + } + + foreach ( $parent_downloads['permission_data_by_file_order_user'] as $parent_file_order_and_user => $parent_download_data ) { + foreach ( $children_with_downloads as $child_id => $child_download_data ) { + $file_url = $parent_download_data['file']; + + $must_create_permission = + // The variation offers the same file as the parent for download... + in_array( $file_url, array_keys( $child_download_data['download_ids_by_file_url'] ), true ) && + // ...but no equivalent download permission (same file URL, order id and user id) exists. + ! array_key_exists( $parent_file_order_and_user, $child_download_data['permission_data_by_file_order_user'] ); + + if ( $must_create_permission ) { + // The new child download permission is a copy of the parent's, + // but with the product and download ids changed to match those of the variation. + $new_download_data = $parent_download_data['data']; + $new_download_data['product_id'] = $child_id; + $new_download_data['download_id'] = $child_download_data['download_ids_by_file_url'][ $file_url ]; + $this->downloads_data_store->create_from_data( $new_download_data ); + } + } + } + } + + /** + * Get the existing downloadable files and download permissions for a given product. + * The returned value is an array with two keys: + * + * - download_ids_by_file_url: an associative array of file url => download_id. + * - permission_data_by_file_order_user: an associative array where key is "file_url:customer_id:order_id" and value is the full permission data set. + * + * @param \WC_Product $product The product to get the downloadable files and permissions for. + * @return array[] Information about the downloadable files and permissions for the product. + */ + private function get_download_files_and_permissions( \WC_Product $product ) { + $result = array( + 'permission_data_by_file_order_user' => array(), + 'download_ids_by_file_url' => array(), + ); + + $downloads = $product->get_downloads(); + foreach ( $downloads as $download ) { + $result['download_ids_by_file_url'][ $download->get_file() ] = $download->get_id(); + } + + $permissions = $this->downloads_data_store->get_downloads( array( 'product_id' => $product->get_id() ) ); + foreach ( $permissions as $permission ) { + $permission_data = (array) $permission->data; + $file = $downloads[ $permission_data['download_id'] ]->get_file(); + $data = array( + 'file' => $file, + 'data' => (array) $permission->data, + ); + $result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data; + } + + return $result; + } +} diff --git a/src/Proxies/LegacyProxy.php b/src/Proxies/LegacyProxy.php index fff4ee62986..b4bce3e1bba 100644 --- a/src/Proxies/LegacyProxy.php +++ b/src/Proxies/LegacyProxy.php @@ -52,6 +52,11 @@ class LegacyProxy { return $class_name::instance( ...$args ); } + // If the class has a "load" method, use it. + if ( method_exists( $class_name, 'load' ) ) { + return $class_name::load( ...$args ); + } + // Fallback to simply creating a new instance of the class. return new $class_name( ...$args ); } From 36d6ff4c525b60faf41094d057a5d7d4195470c5 Mon Sep 17 00:00:00 2001 From: Menaka S Date: Mon, 14 Dec 2020 14:50:29 +0530 Subject: [PATCH 006/120] Add a function to get order counts by payment method --- includes/class-wc-tracker.php | 50 ++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 27a36961c7f..9fd07cc9977 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -371,11 +371,12 @@ class WC_Tracker { * @return array */ private static function get_orders() { - $order_dates = self::get_order_dates(); - $order_counts = self::get_order_counts(); - $order_totals = self::get_order_totals(); + $order_dates = self::get_order_dates(); + $order_counts = self::get_order_counts(); + $order_totals = self::get_order_totals(); + $order_payment_methods = self::get_order_counts_by_payment_method(); - return array_merge( $order_dates, $order_counts, $order_totals ); + return array_merge( $order_dates, $order_counts, $order_totals, $order_payment_methods ); } /** @@ -540,6 +541,47 @@ class WC_Tracker { return array_filter( (array) get_option( 'woocommerce_tracker_ua', array() ) ); } + /** + * Get order counts by payment method + * + * @return array + */ + public static function get_order_counts_by_payment_method() { + global $wpdb; + + $orders_by_payment_method = $wpdb->get_results( + " + SELECT + order_gateway.payment_method AS payment_method, + COUNT( id ) AS orders_count + FROM ( + SELECT + orders.ID AS id, + MAX( + CASE + WHEN meta_key = '_payment_method' + THEN meta_value + END + ) AS payment_method + FROM {$wpdb->prefix}posts AS orders + LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID + WHERE orders.post_status in ( 'wc-completed', 'wc-processing', 'wc-refunded' ) + GROUP BY orders.ID + ) AS order_gateway + WHERE payment_method IS NOT NULL + GROUP BY payment_method + ORDER BY orders_count DESC + " + ); + + foreach ( $orders_by_payment_method as $orders_count ) { + $method = 'gateway_' . $orders_count->payment_method; + $order_counts_by_payment_method[ $method ] = $orders_count->orders_count; + } + + return $order_counts_by_payment_method; + } + /** * Get order totals * From 8d83b47c278915326f9f7cd0fcc30ac3e82dee7d Mon Sep 17 00:00:00 2001 From: Menaka S Date: Tue, 15 Dec 2020 06:24:36 +0530 Subject: [PATCH 007/120] Check if post_type is shop_order --- includes/class-wc-tracker.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 9fd07cc9977..71735d0dd17 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -565,7 +565,9 @@ class WC_Tracker { ) AS payment_method FROM {$wpdb->prefix}posts AS orders LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID - WHERE orders.post_status in ( 'wc-completed', 'wc-processing', 'wc-refunded' ) + WHERE + orders.post_type = 'shop_order' AND + orders.post_status in ( 'wc-completed', 'wc-processing', 'wc-refunded' ) GROUP BY orders.ID ) AS order_gateway WHERE payment_method IS NOT NULL @@ -575,7 +577,7 @@ class WC_Tracker { ); foreach ( $orders_by_payment_method as $orders_count ) { - $method = 'gateway_' . $orders_count->payment_method; + $method = 'gateway_' . $orders_count->payment_method; $order_counts_by_payment_method[ $method ] = $orders_count->orders_count; } From 0aee0a0848574fce7664f9670e77d7c93089d369 Mon Sep 17 00:00:00 2001 From: Menaka S Date: Wed, 23 Dec 2020 06:43:32 +0530 Subject: [PATCH 008/120] Use wc_get_orders() to add order dates for tracker --- includes/class-wc-tracker.php | 45 ++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 71735d0dd17..7eddafda32f 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -366,17 +366,50 @@ class WC_Tracker { } /** - * Combine all order data. + * Get all order data. * * @return array */ private static function get_orders() { - $order_dates = self::get_order_dates(); - $order_counts = self::get_order_counts(); - $order_totals = self::get_order_totals(); - $order_payment_methods = self::get_order_counts_by_payment_method(); + $orders = wc_get_orders( + array( + 'type' => array( 'shop_order', 'shop_order_refund' ), + 'customer' => '', + 'posts_per_page' => -1, + ) + ); - return array_merge( $order_dates, $order_counts, $order_totals, $order_payment_methods ); + $first = current_time( 'timestamp', true ); + $last = 0; + $processing_first = current_time( 'timestamp', true ); + $processing_last = 0; + + foreach ( $orders as $order ) { + $date_created = (int) $order->get_date_created()->getTimestamp(); + $type = $order->get_type(); + $status = $order->get_status(); + + if ( 'shop_order' == $type ) { + if ( "completed" == $status and $date_created < $first ) { + $first = $date_created; + } + if ( "completed" == $status and $date_created > $last ) { + $last = $date_created; + } + if ( "processing" == $status and $date_created < $processing_first ) { + $processing_first = $date_created; + } + if ( "processing" == $status and $date_created > $processing_last ) { + $processing_last = $date_created; + } + } + } + $order_data['first'] = gmdate( 'Y-m-d H:i:s', $first ); + $order_data['last'] = gmdate( 'Y-m-d H:i:s', $last ); + $order_data['processing_first'] = gmdate( 'Y-m-d H:i:s', $processing_first ); + $order_data['processing_last'] = gmdate( 'Y-m-d H:i:s', $processing_last ); + + return $order_data; } /** From 1a6835019a2b4474af466b9aae9819dfd2544141 Mon Sep 17 00:00:00 2001 From: Menaka S Date: Wed, 23 Dec 2020 06:47:39 +0530 Subject: [PATCH 009/120] Add order counts by status --- includes/class-wc-tracker.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 7eddafda32f..a50e6538575 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -390,6 +390,8 @@ class WC_Tracker { $status = $order->get_status(); if ( 'shop_order' == $type ) { + + // Find the first and last order dates for completed and processing statuses if ( "completed" == $status and $date_created < $first ) { $first = $date_created; } @@ -402,6 +404,15 @@ class WC_Tracker { if ( "processing" == $status and $date_created > $processing_last ) { $processing_last = $date_created; } + + // Get order counts by status + $status = "wc-" . $status; + + if ( ! isset( $order_data[ $status ] ) ) { + $order_data[ $status ] = 1; + } else { + $order_data[ $status ] += 1; + } } } $order_data['first'] = gmdate( 'Y-m-d H:i:s', $first ); From e9804ea7173bf037f98901b8a445e5da8644f7d7 Mon Sep 17 00:00:00 2001 From: Menaka S Date: Wed, 23 Dec 2020 06:50:15 +0530 Subject: [PATCH 010/120] Add order counts by gateway --- includes/class-wc-tracker.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index a50e6538575..2176a0e5e24 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -413,6 +413,18 @@ class WC_Tracker { } else { $order_data[ $status ] += 1; } + + $gateway = $order->get_payment_method(); + + if ( ! empty( $gateway ) and in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) { + $gateway = "gateway_" . $gateway; + + if ( ! isset( $order_data[ $gateway ] ) ) { + $order_data[ $gateway ] = 1; + } else { + $order_data[ $gateway ] += 1; + } + } } } $order_data['first'] = gmdate( 'Y-m-d H:i:s', $first ); From 092c36b71b2800fa965bdcf81bbc0a5d8e2c062b Mon Sep 17 00:00:00 2001 From: Menaka S Date: Wed, 23 Dec 2020 06:54:55 +0530 Subject: [PATCH 011/120] Get order total values --- includes/class-wc-tracker.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 2176a0e5e24..63c3ebbd582 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -414,6 +414,7 @@ class WC_Tracker { $order_data[ $status ] += 1; } + // Count number of orders by gateway used $gateway = $order->get_payment_method(); if ( ! empty( $gateway ) and in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) { @@ -426,7 +427,30 @@ class WC_Tracker { } } } + else { + // If it is a refunded order (shop_order_refunnd type), add the prefix as this prefix gets + // added midway in the if clause + $status = "wc-" . $status; + } + + // Calculate the gross total for 'completed' and 'processing' orders + $total = $order->get_total(); + + if ( in_array( $status, array( 'wc-completed', 'wc-refunded' ) ) ) { + if ( ! isset( $order_data['gross'] ) ) { + $order_data['gross'] = $total; + } else { + $order_data['gross'] += $total; + } + } elseif ( 'wc-processing' == $status ) { + if ( ! isset( $order_data['processing_gross'] ) ) { + $order_data['processing_gross'] = $total; + } else { + $order_data['processing_gross'] += $total; + } + } } + $order_data['first'] = gmdate( 'Y-m-d H:i:s', $first ); $order_data['last'] = gmdate( 'Y-m-d H:i:s', $last ); $order_data['processing_first'] = gmdate( 'Y-m-d H:i:s', $processing_first ); From 88dd6473d41091b039af5f54bd6334f465500dff Mon Sep 17 00:00:00 2001 From: Menaka S Date: Wed, 23 Dec 2020 07:40:44 +0530 Subject: [PATCH 012/120] Some fixes for coding standards --- includes/class-wc-tracker.php | 37 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 63c3ebbd582..8cc568be8d6 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -379,9 +379,9 @@ class WC_Tracker { ) ); - $first = current_time( 'timestamp', true ); + $first = time(); $last = 0; - $processing_first = current_time( 'timestamp', true ); + $processing_first = time(); $processing_last = 0; foreach ( $orders as $order ) { @@ -391,22 +391,22 @@ class WC_Tracker { if ( 'shop_order' == $type ) { - // Find the first and last order dates for completed and processing statuses - if ( "completed" == $status and $date_created < $first ) { + // Find the first and last order dates for completed and processing statuses. + if ( 'completed' == $status && $date_created < $first ) { $first = $date_created; } - if ( "completed" == $status and $date_created > $last ) { + if ( 'completed' == $status && $date_created > $last ) { $last = $date_created; } - if ( "processing" == $status and $date_created < $processing_first ) { + if ( 'processing' == $status && $date_created < $processing_first ) { $processing_first = $date_created; } - if ( "processing" == $status and $date_created > $processing_last ) { + if ( 'processing' == $status && $date_created > $processing_last ) { $processing_last = $date_created; } - // Get order counts by status - $status = "wc-" . $status; + // Get order counts by status. + $status = 'wc-' . $status; if ( ! isset( $order_data[ $status ] ) ) { $order_data[ $status ] = 1; @@ -414,11 +414,11 @@ class WC_Tracker { $order_data[ $status ] += 1; } - // Count number of orders by gateway used + // Count number of orders by gateway used. $gateway = $order->get_payment_method(); - if ( ! empty( $gateway ) and in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) { - $gateway = "gateway_" . $gateway; + if ( ! empty( $gateway ) && in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) { + $gateway = 'gateway_' . $gateway; if ( ! isset( $order_data[ $gateway ] ) ) { $order_data[ $gateway ] = 1; @@ -426,24 +426,23 @@ class WC_Tracker { $order_data[ $gateway ] += 1; } } - } - else { + } else { // If it is a refunded order (shop_order_refunnd type), add the prefix as this prefix gets - // added midway in the if clause - $status = "wc-" . $status; + // added midway in the if clause. + $status = 'wc-' . $status; } - // Calculate the gross total for 'completed' and 'processing' orders + // Calculate the gross total for 'completed' and 'processing' orders. $total = $order->get_total(); if ( in_array( $status, array( 'wc-completed', 'wc-refunded' ) ) ) { - if ( ! isset( $order_data['gross'] ) ) { + if ( ! isset( $order_data['gross'] ) ) { $order_data['gross'] = $total; } else { $order_data['gross'] += $total; } } elseif ( 'wc-processing' == $status ) { - if ( ! isset( $order_data['processing_gross'] ) ) { + if ( ! isset( $order_data['processing_gross'] ) ) { $order_data['processing_gross'] = $total; } else { $order_data['processing_gross'] += $total; From 4b62b2358060ae537c9fc1c0c4291be37ce06060 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Wed, 23 Dec 2020 15:04:14 +0100 Subject: [PATCH 013/120] Add new e2e test shopper cart apply coupon --- tests/e2e/core-tests/specs/index.js | 3 + .../shopper/front-end-cart-coupons.test.js | 91 +++++++++++++++++++ .../e2e/specs/front-end/test-cart-coupons.js | 6 ++ tests/e2e/utils/src/components.js | 7 +- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js create mode 100644 tests/e2e/specs/front-end/test-cart-coupons.js diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 8c8bd03b152..5a40f310afb 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -9,6 +9,7 @@ const { runOnboardingFlowTest, runTaskListTest } = require( './activate-and-setu const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ); // Shopper tests +const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test') const runCartPageTest = require( './shopper/front-end-cart.test' ); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); @@ -33,6 +34,7 @@ const runSetupOnboardingTests = () => { }; const runShopperTests = () => { + runCartApplyCouponsTest(); runCartPageTest(); runCheckoutPageTest(); runMyAccountPageTest(); @@ -58,6 +60,7 @@ module.exports = { runTaskListTest, runInitialStoreSettingsTest, runSetupOnboardingTests, + runCartApplyCouponsTest, runCartPageTest, runCheckoutPageTest, runMyAccountPageTest, diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js new file mode 100644 index 00000000000..1a0bc994121 --- /dev/null +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -0,0 +1,91 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */ +/** + * Internal dependencies + */ +const { + shopper, + merchant, + createCoupon, + createSimpleProduct +} = require( '@woocommerce/e2e-utils' ); + +/** + * External dependencies + */ +const { + it, + describe, + beforeAll, +} = require( '@jest/globals' ); + +const runCartApplyCouponsTest = () => { + describe('Cart applying coupons', () => { + beforeAll(async () => { + await merchant.login(); + await createSimpleProduct(); + await createCoupon('Fixed cart discount'); + await createCoupon('Percentage discount', '50'); + await createCoupon('Fixed product discount'); + await merchant.logout(); + }); + + it('allows customer to apply a fixed cart coupon in the cart', async () => { + await shopper.goToShop(); + await shopper.addToCartFromShopPage('Simple product'); + + await shopper.goToCart(); + await shopper.productIsInCart('Simple product'); + + // Apply Fixed cart discount coupon + await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + + // Wait for page to expand total calculations to avoid flakyness + await page.waitForSelector('.order-total'); + + // Verify discount applied and order total + await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); + + // Remove coupon + await expect(page).toClick('.woocommerce-remove-coupon'); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Apply Percentage discount coupon + await expect(page).toFill('#coupon_code', 'Code-Percentage discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'}); + + // Remove coupon + await expect(page).toClick('.woocommerce-remove-coupon'); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Apply Fixed product discount coupon + await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); + + // Try to apply the same coupon + await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await expect(page).toMatchElement('.woocommerce-error', { text: 'Coupon code already applied!' }); + + // Try to apply multiple coupons + await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await expect(page).toMatchElement('.order-total .amount', {text: '$0.00'}); + + // Remove coupon + await expect(page).toClick('.woocommerce-remove-coupon'); + await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); + }); + }); +}; + +module.exports = runCartApplyCouponsTest; diff --git a/tests/e2e/specs/front-end/test-cart-coupons.js b/tests/e2e/specs/front-end/test-cart-coupons.js new file mode 100644 index 00000000000..66c70adbb88 --- /dev/null +++ b/tests/e2e/specs/front-end/test-cart-coupons.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runCartApplyCouponsTest } = require( '@woocommerce/e2e-core-tests' ); + +runCartApplyCouponsTest(); diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 09d467011d5..848226a5bcd 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -401,18 +401,19 @@ const addProductToOrder = async ( orderId, productName ) => { /** * Creates a basic coupon with the provided coupon amount. Returns the coupon code. * + * @param discountType Type of a coupon. Defaults to Fixed cart discount. * @param couponAmount Amount to be applied. Defaults to 5. */ -const createCoupon = async ( couponAmount = '5' ) => { +const createCoupon = async ( discountType = 'Fixed cart discount', couponAmount = '5' ) => { await merchant.openNewCoupon(); // Fill in coupon code - let couponCode = 'code-' + new Date().getTime().toString(); + let couponCode = 'Code-' + discountType; await expect(page).toFill( '#title', couponCode ); // Set general coupon data await clickTab( 'General' ); - await expect(page).toSelect( '#discount_type', 'Fixed cart discount' ); + await expect(page).toSelect( '#discount_type', discountType ); await expect(page).toFill( '#coupon_amount', couponAmount ); // Publish coupon From a033be3fdebc9407da33ff27e91558863649e95a Mon Sep 17 00:00:00 2001 From: Veljko V Date: Wed, 23 Dec 2020 16:51:25 +0100 Subject: [PATCH 014/120] Add e2e test shopper checkout apply coupon --- tests/e2e/core-tests/specs/index.js | 3 + .../front-end-checkout-coupons.test.js | 101 ++++++++++++++++++ .../specs/front-end/test-checkout-coupons.js | 6 ++ tests/e2e/utils/src/components.js | 7 +- 4 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js create mode 100644 tests/e2e/specs/front-end/test-checkout-coupons.js diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 8c8bd03b152..4ced19475d5 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -10,6 +10,7 @@ const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ) // Shopper tests const runCartPageTest = require( './shopper/front-end-cart.test' ); +const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test') const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); @@ -34,6 +35,7 @@ const runSetupOnboardingTests = () => { const runShopperTests = () => { runCartPageTest(); + runCheckoutApplyCouponsTest(); runCheckoutPageTest(); runMyAccountPageTest(); runSingleProductPageTest(); @@ -59,6 +61,7 @@ module.exports = { runInitialStoreSettingsTest, runSetupOnboardingTests, runCartPageTest, + runCheckoutApplyCouponsTest, runCheckoutPageTest, runMyAccountPageTest, runSingleProductPageTest, diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js new file mode 100644 index 00000000000..7d20c2b5486 --- /dev/null +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -0,0 +1,101 @@ +/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */ +/** + * Internal dependencies + */ +const { + shopper, + merchant, + createCoupon, + createSimpleProduct +} = require( '@woocommerce/e2e-utils' ); + +/** + * External dependencies + */ +const { + it, + describe, + beforeAll, +} = require( '@jest/globals' ); + +const runCheckoutApplyCouponsTest = () => { + describe('Checkout applying coupons', () => { + beforeAll(async () => { + await merchant.login(); + await createSimpleProduct(); + await createCoupon('Fixed cart discount'); + await createCoupon('Percentage discount', '50'); + await createCoupon('Fixed product discount'); + await merchant.logout(); + }); + + it('allows customer to apply coupons in the checkout', async () => { + await shopper.goToShop(); + await shopper.addToCartFromShopPage('Simple product'); + await shopper.goToCart(); + await shopper.productIsInCart('Simple product'); + await shopper.goToCheckout(); + + // Apply Fixed cart discount coupon + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + + // Wait for page to expand total calculations to avoid flakyness + await page.waitForSelector('.order-total'); + + // Verify discount applied and order total + await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); + await page.waitForSelector('.order-total .amount', {text: '$4.99'}); + + // Remove coupon + await expect(page).toClick('.coupon-code-fixed-cart-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Apply Percentage discount coupon + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toFill('#coupon_code', 'Code-Percentage discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await page.waitForSelector('.cart-discount .amount', {text: '$4.99'}); + await page.waitForSelector('.order-total .amount', {text: '$5.00'}); + + // Remove coupon + await expect(page).toClick('.coupon-code-percentage-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Apply Fixed product discount coupon + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); + await page.waitForSelector('.order-total .amount', {text: '$4.99'}); + + // Try to apply the same coupon + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' }); + + // Try to apply multiple coupons + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toClick('button', {text: 'Apply coupon'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await page.waitForSelector('.order-total .amount', {text: '$0.00'}); + + // Remove coupon + await expect(page).toClick('.coupon-code-fixed-cart-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + await expect(page).toClick('.coupon-code-fixed-product-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Verify the total amount after all coupons removal + await page.waitForSelector('.order-total .amount', {text: '$9.99'}); + }); + }); +}; + +module.exports = runCheckoutApplyCouponsTest; diff --git a/tests/e2e/specs/front-end/test-checkout-coupons.js b/tests/e2e/specs/front-end/test-checkout-coupons.js new file mode 100644 index 00000000000..6bc6e858302 --- /dev/null +++ b/tests/e2e/specs/front-end/test-checkout-coupons.js @@ -0,0 +1,6 @@ +/* + * Internal dependencies + */ +const { runCheckoutApplyCouponsTest } = require( '@woocommerce/e2e-core-tests' ); + +runCheckoutApplyCouponsTest(); diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 09d467011d5..848226a5bcd 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -401,18 +401,19 @@ const addProductToOrder = async ( orderId, productName ) => { /** * Creates a basic coupon with the provided coupon amount. Returns the coupon code. * + * @param discountType Type of a coupon. Defaults to Fixed cart discount. * @param couponAmount Amount to be applied. Defaults to 5. */ -const createCoupon = async ( couponAmount = '5' ) => { +const createCoupon = async ( discountType = 'Fixed cart discount', couponAmount = '5' ) => { await merchant.openNewCoupon(); // Fill in coupon code - let couponCode = 'code-' + new Date().getTime().toString(); + let couponCode = 'Code-' + discountType; await expect(page).toFill( '#title', couponCode ); // Set general coupon data await clickTab( 'General' ); - await expect(page).toSelect( '#discount_type', 'Fixed cart discount' ); + await expect(page).toSelect( '#discount_type', discountType ); await expect(page).toFill( '#coupon_amount', couponAmount ); // Publish coupon From e6ebfc51541ec2ebd948ac56cf1ebced2a6836d9 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Wed, 23 Dec 2020 17:19:48 +0100 Subject: [PATCH 015/120] Update existing code to be headless compatible --- .../shopper/front-end-cart-coupons.test.js | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js index 1a0bc994121..808e62f5af9 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -32,58 +32,62 @@ const runCartApplyCouponsTest = () => { it('allows customer to apply a fixed cart coupon in the cart', async () => { await shopper.goToShop(); await shopper.addToCartFromShopPage('Simple product'); - await shopper.goToCart(); await shopper.productIsInCart('Simple product'); // Apply Fixed cart discount coupon await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Wait for page to expand total calculations to avoid flakyness await page.waitForSelector('.order-total'); // Verify discount applied and order total - await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); - await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); + await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); + await page.waitForSelector('.order-total .amount', {text: '$4.99'}); // Remove coupon - await expect(page).toClick('.woocommerce-remove-coupon'); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Percentage discount coupon await expect(page).toFill('#coupon_code', 'Code-Percentage discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - await expect(page).toMatchElement('.cart-discount .amount', {text: '$4.99'}); - await expect(page).toMatchElement('.order-total .amount', {text: '$5.00'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await page.waitForSelector('.cart-discount .amount', {text: '$4.99'}); + await page.waitForSelector('.order-total .amount', {text: '$5.00'}); // Remove coupon - await expect(page).toClick('.woocommerce-remove-coupon'); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Fixed product discount coupon await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - await expect(page).toMatchElement('.cart-discount .amount', {text: '$5.00'}); - await expect(page).toMatchElement('.order-total .amount', {text: '$4.99'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); + await page.waitForSelector('.order-total .amount', {text: '$4.99'}); // Try to apply the same coupon await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); - await expect(page).toMatchElement('.woocommerce-error', { text: 'Coupon code already applied!' }); + await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' }); // Try to apply multiple coupons await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - await expect(page).toMatchElement('.order-total .amount', {text: '$0.00'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); + await page.waitForSelector('.order-total .amount', {text: '$0.00'}); // Remove coupon - await expect(page).toClick('.woocommerce-remove-coupon'); - await expect(page).toMatchElement('.woocommerce-message', {text: 'Coupon has been removed.'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Verify the total amount after all coupons removal + await page.waitForSelector('.order-total .amount', {text: '$9.99'}); }); }); }; From 314295c785f3c175e930658db998f14a45b940b8 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Wed, 23 Dec 2020 17:24:34 +0100 Subject: [PATCH 016/120] Update existing code to be more successful in headless --- .../specs/shopper/front-end-checkout-coupons.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index 7d20c2b5486..fc7373f4479 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -50,7 +50,7 @@ const runCheckoutApplyCouponsTest = () => { await page.waitForSelector('.order-total .amount', {text: '$4.99'}); // Remove coupon - await expect(page).toClick('.coupon-code-fixed-cart-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Percentage discount coupon @@ -62,7 +62,7 @@ const runCheckoutApplyCouponsTest = () => { await page.waitForSelector('.order-total .amount', {text: '$5.00'}); // Remove coupon - await expect(page).toClick('.coupon-code-percentage-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Fixed product discount coupon @@ -87,9 +87,9 @@ const runCheckoutApplyCouponsTest = () => { await page.waitForSelector('.order-total .amount', {text: '$0.00'}); // Remove coupon - await expect(page).toClick('.coupon-code-fixed-cart-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - await expect(page).toClick('.coupon-code-fixed-product-discount .woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Verify the total amount after all coupons removal From f4be2afa2a2c2f4ddaa6fb91d6f9e57ec5b36888 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 24 Dec 2020 10:54:21 +0100 Subject: [PATCH 017/120] Updated changelogs to utils and core tests --- tests/e2e/core-tests/CHANGELOG.md | 1 + tests/e2e/utils/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 12363b5ad98..3ae9b1c2a16 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -4,6 +4,7 @@ - Merchant Order Status Filter tests - Merchant Order Refund tests - Merchant Apply Coupon tests +- Shopper Cart Apply Coupon ## Fixed diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index ac35c538321..48f5ad35391 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -17,6 +17,7 @@ - Deprecated `StoreOwnerFlow`, `CustomerFlow` in favour of `merchant`,`shopper` - `createSimpleOrder( status )` returns the ID of the order that was created +- Updated `createCoupon( couponAmount )` component to be more dynamic in functioning, now you can use any coupon discount type in tests # 0.1.1 From 3c85972780b289f71ac1659e745d4479df35a1e3 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 24 Dec 2020 10:55:20 +0100 Subject: [PATCH 018/120] Updated changelogs to utils and core tests --- tests/e2e/core-tests/CHANGELOG.md | 1 + tests/e2e/utils/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 12363b5ad98..6309052de51 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -4,6 +4,7 @@ - Merchant Order Status Filter tests - Merchant Order Refund tests - Merchant Apply Coupon tests +- Shopper Checkout Apply Coupon ## Fixed diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index ac35c538321..48f5ad35391 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -17,6 +17,7 @@ - Deprecated `StoreOwnerFlow`, `CustomerFlow` in favour of `merchant`,`shopper` - `createSimpleOrder( status )` returns the ID of the order that was created +- Updated `createCoupon( couponAmount )` component to be more dynamic in functioning, now you can use any coupon discount type in tests # 0.1.1 From 121099afe7c3f29d0d0bfbd7716a3f75e936290b Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 24 Dec 2020 11:53:45 +0100 Subject: [PATCH 019/120] Fix problem in headless by adding uiUnblocked --- .../specs/shopper/front-end-cart-coupons.test.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js index 808e62f5af9..64d7dc40ad0 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -6,7 +6,8 @@ const { shopper, merchant, createCoupon, - createSimpleProduct + createSimpleProduct, + uiUnblocked } = require( '@woocommerce/e2e-utils' ); /** @@ -38,6 +39,7 @@ const runCartApplyCouponsTest = () => { // Apply Fixed cart discount coupon await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Wait for page to expand total calculations to avoid flakyness @@ -49,22 +51,26 @@ const runCartApplyCouponsTest = () => { // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Percentage discount coupon await expect(page).toFill('#coupon_code', 'Code-Percentage discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$4.99'}); await page.waitForSelector('.order-total .amount', {text: '$5.00'}); // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Fixed product discount coupon await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); await page.waitForSelector('.order-total .amount', {text: '$4.99'}); @@ -72,18 +78,22 @@ const runCartApplyCouponsTest = () => { // Try to apply the same coupon await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' }); // Try to apply multiple coupons await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.order-total .amount', {text: '$0.00'}); // Remove coupon - await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Verify the total amount after all coupons removal From a5175c75d6741e74d327a9040b1c9ec2c6f575fc Mon Sep 17 00:00:00 2001 From: Veljko V Date: Thu, 24 Dec 2020 11:57:17 +0100 Subject: [PATCH 020/120] Fix problem in headless by adding uiUnblocked --- .../front-end-checkout-coupons.test.js | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index fc7373f4479..da89375b9fe 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -6,7 +6,8 @@ const { shopper, merchant, createCoupon, - createSimpleProduct + createSimpleProduct, + uiUnblocked } = require( '@woocommerce/e2e-utils' ); /** @@ -37,9 +38,11 @@ const runCheckoutApplyCouponsTest = () => { await shopper.goToCheckout(); // Apply Fixed cart discount coupon - await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await uiUnblocked(); await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); // Wait for page to expand total calculations to avoid flakyness @@ -51,45 +54,57 @@ const runCheckoutApplyCouponsTest = () => { // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Percentage discount coupon - await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await uiUnblocked(); await expect(page).toFill('#coupon_code', 'Code-Percentage discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$4.99'}); await page.waitForSelector('.order-total .amount', {text: '$5.00'}); // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Fixed product discount coupon - await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await uiUnblocked(); await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); await page.waitForSelector('.order-total .amount', {text: '$4.99'}); // Try to apply the same coupon - await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await uiUnblocked(); await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' }); // Try to apply multiple coupons - await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await expect(page).toClick('a', {text: 'Click here to enter your code'}); + await uiUnblocked(); await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); await expect(page).toClick('button', {text: 'Apply coupon'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.order-total .amount', {text: '$0.00'}); // Remove coupon - await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); + await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Verify the total amount after all coupons removal From c81df313b2fddf6a54750e731eb6db109b2ad829 Mon Sep 17 00:00:00 2001 From: budzanowski Date: Mon, 28 Dec 2020 16:19:51 +0100 Subject: [PATCH 021/120] Add block information for woocommerce pages report. --- ...ss-wc-rest-system-status-v2-controller.php | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php index e2d6569140b..069808a3149 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php @@ -1162,7 +1162,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { /** * Returns a mini-report on WC pages and if they are configured correctly: - * Present, visible, and including the correct shortcode. + * Present, visible, and including the correct shortcode or block. * * @return array */ @@ -1172,22 +1172,27 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { _x( 'Shop base', 'Page setting', 'woocommerce' ) => array( 'option' => 'woocommerce_shop_page_id', 'shortcode' => '', + 'block' => '', ), _x( 'Cart', 'Page setting', 'woocommerce' ) => array( 'option' => 'woocommerce_cart_page_id', 'shortcode' => '[' . apply_filters( 'woocommerce_cart_shortcode_tag', 'woocommerce_cart' ) . ']', + 'block' => apply_filters( 'woocommerce_cart_block_tag', 'woocommerce/cart' ), ), _x( 'Checkout', 'Page setting', 'woocommerce' ) => array( 'option' => 'woocommerce_checkout_page_id', 'shortcode' => '[' . apply_filters( 'woocommerce_checkout_shortcode_tag', 'woocommerce_checkout' ) . ']', + 'block' => apply_filters( 'woocommerce_checkout_block_tag', 'woocommerce/checkout' ), ), _x( 'My account', 'Page setting', 'woocommerce' ) => array( 'option' => 'woocommerce_myaccount_page_id', 'shortcode' => '[' . apply_filters( 'woocommerce_my_account_shortcode_tag', 'woocommerce_my_account' ) . ']', + 'block' => '', ), _x( 'Terms and conditions', 'Page setting', 'woocommerce' ) => array( 'option' => 'woocommerce_terms_page_id', 'shortcode' => '', + 'block' => '', ), ); @@ -1199,6 +1204,8 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { $page_visible = false; $shortcode_present = false; $shortcode_required = false; + $block_present = false; + $block_required = false; // Page checks. if ( $page_id ) { @@ -1220,6 +1227,16 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { } } + // Block checks. + if ( $values['block'] && get_post( $page_id ) ) { + $block_required = true; + $page = get_post( $page_id ); + $page_info = WC_Tracker::get_block_tracker_data( $values['block'], $page_name ); + if ( 'Yes' === $page_info['page_contains_block'] ) { + $block_present = true; + } + } + // Wrap up our findings into an output array. $pages_output[] = array( 'page_name' => $page_name, @@ -1228,8 +1245,11 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { 'page_exists' => $page_exists, 'page_visible' => $page_visible, 'shortcode' => $values['shortcode'], + 'block' => $values['block'], 'shortcode_required' => $shortcode_required, 'shortcode_present' => $shortcode_present, + 'block_present' => $block_present, + 'block_required' => $block_required, ); } From 32c1c9c9633c7ca92609ff06861ea7478385bf27 Mon Sep 17 00:00:00 2001 From: budzanowski Date: Mon, 28 Dec 2020 16:27:31 +0100 Subject: [PATCH 022/120] Print missing block info in the status page. --- includes/admin/views/html-admin-page-status-report.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/includes/admin/views/html-admin-page-status-report.php b/includes/admin/views/html-admin-page-status-report.php index 5b3028a11c5..7b8dd084c09 100644 --- a/includes/admin/views/html-admin-page-status-report.php +++ b/includes/admin/views/html-admin-page-status-report.php @@ -844,10 +844,11 @@ if ( 0 < count( $dropins_mu_plugins['mu_plugins'] ) ) : echo ' ' . wp_kses_post( sprintf( __( 'Page visibility should be public', 'woocommerce' ), 'https://wordpress.org/support/article/content-visibility/' ) ) . ''; $found_error = true; } else { - // Shortcode check. - if ( $_page['shortcode_required'] ) { - if ( ! $_page['shortcode_present'] ) { - echo ' ' . sprintf( esc_html__( 'Page does not contain the shortcode.', 'woocommerce' ), esc_html( $_page['shortcode'] ) ) . ''; + // Shortcode and block check. + if ( $_page['shortcode_required'] || $_page['block_required'] ) { + if ( ! $_page['shortcode_present'] && ! $_page['block_present'] ) { + /* Translators: %1$s: shortcode text, %2$s: block slug. */ + echo ' ' . ( $_page['block_required'] ? sprintf( esc_html__( 'Page does not contain the %1$s shortcode or the %2$s block.', 'woocommerce' ), esc_html( $_page['shortcode'] ), esc_html( $_page['block'] ) ) : sprintf( esc_html__( 'Page does not contain the %s shortcode.', 'woocommerce' ), esc_html( $_page['shortcode'] ) ) ) . ''; /* phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped */ $found_error = true; } } From 5a21a4f71c88933e8a7c58975564e84c66a5b0c1 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Tue, 29 Dec 2020 14:31:46 -0300 Subject: [PATCH 023/120] Added merchant email note configuration This commit adds a merchant email note configuration option --- .../admin/settings/class-wc-settings-emails.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/includes/admin/settings/class-wc-settings-emails.php b/includes/admin/settings/class-wc-settings-emails.php index 9b4c524ebc5..b08b525156c 100644 --- a/includes/admin/settings/class-wc-settings-emails.php +++ b/includes/admin/settings/class-wc-settings-emails.php @@ -191,6 +191,22 @@ class WC_Settings_Emails extends WC_Settings_Page { 'id' => 'email_template_options', ), + array( + 'title' => __( 'Store management insights', 'woocommerce' ), + 'type' => 'title', + 'id' => 'email_merchant_notes', + ), + + array( + 'title' => __( 'Enable email insights', 'woocommerce' ), + 'desc' => __( 'Receive email notifications with additional guidance to complete the basic store setup and helpful insights.', 'woocommerce' ), + 'id' => 'woocommerce_merchant_email_notifications', + 'type' => 'checkbox', + 'checkboxgroup' => 'start', + 'default' => 'no', + 'autoload' => false, + ), + ) ); From 47c6d06a7725efddbc044f408a0164ef1bf579ae Mon Sep 17 00:00:00 2001 From: Ron Rennick Date: Tue, 29 Dec 2020 15:56:55 -0400 Subject: [PATCH 024/120] introduce evalAndClick() E2E utility function --- .../wp-admin-order-apply-coupon.test.js | 6 ++---- .../merchant/wp-admin-order-refund.test.js | 6 ++---- .../merchant/wp-admin-product-new.test.js | 7 +++---- tests/e2e/utils/CHANGELOG.md | 1 + tests/e2e/utils/src/page-utils.js | 18 ++++++++++++++++-- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js index 4319e8606cb..176c8ae2fc3 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js @@ -10,6 +10,7 @@ const { createCoupon, uiUnblocked, addProductToOrder, + evalAndClick, } = require( '@woocommerce/e2e-utils' ); const config = require( 'config' ); @@ -61,10 +62,7 @@ const runOrderApplyCouponTest = () => { // Make sure we have a coupon on the page to use await page.waitForSelector('.wc-used-coupons'); await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode }); - - // We need to use this here as `expect(page).toClick()` was unable to find the element - // See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219 - page.$eval('a.remove-coupon', elem => elem.click()); + await evalAndClick( 'a.remove-coupon' ); await uiUnblocked(); diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-refund.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-refund.test.js index 749d486a764..5b601771b0d 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-refund.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-refund.test.js @@ -11,6 +11,7 @@ const { verifyValueOfInputField, uiUnblocked, addProductToOrder, + evalAndClick, } = require( '@woocommerce/e2e-utils' ); const config = require( 'config' ); @@ -74,10 +75,7 @@ const runRefundOrderTest = () => { }); it('can delete an issued refund', async () => { - // We need to use this here as `expect(page).toClick()` was unable to find the element - // See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219 - page.$eval('a.delete_refund', elem => elem.click()); - + await evalAndClick( 'a.delete_refund' ); await uiUnblocked(); // Verify the refunded row item is no longer showing diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-product-new.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-product-new.test.js index dca3fc8129a..0e64d75af4d 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-product-new.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-product-new.test.js @@ -5,7 +5,8 @@ const { merchant, clickTab, - uiUnblocked + uiUnblocked, + evalAndClick, } = require( '@woocommerce/e2e-utils' ); /** @@ -113,9 +114,7 @@ const runAddVariableProductTest = () => { await expect(page).toSelect('select.variation_actions', 'Create variations from all attributes'); const firstDialog = await expect(page).toDisplayDialog(async () => { - // Using this technique since toClick() isn't working. - // See: https://github.com/GoogleChrome/puppeteer/issues/1805#issuecomment-464802876 - page.$eval('a.do_variation_action', elem => elem.click()); + await evalAndClick( 'a.do_variation_action' ); }); expect(firstDialog.message()).toMatch('Are you sure you want to link all variations?'); diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index ac35c538321..fc9ff9f0f8d 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -12,6 +12,7 @@ - `createSimpleOrder( status )` component which accepts an order status string and creates a basic order with that status - `addProductToOrder( orderId, productName )` component which adds the provided productName to the passed in orderId - `createCoupon( couponAmount )` component which accepts a coupon amount string (it defaults to 5) and creates a basic coupon. Returns the generated coupon code. +- `evalAndClick( selector )` use Puppeteer page.$eval to select and click and element. ## Changes diff --git a/tests/e2e/utils/src/page-utils.js b/tests/e2e/utils/src/page-utils.js index 2a77ba4b709..1472e4684a3 100644 --- a/tests/e2e/utils/src/page-utils.js +++ b/tests/e2e/utils/src/page-utils.js @@ -160,7 +160,7 @@ const verifyValueOfInputField = async( selector, value ) => { * * @param {string} selector Selector of the filter link to be clicked. */ -const clickFilter = async( selector ) => { +const clickFilter = async ( selector ) => { await page.waitForSelector( selector ); await page.focus( selector ); await Promise.all( [ @@ -174,7 +174,7 @@ const clickFilter = async( selector ) => { * * If there's more than 20 items, it moves all 20 items on the current page. */ -const moveAllItemsToTrash = async() => { +const moveAllItemsToTrash = async () => { await setCheckbox( '#cb-select-all-1' ); await expect( page ).toSelect( '#bulk-action-selector-top', 'Move to Trash' ); await Promise.all( [ @@ -183,6 +183,19 @@ const moveAllItemsToTrash = async() => { ] ); }; +/** + * Use puppeteer page eval to click an element. + * + * Useful for clicking items that have been added to the DOM via ajax. + * + * @param {string} selector Selector of the filter link to be clicked. + */ +const evalAndClick = async ( selector ) => { + // We use this when `expect(page).toClick()` is unable to find the element + // See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219 + page.$eval( selector, elem => elem.click() ); +}; + export { clearAndFillInput, clickTab, @@ -197,4 +210,5 @@ export { verifyValueOfInputField, clickFilter, moveAllItemsToTrash, + evalAndClick, }; From f2648d76cf53e7aae5b03164fc2d0de7aec75c6e Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Wed, 30 Dec 2020 10:42:53 -0300 Subject: [PATCH 025/120] Turned merchant email notifications on by default This commit adds the code to turn email notifications on by default --- includes/admin/settings/class-wc-settings-emails.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/settings/class-wc-settings-emails.php b/includes/admin/settings/class-wc-settings-emails.php index b08b525156c..4d5a087368e 100644 --- a/includes/admin/settings/class-wc-settings-emails.php +++ b/includes/admin/settings/class-wc-settings-emails.php @@ -203,7 +203,7 @@ class WC_Settings_Emails extends WC_Settings_Page { 'id' => 'woocommerce_merchant_email_notifications', 'type' => 'checkbox', 'checkboxgroup' => 'start', - 'default' => 'no', + 'default' => 'yes', 'autoload' => false, ), From 543f1e13070d43394c113eeb1ae1b21c954c3187 Mon Sep 17 00:00:00 2001 From: Fernando Marichal Date: Wed, 30 Dec 2020 12:13:39 -0300 Subject: [PATCH 026/120] Fixed copy --- includes/admin/settings/class-wc-settings-emails.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/settings/class-wc-settings-emails.php b/includes/admin/settings/class-wc-settings-emails.php index 4d5a087368e..a3da4c3f514 100644 --- a/includes/admin/settings/class-wc-settings-emails.php +++ b/includes/admin/settings/class-wc-settings-emails.php @@ -199,7 +199,7 @@ class WC_Settings_Emails extends WC_Settings_Page { array( 'title' => __( 'Enable email insights', 'woocommerce' ), - 'desc' => __( 'Receive email notifications with additional guidance to complete the basic store setup and helpful insights.', 'woocommerce' ), + 'desc' => __( 'Receive email notifications with additional guidance to complete the basic store setup and helpful insights', 'woocommerce' ), 'id' => 'woocommerce_merchant_email_notifications', 'type' => 'checkbox', 'checkboxgroup' => 'start', From 55fb3c062818eea5e8c235f3224b11cc2d8f8485 Mon Sep 17 00:00:00 2001 From: Kevin Ruscoe Date: Sat, 2 Jan 2021 11:36:02 +0000 Subject: [PATCH 027/120] Export sass vars to css var --- assets/css/_variables.scss | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/assets/css/_variables.scss b/assets/css/_variables.scss index e2c3d1b29b1..f215c2eaceb 100644 --- a/assets/css/_variables.scss +++ b/assets/css/_variables.scss @@ -8,7 +8,7 @@ $red: #a00 !default; $orange: #ffba00 !default; $blue: #2ea2cc !default; -$primary: #a46497 !default; // Primary color for buttons (alt) +$primary: #a46497 !default; // Primary color for buttons (alt) $primarytext: desaturate(lighten($primary, 50%), 18%) !default; // Text on primary color bg $secondary: desaturate(lighten($primary, 40%), 21%) !default; // Secondary buttons @@ -17,5 +17,22 @@ $secondarytext: desaturate(darken($secondary, 60%), 21%) !default; // Text $highlight: adjust-hue($primary, 150deg) !default; // Prices, In stock labels, sales flash $highlightext: desaturate(lighten($highlight, 50%), 18%) !default; // Text on highlight color bg -$contentbg: #fff !default; // Content BG - Tabs (active state) -$subtext: #767676 !default; // small, breadcrumbs etc +$contentbg: #fff !default; // Content BG - Tabs (active state) +$subtext: #767676 !default; // small, breadcrumbs etc + +// export vars as CSS vars +:root { + --woocommerce: $woocommerce; + --wc-green: $green; + --wc-red: $red; + --wc-orange: $orange; + --wc-blue: $blue; + --wc-primary: $primary; + --wc-primarytext: $primarytext; + --wc-secondary: $secondary; + --wc-secondarytext: $secondarytext; + --wc-highlight: $highlight; + --wc-highlightext: $highlightext; + --wc-contentbg: $contentbg; + --wc-subtext: $subtext; +} From a1b8aed702ab76a9c575ab2d54e9f95fc70b717b Mon Sep 17 00:00:00 2001 From: Jose Arturo Garcia Date: Sun, 3 Jan 2021 18:34:32 -0400 Subject: [PATCH 028/120] Update states.php --- i18n/states.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/i18n/states.php b/i18n/states.php index 6613a4831d7..11ff7f00262 100644 --- a/i18n/states.php +++ b/i18n/states.php @@ -306,6 +306,40 @@ return array( 'CZ' => array(), 'DE' => array(), 'DK' => array(), + 'DO' => array( + 'DO-01' => __( 'Distrito Nacional', 'woocommerce' ), + 'DO-02' => __( 'Azua', 'woocommerce' ), + 'DO-03' => __( 'Baoruco', 'woocommerce' ), + 'DO-04' => __( 'Barahona', 'woocommerce' ), + 'DO-05' => __( 'Dajabón', 'woocommerce' ), + 'DO-06' => __( 'Duarte', 'woocommerce' ), + 'DO-07' => __( 'Elías Piña', 'woocommerce' ), + 'DO-08' => __( 'El Seibo', 'woocommerce' ), + 'DO-09' => __( 'Espaillat', 'woocommerce' ), + 'DO-10' => __( 'Independencia', 'woocommerce' ), + 'DO-11' => __( 'La Altagracia', 'woocommerce' ), + 'DO-12' => __( 'La Romana', 'woocommerce' ), + 'DO-13' => __( 'La Vega', 'woocommerce' ), + 'DO-14' => __( 'María Trinidad Sánchez', 'woocommerce' ), + 'DO-15' => __( 'Monte Cristi', 'woocommerce' ), + 'DO-16' => __( 'Pedernales', 'woocommerce' ), + 'DO-17' => __( 'Peravia', 'woocommerce' ), + 'DO-18' => __( 'Puerto Plata', 'woocommerce' ), + 'DO-19' => __( 'Hermanas Mirabal', 'woocommerce' ), + 'DO-20' => __( 'Samaná', 'woocommerce' ), + 'DO-21' => __( 'San Cristóbal', 'woocommerce' ), + 'DO-22' => __( 'San Juan', 'woocommerce' ), + 'DO-23' => __( 'San Pedro de Macorís', 'woocommerce' ), + 'DO-24' => __( 'Sánchez Ramírez', 'woocommerce' ), + 'DO-25' => __( 'Santiago', 'woocommerce' ), + 'DO-26' => __( 'Santiago Rodríguez', 'woocommerce' ), + 'DO-27' => __( 'Valverde', 'woocommerce' ), + 'DO-28' => __( 'Monseñor Nouel', 'woocommerce' ), + 'DO-29' => __( 'Monte Plata', 'woocommerce' ), + 'DO-30' => __( 'Hato Mayor', 'woocommerce' ), + 'DO-31' => __( 'San José de Ocoa', 'woocommerce' ), + 'DO-32' => __( 'Santo Domingo', 'woocommerce' ), + ), 'DZ' => array( 'DZ-01' => __( 'Adrar', 'woocommerce' ), 'DZ-02' => __( 'Chlef', 'woocommerce' ), From de38c010e1dfaf5bbf6ed40ddcac09b0d671dab9 Mon Sep 17 00:00:00 2001 From: Jose Arturo Garcia Date: Sun, 3 Jan 2021 19:10:39 -0400 Subject: [PATCH 029/120] Update states.php --- i18n/states.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/states.php b/i18n/states.php index 11ff7f00262..a48bcf31135 100644 --- a/i18n/states.php +++ b/i18n/states.php @@ -306,7 +306,7 @@ return array( 'CZ' => array(), 'DE' => array(), 'DK' => array(), - 'DO' => array( + 'DO' => array( // Dominican Republic. 'DO-01' => __( 'Distrito Nacional', 'woocommerce' ), 'DO-02' => __( 'Azua', 'woocommerce' ), 'DO-03' => __( 'Baoruco', 'woocommerce' ), From 2204867a20be1ce9a23ed35ce11b65fe78f522ef Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 4 Jan 2021 15:11:41 +0100 Subject: [PATCH 030/120] Fix conflict with another tests We're using dynamic coupon creation now. I updated other test to search for specific coupon name because in order edit page there's only lowercased coupon names, but we're using uppercased for two reasons at least. --- .../specs/merchant/wp-admin-order-apply-coupon.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js index 4319e8606cb..a51d435e1d0 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js @@ -50,7 +50,7 @@ const runOrderApplyCouponTest = () => { // Verify the coupon list is showing await page.waitForSelector('.wc-used-coupons'); await expect(page).toMatchElement('.wc_coupon_list', { text: 'Coupon(s)' }); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); // Check that the coupon has been applied await expect(page).toMatchElement('.wc-order-item-discount', { text: '5.00' }); @@ -60,7 +60,7 @@ const runOrderApplyCouponTest = () => { it('can remove a coupon', async () => { // Make sure we have a coupon on the page to use await page.waitForSelector('.wc-used-coupons'); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); // We need to use this here as `expect(page).toClick()` was unable to find the element // See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219 @@ -69,7 +69,7 @@ const runOrderApplyCouponTest = () => { await uiUnblocked(); // Verify the coupon pricing has been removed - await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode }); + await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); await expect(page).not.toMatchElement('.wc-order-item-discount', { text: '5.00' }); await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: '4.99' }); From 57f3eccc956b865de1288d2e98a3a50d24e1eecb Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 4 Jan 2021 15:20:40 +0100 Subject: [PATCH 031/120] Fix conflict with another tests We're using dynamic coupon creation now. I updated other test to search for specific coupon name because in order edit page there's only lowercased coupon names, but we're using uppercased for two reasons at least. --- .../specs/merchant/wp-admin-order-apply-coupon.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js index 4319e8606cb..a51d435e1d0 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js @@ -50,7 +50,7 @@ const runOrderApplyCouponTest = () => { // Verify the coupon list is showing await page.waitForSelector('.wc-used-coupons'); await expect(page).toMatchElement('.wc_coupon_list', { text: 'Coupon(s)' }); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); // Check that the coupon has been applied await expect(page).toMatchElement('.wc-order-item-discount', { text: '5.00' }); @@ -60,7 +60,7 @@ const runOrderApplyCouponTest = () => { it('can remove a coupon', async () => { // Make sure we have a coupon on the page to use await page.waitForSelector('.wc-used-coupons'); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); // We need to use this here as `expect(page).toClick()` was unable to find the element // See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219 @@ -69,7 +69,7 @@ const runOrderApplyCouponTest = () => { await uiUnblocked(); // Verify the coupon pricing has been removed - await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode }); + await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); await expect(page).not.toMatchElement('.wc-order-item-discount', { text: '5.00' }); await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: '4.99' }); From cf8c114198652fb122432361f87b6963ae67b321 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 4 Jan 2021 15:27:05 +0100 Subject: [PATCH 032/120] Update changelog information --- tests/e2e/core-tests/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 6309052de51..1d93cdb250d 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -4,6 +4,7 @@ - Merchant Order Status Filter tests - Merchant Order Refund tests - Merchant Apply Coupon tests + - Shopper Checkout Apply Coupon ## Fixed From f782bcdc8699205ed7b980460cdd749ef23e5310 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 4 Jan 2021 15:28:09 +0100 Subject: [PATCH 033/120] Update changelog information --- tests/e2e/core-tests/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/core-tests/CHANGELOG.md b/tests/e2e/core-tests/CHANGELOG.md index 3ae9b1c2a16..1f1fcfa67a0 100644 --- a/tests/e2e/core-tests/CHANGELOG.md +++ b/tests/e2e/core-tests/CHANGELOG.md @@ -4,6 +4,8 @@ - Merchant Order Status Filter tests - Merchant Order Refund tests - Merchant Apply Coupon tests + + - Shopper Cart Apply Coupon ## Fixed From d3169dcf4361c66bc58d75d96865e48888b47c49 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Tue, 5 Jan 2021 22:24:03 +0100 Subject: [PATCH 034/120] Remove unnecessary code --- .../specs/shopper/front-end-checkout-coupons.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index da89375b9fe..e8342cb6813 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -1,4 +1,4 @@ -/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect */ +/* eslint-disable jest/no-export, jest/no-disabled-tests, jest/expect-expect, jest/no-standalone-expect */ /** * Internal dependencies */ @@ -33,8 +33,6 @@ const runCheckoutApplyCouponsTest = () => { it('allows customer to apply coupons in the checkout', async () => { await shopper.goToShop(); await shopper.addToCartFromShopPage('Simple product'); - await shopper.goToCart(); - await shopper.productIsInCart('Simple product'); await shopper.goToCheckout(); // Apply Fixed cart discount coupon From a0049c10ffadc186a4b4f0cd356bc88e079c177c Mon Sep 17 00:00:00 2001 From: Veljko V Date: Tue, 5 Jan 2021 22:48:34 +0100 Subject: [PATCH 035/120] Fix test title to be more concise --- .../e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js index 64d7dc40ad0..6850401abb7 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -30,7 +30,7 @@ const runCartApplyCouponsTest = () => { await merchant.logout(); }); - it('allows customer to apply a fixed cart coupon in the cart', async () => { + it('allows customer to apply coupons in the cart', async () => { await shopper.goToShop(); await shopper.addToCartFromShopPage('Simple product'); await shopper.goToCart(); From fac6d3a246e9c004096b395896e397cb817f5349 Mon Sep 17 00:00:00 2001 From: roykho Date: Tue, 5 Jan 2021 14:16:46 -0800 Subject: [PATCH 036/120] Remove do_update_woocommerce query arg to prevent actions taken on it --- includes/admin/notes/class-wc-notes-run-db-update.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/notes/class-wc-notes-run-db-update.php b/includes/admin/notes/class-wc-notes-run-db-update.php index f59074679d5..1fad100ff9e 100644 --- a/includes/admin/notes/class-wc-notes-run-db-update.php +++ b/includes/admin/notes/class-wc-notes-run-db-update.php @@ -215,7 +215,7 @@ class WC_Notes_Run_Db_Update { '_nonce_action' => 'woocommerce_hide_notices_nonce', '_nonce_name' => '_wc_notice_nonce', ), - wc_get_current_admin_url() ? wc_get_current_admin_url() : admin_url( 'admin.php?page=wc-settings' ) + wc_get_current_admin_url() ? remove_query_arg( 'do_update_woocommerce', wc_get_current_admin_url() ) : admin_url( 'admin.php?page=wc-settings' ) ) ); From 29837c42ffef204aa98b3393cb30292d1c2e267f Mon Sep 17 00:00:00 2001 From: budzanowski Date: Wed, 6 Jan 2021 11:33:28 +0100 Subject: [PATCH 037/120] Refactor. --- includes/blocks/class-wc-blocks-utils.php | 84 +++++++++++++++++++ includes/class-wc-tracker.php | 45 +--------- includes/class-woocommerce.php | 1 + ...ss-wc-rest-system-status-v2-controller.php | 4 +- 4 files changed, 87 insertions(+), 47 deletions(-) create mode 100644 includes/blocks/class-wc-blocks-utils.php diff --git a/includes/blocks/class-wc-blocks-utils.php b/includes/blocks/class-wc-blocks-utils.php new file mode 100644 index 00000000000..8aca7f8d59e --- /dev/null +++ b/includes/blocks/class-wc-blocks-utils.php @@ -0,0 +1,84 @@ +post_content ); + if ( ! $blocks ) { + return array(); + } + + return $blocks; + } + + /** + * Get all instances of the specified block on a specific woo page + * (e.g. `cart` or `checkout` page). + * + * @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`. + * @param string $woo_page_name The woo page to search, e.g. `cart`. + * @return array Array of blocks as returned by parse_blocks(). + */ + public static function get_blocks_from_page( $block_name, $woo_page_name ) { + $page_blocks = self::get_all_blocks_from_page( $woo_page_name ); + + // Get any instances of the specified block. + return array_values( + array_filter( + $page_blocks, + function ( $block ) use ( $block_name ) { + return ( $block_name === $block['blockName'] ); + } + ) + ); + } + + /** + * Check if a given page contains a particular block. + * + * @param string $page (int|WP_Post) Page post ID or post object. + * @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`. + * @return bool|null Boolean value if the page contains the block or not. Null in case the page does not exist. + */ + public static function does_page_contains_block( $page, $block_name ) { + $page_to_check = get_post( $page_id ); + if ( null === $page_to_check ) { + return null; + } + + $blocks = parse_blocks( $page_to_check->post_content ); + foreach ( $blocks as $block ) { + if ( $block_name === $block['blockName'] ) { + return true; + } + } + + return false; + } +} diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 27a36961c7f..713a9e735b8 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -660,49 +660,6 @@ class WC_Tracker { return ( '0' !== $result ) ? 'Yes' : 'No'; } - /** - * Get blocks from a woocommerce page. - * - * @param string $woo_page_name A woocommerce page e.g. `checkout` or `cart`. - * @return array Array of blocks as returned by parse_blocks(). - */ - private static function get_all_blocks_from_page( $woo_page_name ) { - $page_id = wc_get_page_id( $woo_page_name ); - - $page = get_post( $page_id ); - if ( ! $page ) { - return array(); - } - - $blocks = parse_blocks( $page->post_content ); - if ( ! $blocks ) { - return array(); - } - - return $blocks; - } - - /** - * Get all instances of the specified block on a specific woo page - * (e.g. `cart` or `checkout` page). - * - * @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`. - * @param string $woo_page_name The woo page to search, e.g. `cart`. - * @return array Array of blocks as returned by parse_blocks(). - */ - private static function get_blocks_from_page( $block_name, $woo_page_name ) { - $page_blocks = self::get_all_blocks_from_page( $woo_page_name ); - - // Get any instances of the specified block. - return array_values( - array_filter( - $page_blocks, - function ( $block ) use ( $block_name ) { - return ( $block_name === $block['blockName'] ); - } - ) - ); - } /** * Get tracker data for a specific block type on a woocommerce page. @@ -714,7 +671,7 @@ class WC_Tracker { * - block_attributes */ public static function get_block_tracker_data( $block_name, $woo_page_name ) { - $blocks = self::get_blocks_from_page( $block_name, $woo_page_name ); + $blocks = WC_Blocks_Utils::get_blocks_from_page( $block_name, $woo_page_name ); $block_present = false; $attributes = array(); diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 1fe2dd3b2c7..0e1820625ee 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -421,6 +421,7 @@ final class WooCommerce { include_once WC_ABSPATH . 'includes/queue/class-wc-action-queue.php'; include_once WC_ABSPATH . 'includes/queue/class-wc-queue.php'; include_once WC_ABSPATH . 'includes/admin/marketplace-suggestions/class-wc-marketplace-updater.php'; + include_once WC_ABSPATH . 'includes/blocks/class-wc-blocks-utils.php'; /** * Data stores - used to store and retrieve CRUD object data from the database. diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php index 069808a3149..a74f65a2937 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php @@ -1230,9 +1230,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { // Block checks. if ( $values['block'] && get_post( $page_id ) ) { $block_required = true; - $page = get_post( $page_id ); - $page_info = WC_Tracker::get_block_tracker_data( $values['block'], $page_name ); - if ( 'Yes' === $page_info['page_contains_block'] ) { + if ( WC_Blocks_Utils::does_page_contains_block( $page_id, $values['block'] ) ) { $block_present = true; } } From 0b4eb9aa36ee7686858823f4cdea2bb468d05bfc Mon Sep 17 00:00:00 2001 From: budzanowski Date: Wed, 6 Jan 2021 11:35:27 +0100 Subject: [PATCH 038/120] Ups. --- includes/blocks/class-wc-blocks-utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/blocks/class-wc-blocks-utils.php b/includes/blocks/class-wc-blocks-utils.php index 8aca7f8d59e..a4df9126e35 100644 --- a/includes/blocks/class-wc-blocks-utils.php +++ b/includes/blocks/class-wc-blocks-utils.php @@ -67,7 +67,7 @@ class WC_Blocks_Utils { * @return bool|null Boolean value if the page contains the block or not. Null in case the page does not exist. */ public static function does_page_contains_block( $page, $block_name ) { - $page_to_check = get_post( $page_id ); + $page_to_check = get_post( $page ); if ( null === $page_to_check ) { return null; } From ca3cd5fd7e002d404c10eeb404877d6bcba41a91 Mon Sep 17 00:00:00 2001 From: Bartosz Budzanowski Date: Wed, 6 Jan 2021 15:25:15 +0100 Subject: [PATCH 039/120] Update includes/blocks/class-wc-blocks-utils.php Co-authored-by: Darren Ethier --- includes/blocks/class-wc-blocks-utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/blocks/class-wc-blocks-utils.php b/includes/blocks/class-wc-blocks-utils.php index a4df9126e35..67f5c61bb51 100644 --- a/includes/blocks/class-wc-blocks-utils.php +++ b/includes/blocks/class-wc-blocks-utils.php @@ -62,11 +62,11 @@ class WC_Blocks_Utils { /** * Check if a given page contains a particular block. * - * @param string $page (int|WP_Post) Page post ID or post object. + * @param int|WP_Post $page Page post ID or post object. * @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`. * @return bool|null Boolean value if the page contains the block or not. Null in case the page does not exist. */ - public static function does_page_contains_block( $page, $block_name ) { + public static function has_block_in_page( $page, $block_name ) { $page_to_check = get_post( $page ); if ( null === $page_to_check ) { return null; From d5babd800a187f96d678fa228ef34c47d9beb337 Mon Sep 17 00:00:00 2001 From: budzanowski Date: Wed, 6 Jan 2021 15:28:51 +0100 Subject: [PATCH 040/120] Review refactor. --- includes/blocks/class-wc-blocks-utils.php | 8 ++++---- .../class-wc-rest-system-status-v2-controller.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/includes/blocks/class-wc-blocks-utils.php b/includes/blocks/class-wc-blocks-utils.php index 67f5c61bb51..4ecb4c212e5 100644 --- a/includes/blocks/class-wc-blocks-utils.php +++ b/includes/blocks/class-wc-blocks-utils.php @@ -62,14 +62,14 @@ class WC_Blocks_Utils { /** * Check if a given page contains a particular block. * - * @param int|WP_Post $page Page post ID or post object. - * @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`. - * @return bool|null Boolean value if the page contains the block or not. Null in case the page does not exist. + * @param int|WP_Post $page Page post ID or post object. + * @param string $block_name The name (id) of a block, e.g. `woocommerce/cart`. + * @return bool Boolean value if the page contains the block or not. Null in case the page does not exist. */ public static function has_block_in_page( $page, $block_name ) { $page_to_check = get_post( $page ); if ( null === $page_to_check ) { - return null; + return false; } $blocks = parse_blocks( $page_to_check->post_content ); diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php index a74f65a2937..aa074dde190 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php @@ -1230,7 +1230,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { // Block checks. if ( $values['block'] && get_post( $page_id ) ) { $block_required = true; - if ( WC_Blocks_Utils::does_page_contains_block( $page_id, $values['block'] ) ) { + if ( WC_Blocks_Utils::has_block_in_page( $page_id, $values['block'] ) ) { $block_present = true; } } From 1cf09eb063d17e5a20f724c6f9bf4a250d52f63f Mon Sep 17 00:00:00 2001 From: budzanowski Date: Wed, 6 Jan 2021 15:30:13 +0100 Subject: [PATCH 041/120] Simplify. --- .../Version2/class-wc-rest-system-status-v2-controller.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php index aa074dde190..01ef58000dc 100644 --- a/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php +++ b/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php @@ -1230,9 +1230,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller { // Block checks. if ( $values['block'] && get_post( $page_id ) ) { $block_required = true; - if ( WC_Blocks_Utils::has_block_in_page( $page_id, $values['block'] ) ) { - $block_present = true; - } + $block_present = WC_Blocks_Utils::has_block_in_page( $page_id, $values['block'] ); } // Wrap up our findings into an output array. From 79904279fa4f2c9b6d075555877269e49d46f190 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Wed, 6 Jan 2021 17:08:29 +0100 Subject: [PATCH 042/120] Update components and test to be more concise --- .../specs/shopper/front-end-checkout-coupons.test.js | 10 +++++++--- tests/e2e/utils/src/components.js | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index e8342cb6813..05648d11770 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -21,18 +21,22 @@ const { const runCheckoutApplyCouponsTest = () => { describe('Checkout applying coupons', () => { + let couponFixedCart; + let couponPercentage; + let couponFixedProduct; beforeAll(async () => { await merchant.login(); await createSimpleProduct(); - await createCoupon('Fixed cart discount'); - await createCoupon('Percentage discount', '50'); - await createCoupon('Fixed product discount'); + couponFixedCart = await createCoupon(); + couponPercentage = await createCoupon('50', 'Percentage discount'); + couponFixedProduct = await createCoupon('5', 'Fixed product discount'); await merchant.logout(); }); it('allows customer to apply coupons in the checkout', async () => { await shopper.goToShop(); await shopper.addToCartFromShopPage('Simple product'); + await uiUnblocked(); await shopper.goToCheckout(); // Apply Fixed cart discount coupon diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 848226a5bcd..e0816f2a0d1 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -401,10 +401,10 @@ const addProductToOrder = async ( orderId, productName ) => { /** * Creates a basic coupon with the provided coupon amount. Returns the coupon code. * - * @param discountType Type of a coupon. Defaults to Fixed cart discount. * @param couponAmount Amount to be applied. Defaults to 5. + * @param discountType Type of a coupon. Defaults to Fixed cart discount. */ -const createCoupon = async ( discountType = 'Fixed cart discount', couponAmount = '5' ) => { +const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart discount' ) => { await merchant.openNewCoupon(); // Fill in coupon code @@ -413,8 +413,8 @@ const createCoupon = async ( discountType = 'Fixed cart discount', couponAmount // Set general coupon data await clickTab( 'General' ); - await expect(page).toSelect( '#discount_type', discountType ); await expect(page).toFill( '#coupon_amount', couponAmount ); + await expect(page).toSelect( '#discount_type', discountType ); // Publish coupon await expect( page ).toClick( '#publish' ); From cf0a4df04b2c6bf08b0322c4054443a99f8bbac7 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 7 Jan 2021 20:59:53 +0100 Subject: [PATCH 043/120] Improve test case and components --- .../shopper/front-end-cart-coupons.test.js | 19 +++++++++++-------- tests/e2e/utils/src/components.js | 6 +++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js index 6850401abb7..d77bf76274b 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -21,12 +21,15 @@ const { const runCartApplyCouponsTest = () => { describe('Cart applying coupons', () => { + let couponFixedCart; + let couponPercentage; + let couponFixedProduct; beforeAll(async () => { await merchant.login(); await createSimpleProduct(); - await createCoupon('Fixed cart discount'); - await createCoupon('Percentage discount', '50'); - await createCoupon('Fixed product discount'); + couponFixedCart = await createCoupon(); + couponPercentage = await createCoupon('50', 'Percentage discount'); + couponFixedProduct = await createCoupon('5', 'Fixed product discount'); await merchant.logout(); }); @@ -37,7 +40,7 @@ const runCartApplyCouponsTest = () => { await shopper.productIsInCart('Simple product'); // Apply Fixed cart discount coupon - await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toFill('#coupon_code', couponFixedCart); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); @@ -55,7 +58,7 @@ const runCartApplyCouponsTest = () => { await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Percentage discount coupon - await expect(page).toFill('#coupon_code', 'Code-Percentage discount'); + await expect(page).toFill('#coupon_code', couponPercentage); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); @@ -68,7 +71,7 @@ const runCartApplyCouponsTest = () => { await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); // Apply Fixed product discount coupon - await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toFill('#coupon_code', couponFixedProduct); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); @@ -76,13 +79,13 @@ const runCartApplyCouponsTest = () => { await page.waitForSelector('.order-total .amount', {text: '$4.99'}); // Try to apply the same coupon - await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toFill('#coupon_code', couponFixedProduct); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' }); // Try to apply multiple coupons - await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toFill('#coupon_code', couponFixedCart); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 848226a5bcd..9338772cb41 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -401,14 +401,14 @@ const addProductToOrder = async ( orderId, productName ) => { /** * Creates a basic coupon with the provided coupon amount. Returns the coupon code. * - * @param discountType Type of a coupon. Defaults to Fixed cart discount. * @param couponAmount Amount to be applied. Defaults to 5. + * @param discountType Type of a coupon. Defaults to Fixed cart discount. */ -const createCoupon = async ( discountType = 'Fixed cart discount', couponAmount = '5' ) => { +const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart discount' ) => { await merchant.openNewCoupon(); // Fill in coupon code - let couponCode = 'Code-' + discountType; + let couponCode = 'Code-' + discountType + new Date().getTime().toString(); await expect(page).toFill( '#title', couponCode ); // Set general coupon data From 8111c4f4104ec34bc6c5492a33d18f31ce699a47 Mon Sep 17 00:00:00 2001 From: Veljko Date: Thu, 7 Jan 2021 23:39:12 +0100 Subject: [PATCH 044/120] Improve test case and components --- .../specs/shopper/front-end-checkout-coupons.test.js | 10 +++++----- tests/e2e/utils/src/components.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index 05648d11770..a481dc58cef 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -42,7 +42,7 @@ const runCheckoutApplyCouponsTest = () => { // Apply Fixed cart discount coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); - await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toFill('#coupon_code', couponFixedCart); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); @@ -62,7 +62,7 @@ const runCheckoutApplyCouponsTest = () => { // Apply Percentage discount coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); - await expect(page).toFill('#coupon_code', 'Code-Percentage discount'); + await expect(page).toFill('#coupon_code', couponPercentage); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); @@ -77,7 +77,7 @@ const runCheckoutApplyCouponsTest = () => { // Apply Fixed product discount coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); - await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toFill('#coupon_code', couponFixedProduct); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); @@ -87,7 +87,7 @@ const runCheckoutApplyCouponsTest = () => { // Try to apply the same coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); - await expect(page).toFill('#coupon_code', 'Code-Fixed product discount'); + await expect(page).toFill('#coupon_code', couponFixedProduct); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' }); @@ -95,7 +95,7 @@ const runCheckoutApplyCouponsTest = () => { // Try to apply multiple coupons await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); - await expect(page).toFill('#coupon_code', 'Code-Fixed cart discount'); + await expect(page).toFill('#coupon_code', couponFixedCart); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index e0816f2a0d1..9338772cb41 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -408,13 +408,13 @@ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart disc await merchant.openNewCoupon(); // Fill in coupon code - let couponCode = 'Code-' + discountType; + let couponCode = 'Code-' + discountType + new Date().getTime().toString(); await expect(page).toFill( '#title', couponCode ); // Set general coupon data await clickTab( 'General' ); - await expect(page).toFill( '#coupon_amount', couponAmount ); await expect(page).toSelect( '#discount_type', discountType ); + await expect(page).toFill( '#coupon_amount', couponAmount ); // Publish coupon await expect( page ).toClick( '#publish' ); From 9617c638413c5a646198b46e10c05c57ea52d211 Mon Sep 17 00:00:00 2001 From: Paulo Chang Date: Fri, 8 Jan 2021 09:27:21 +0100 Subject: [PATCH 045/120] Add Guatemalan states --- i18n/states.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/i18n/states.php b/i18n/states.php index cda1e51e4aa..809a3c48a8e 100644 --- a/i18n/states.php +++ b/i18n/states.php @@ -442,6 +442,7 @@ return array( ), 'FI' => array(), 'FR' => array(), + 'GF' => array(), 'GH' => array( // Ghanaian Regions. 'AF' => __( 'Ahafo', 'woocommerce' ), 'AH' => __( 'Ashanti', 'woocommerce' ), @@ -477,7 +478,30 @@ return array( 'L' => __( 'South Aegean', 'woocommerce' ), 'M' => __( 'Crete', 'woocommerce' ), ), - 'GF' => array(), + 'GT' => array( // Guatemalan states. + 'GT-AV' => __('Alta Verapaz', 'woocommerce' ), + 'GT-BV' => __('Baja Verapaz', 'woocommerce' ), + 'GT-CM' => __('Chimaltenango', 'woocommerce' ), + 'GT-CQ' => __('Chiquimula', 'woocommerce' ), + 'GT-PR' => __('El Progreso', 'woocommerce' ), + 'GT-ES' => __('Escuintla', 'woocommerce' ), + 'GT-GU' => __('Guatemala', 'woocommerce' ), + 'GT-HU' => __('Huehuetenango', 'woocommerce' ), + 'GT-IZ' => __('Izabal', 'woocommerce' ), + 'GT-JA' => __('Jalapa', 'woocommerce' ), + 'GT-JU' => __('Jutiapa', 'woocommerce' ), + 'GT-PE' => __('Petén', 'woocommerce' ), + 'GT-QZ' => __('Quetzaltenango', 'woocommerce' ), + 'GT-QC' => __('Quiché', 'woocommerce' ), + 'GT-RE' => __('Retalhuleu', 'woocommerce' ), + 'GT-SA' => __('Sacatepéquez', 'woocommerce' ), + 'GT-SM' => __('San Marcos', 'woocommerce' ), + 'GT-SR' => __('Santa Rosa', 'woocommerce' ), + 'GT-SO' => __('Sololá', 'woocommerce' ), + 'GT-SU' => __('Suchitepéquez', 'woocommerce' ), + 'GT-TO' => __('Totonicapán', 'woocommerce' ), + 'GT-ZA' => __('Zacapa', 'woocommerce' ) + ), 'HK' => array( // Hong Kong states. 'HONG KONG' => __( 'Hong Kong Island', 'woocommerce' ), 'KOWLOON' => __( 'Kowloon', 'woocommerce' ), From baace44f19f38e210b334e394f4c57a4535448d6 Mon Sep 17 00:00:00 2001 From: Menaka S Date: Fri, 8 Jan 2021 19:45:46 +0530 Subject: [PATCH 046/120] Remove the functio to get orders count by payment method --- includes/class-wc-tracker.php | 44 ----------------------------------- 1 file changed, 44 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 8cc568be8d6..e1813828860 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -374,7 +374,6 @@ class WC_Tracker { $orders = wc_get_orders( array( 'type' => array( 'shop_order', 'shop_order_refund' ), - 'customer' => '', 'posts_per_page' => -1, ) ); @@ -620,49 +619,6 @@ class WC_Tracker { return array_filter( (array) get_option( 'woocommerce_tracker_ua', array() ) ); } - /** - * Get order counts by payment method - * - * @return array - */ - public static function get_order_counts_by_payment_method() { - global $wpdb; - - $orders_by_payment_method = $wpdb->get_results( - " - SELECT - order_gateway.payment_method AS payment_method, - COUNT( id ) AS orders_count - FROM ( - SELECT - orders.ID AS id, - MAX( - CASE - WHEN meta_key = '_payment_method' - THEN meta_value - END - ) AS payment_method - FROM {$wpdb->prefix}posts AS orders - LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID - WHERE - orders.post_type = 'shop_order' AND - orders.post_status in ( 'wc-completed', 'wc-processing', 'wc-refunded' ) - GROUP BY orders.ID - ) AS order_gateway - WHERE payment_method IS NOT NULL - GROUP BY payment_method - ORDER BY orders_count DESC - " - ); - - foreach ( $orders_by_payment_method as $orders_count ) { - $method = 'gateway_' . $orders_count->payment_method; - $order_counts_by_payment_method[ $method ] = $orders_count->orders_count; - } - - return $order_counts_by_payment_method; - } - /** * Get order totals * From 2cf971486e075c0835ae625d851da89902e3553f Mon Sep 17 00:00:00 2001 From: Veljko Date: Sat, 9 Jan 2021 18:49:59 +0100 Subject: [PATCH 047/120] Update code to be more concise --- tests/e2e/core-tests/specs/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 4ced19475d5..9012ab881aa 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -10,7 +10,7 @@ const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ) // Shopper tests const runCartPageTest = require( './shopper/front-end-cart.test' ); -const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test') +const runCheckoutApplyCouponsTest = require( './shopper/front-end-checkout-coupons.test'); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); const runSingleProductPageTest = require( './shopper/front-end-single-product.test' ); From 5ceda1766456e15fb5921d4bd8a092a9d80db13a Mon Sep 17 00:00:00 2001 From: Veljko Date: Sat, 9 Jan 2021 18:53:12 +0100 Subject: [PATCH 048/120] Update code to be more concise --- tests/e2e/core-tests/specs/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core-tests/specs/index.js b/tests/e2e/core-tests/specs/index.js index 5a40f310afb..b204f371310 100644 --- a/tests/e2e/core-tests/specs/index.js +++ b/tests/e2e/core-tests/specs/index.js @@ -9,7 +9,7 @@ const { runOnboardingFlowTest, runTaskListTest } = require( './activate-and-setu const runInitialStoreSettingsTest = require( './activate-and-setup/setup.test' ); // Shopper tests -const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test') +const runCartApplyCouponsTest = require( './shopper/front-end-cart-coupons.test'); const runCartPageTest = require( './shopper/front-end-cart.test' ); const runCheckoutPageTest = require( './shopper/front-end-checkout.test' ); const runMyAccountPageTest = require( './shopper/front-end-my-account.test' ); From 003f4a0acb0bae7d66c5ecc8956cf7058bf8d8c4 Mon Sep 17 00:00:00 2001 From: Menaka S Date: Fri, 8 Jan 2021 21:58:21 +0530 Subject: [PATCH 049/120] Paginate while getting orders to calculate tracker data --- includes/class-wc-tracker.php | 131 +++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 57 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index e1813828860..ed8529aa7e3 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -26,6 +26,13 @@ class WC_Tracker { */ private static $api_url = 'https://tracking.woocommerce.com/v1/'; + /** + * Rows per page - max. + * + * @var int + */ + const PER_PAGE_MAX = 100; + /** * Hook into cron event. */ @@ -371,11 +378,10 @@ class WC_Tracker { * @return array */ private static function get_orders() { - $orders = wc_get_orders( - array( - 'type' => array( 'shop_order', 'shop_order_refund' ), - 'posts_per_page' => -1, - ) + $args = array( + 'type' => array( 'shop_order', 'shop_order_refund' ), + 'limit' => self::PER_PAGE_MAX, + 'paged' => 1, ); $first = time(); @@ -383,70 +389,81 @@ class WC_Tracker { $processing_first = time(); $processing_last = 0; - foreach ( $orders as $order ) { - $date_created = (int) $order->get_date_created()->getTimestamp(); - $type = $order->get_type(); - $status = $order->get_status(); + $orders = wc_get_orders( $args ); + $orders_count = count( $orders ); - if ( 'shop_order' == $type ) { + while ( $orders_count ) { - // Find the first and last order dates for completed and processing statuses. - if ( 'completed' == $status && $date_created < $first ) { - $first = $date_created; - } - if ( 'completed' == $status && $date_created > $last ) { - $last = $date_created; - } - if ( 'processing' == $status && $date_created < $processing_first ) { - $processing_first = $date_created; - } - if ( 'processing' == $status && $date_created > $processing_last ) { - $processing_last = $date_created; - } + foreach ( $orders as $order ) { - // Get order counts by status. - $status = 'wc-' . $status; + $date_created = (int) $order->get_date_created()->getTimestamp(); + $type = $order->get_type(); + $status = $order->get_status(); - if ( ! isset( $order_data[ $status ] ) ) { - $order_data[ $status ] = 1; - } else { - $order_data[ $status ] += 1; - } + if ( 'shop_order' == $type ) { - // Count number of orders by gateway used. - $gateway = $order->get_payment_method(); + // Find the first and last order dates for completed and processing statuses. + if ( 'completed' == $status && $date_created < $first ) { + $first = $date_created; + } + if ( 'completed' == $status && $date_created > $last ) { + $last = $date_created; + } + if ( 'processing' == $status && $date_created < $processing_first ) { + $processing_first = $date_created; + } + if ( 'processing' == $status && $date_created > $processing_last ) { + $processing_last = $date_created; + } - if ( ! empty( $gateway ) && in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) { - $gateway = 'gateway_' . $gateway; + // Get order counts by status. + $status = 'wc-' . $status; - if ( ! isset( $order_data[ $gateway ] ) ) { - $order_data[ $gateway ] = 1; + if ( ! isset( $order_data[ $status ] ) ) { + $order_data[ $status ] = 1; } else { - $order_data[ $gateway ] += 1; + $order_data[ $status ] += 1; + } + + // Count number of orders by gateway used. + $gateway = $order->get_payment_method(); + + if ( ! empty( $gateway ) && in_array( $status, array( 'wc-completed', 'wc-refunded', 'wc-processing' ) ) ) { + $gateway = 'gateway_' . $gateway; + + if ( ! isset( $order_data[ $gateway ] ) ) { + $order_data[ $gateway ] = 1; + } else { + $order_data[ $gateway ] += 1; + } + } + } else { + // If it is a refunded order (shop_order_refunnd type), add the prefix as this prefix gets + // added midway in the if clause. + $status = 'wc-' . $status; + } + + // Calculate the gross total for 'completed' and 'processing' orders. + $total = $order->get_total(); + + if ( in_array( $status, array( 'wc-completed', 'wc-refunded' ) ) ) { + if ( ! isset( $order_data['gross'] ) ) { + $order_data['gross'] = $total; + } else { + $order_data['gross'] += $total; + } + } elseif ( 'wc-processing' == $status ) { + if ( ! isset( $order_data['processing_gross'] ) ) { + $order_data['processing_gross'] = $total; + } else { + $order_data['processing_gross'] += $total; } } - } else { - // If it is a refunded order (shop_order_refunnd type), add the prefix as this prefix gets - // added midway in the if clause. - $status = 'wc-' . $status; } + $args['paged']++; - // Calculate the gross total for 'completed' and 'processing' orders. - $total = $order->get_total(); - - if ( in_array( $status, array( 'wc-completed', 'wc-refunded' ) ) ) { - if ( ! isset( $order_data['gross'] ) ) { - $order_data['gross'] = $total; - } else { - $order_data['gross'] += $total; - } - } elseif ( 'wc-processing' == $status ) { - if ( ! isset( $order_data['processing_gross'] ) ) { - $order_data['processing_gross'] = $total; - } else { - $order_data['processing_gross'] += $total; - } - } + $orders = wc_get_orders( $args ); + $orders_count = count( $orders ); } $order_data['first'] = gmdate( 'Y-m-d H:i:s', $first ); From 415e1e7bbfa62488fa90466a3c2330fc93f415ae Mon Sep 17 00:00:00 2001 From: Menaka S Date: Sun, 10 Jan 2021 15:21:06 +0530 Subject: [PATCH 050/120] Use site's posts_per_page size --- includes/class-wc-tracker.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index ed8529aa7e3..85e20577fc5 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -26,13 +26,6 @@ class WC_Tracker { */ private static $api_url = 'https://tracking.woocommerce.com/v1/'; - /** - * Rows per page - max. - * - * @var int - */ - const PER_PAGE_MAX = 100; - /** * Hook into cron event. */ @@ -380,7 +373,7 @@ class WC_Tracker { private static function get_orders() { $args = array( 'type' => array( 'shop_order', 'shop_order_refund' ), - 'limit' => self::PER_PAGE_MAX, + 'limit' => get_option( 'posts_per_page' ), 'paged' => 1, ); From 255b03ea04ee0e789c0bbef0b04e477d39399cb1 Mon Sep 17 00:00:00 2001 From: Aminul Islam Date: Mon, 11 Jan 2021 11:54:58 +0600 Subject: [PATCH 051/120] Update states.php Removed duplicate entry for "RS". See Line: 1506 --- i18n/states.php | 1 - 1 file changed, 1 deletion(-) diff --git a/i18n/states.php b/i18n/states.php index cda1e51e4aa..f0c01ac321b 100644 --- a/i18n/states.php +++ b/i18n/states.php @@ -1304,7 +1304,6 @@ return array( 'VS' => __( 'Vaslui', 'woocommerce' ), 'VN' => __( 'Vrancea', 'woocommerce' ), ), - 'RS' => array(), 'SG' => array(), 'SK' => array(), 'SI' => array(), From 950bfcdf390e9ff94eb4da760b738e1af7e77290 Mon Sep 17 00:00:00 2001 From: Menaka S Date: Mon, 11 Jan 2021 18:18:08 +0530 Subject: [PATCH 052/120] Deprecate public function and remove private functions --- includes/class-wc-tracker.php | 102 +--------------------------------- 1 file changed, 3 insertions(+), 99 deletions(-) diff --git a/includes/class-wc-tracker.php b/includes/class-wc-tracker.php index 85e20577fc5..35e33d0a551 100644 --- a/includes/class-wc-tracker.php +++ b/includes/class-wc-tracker.php @@ -351,20 +351,6 @@ class WC_Tracker { return $product_count; } - /** - * Get order counts - * - * @return array - */ - private static function get_order_counts() { - $order_count = array(); - $order_count_data = wp_count_posts( 'shop_order' ); - foreach ( wc_get_order_statuses() as $status_slug => $status_name ) { - $order_count[ $status_slug ] = $order_count_data->{ $status_slug }; - } - return $order_count; - } - /** * Get all order data. * @@ -632,94 +618,12 @@ class WC_Tracker { /** * Get order totals * + * @deprecated 4.10.0 Logic moved to get_orders. * @return array */ public static function get_order_totals() { - global $wpdb; - - $gross_total = $wpdb->get_var( - " - SELECT - SUM( order_meta.meta_value ) AS 'gross_total' - FROM {$wpdb->prefix}posts AS orders - LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID - WHERE order_meta.meta_key = '_order_total' - AND orders.post_status in ( 'wc-completed', 'wc-refunded' ) - GROUP BY order_meta.meta_key - " - ); - - if ( is_null( $gross_total ) ) { - $gross_total = 0; - } - - $processing_gross_total = $wpdb->get_var( - " - SELECT - SUM( order_meta.meta_value ) AS 'gross_total' - FROM {$wpdb->prefix}posts AS orders - LEFT JOIN {$wpdb->prefix}postmeta AS order_meta ON order_meta.post_id = orders.ID - WHERE order_meta.meta_key = '_order_total' - AND orders.post_status = 'wc-processing' - GROUP BY order_meta.meta_key - " - ); - - if ( is_null( $processing_gross_total ) ) { - $processing_gross_total = 0; - } - - return array( - 'gross' => $gross_total, - 'processing_gross' => $processing_gross_total, - ); - } - - /** - * Get last order date - * - * @return string - */ - private static function get_order_dates() { - global $wpdb; - - $min_max = $wpdb->get_row( - " - SELECT - MIN( post_date_gmt ) as 'first', MAX( post_date_gmt ) as 'last' - FROM {$wpdb->prefix}posts - WHERE post_type = 'shop_order' - AND post_status = 'wc-completed' - ", - ARRAY_A - ); - - if ( is_null( $min_max ) ) { - $min_max = array( - 'first' => '-', - 'last' => '-', - ); - } - - $processing_min_max = $wpdb->get_row( - " - SELECT - MIN( post_date_gmt ) as 'processing_first', MAX( post_date_gmt ) as 'processing_last' - FROM {$wpdb->prefix}posts - WHERE post_type = 'shop_order' - AND post_status = 'wc-processing' - ", - ARRAY_A - ); - - if ( is_null( $processing_min_max ) ) { - $processing_min_max = array( - 'processing_first' => '-', - 'processing_last' => '-', - ); - } - - return array_merge( $min_max, $processing_min_max ); + wc_deprecated_function( 'WC_Tracker::get_order_totals', '4.10.0', '' ); + return self::get_orders(); } /** From 2e8b6cb0f15a1f76c922faf76e283c19b8c92876 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 11 Jan 2021 16:31:47 +0100 Subject: [PATCH 053/120] Update test to include lower case method --- .../specs/merchant/wp-admin-order-apply-coupon.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js index a51d435e1d0..5f745e6af6b 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js @@ -50,7 +50,7 @@ const runOrderApplyCouponTest = () => { // Verify the coupon list is showing await page.waitForSelector('.wc-used-coupons'); await expect(page).toMatchElement('.wc_coupon_list', { text: 'Coupon(s)' }); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() }); // Check that the coupon has been applied await expect(page).toMatchElement('.wc-order-item-discount', { text: '5.00' }); @@ -60,7 +60,7 @@ const runOrderApplyCouponTest = () => { it('can remove a coupon', async () => { // Make sure we have a coupon on the page to use await page.waitForSelector('.wc-used-coupons'); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() }); // We need to use this here as `expect(page).toClick()` was unable to find the element // See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219 @@ -69,7 +69,7 @@ const runOrderApplyCouponTest = () => { await uiUnblocked(); // Verify the coupon pricing has been removed - await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); + await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() }); await expect(page).not.toMatchElement('.wc-order-item-discount', { text: '5.00' }); await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: '4.99' }); From 633251009bb86ab216459d15d2f48a591e201dc4 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 11 Jan 2021 16:34:10 +0100 Subject: [PATCH 054/120] Update test to include lower case method --- .../specs/merchant/wp-admin-order-apply-coupon.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js index a51d435e1d0..5f745e6af6b 100644 --- a/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js +++ b/tests/e2e/core-tests/specs/merchant/wp-admin-order-apply-coupon.test.js @@ -50,7 +50,7 @@ const runOrderApplyCouponTest = () => { // Verify the coupon list is showing await page.waitForSelector('.wc-used-coupons'); await expect(page).toMatchElement('.wc_coupon_list', { text: 'Coupon(s)' }); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() }); // Check that the coupon has been applied await expect(page).toMatchElement('.wc-order-item-discount', { text: '5.00' }); @@ -60,7 +60,7 @@ const runOrderApplyCouponTest = () => { it('can remove a coupon', async () => { // Make sure we have a coupon on the page to use await page.waitForSelector('.wc-used-coupons'); - await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); + await expect(page).toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() }); // We need to use this here as `expect(page).toClick()` was unable to find the element // See: https://github.com/puppeteer/puppeteer/issues/1769#issuecomment-637645219 @@ -69,7 +69,7 @@ const runOrderApplyCouponTest = () => { await uiUnblocked(); // Verify the coupon pricing has been removed - await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: 'code-fixed cart discount' }); + await expect(page).not.toMatchElement('.wc_coupon_list li.code.editable', { text: couponCode.toLowerCase() }); await expect(page).not.toMatchElement('.wc-order-item-discount', { text: '5.00' }); await expect(page).not.toMatchElement('.line-cost .view .woocommerce-Price-amount', { text: '4.99' }); From 8b85a739773434eb784217789023c1b65143888f Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 11 Jan 2021 16:40:40 +0100 Subject: [PATCH 055/120] Fix white spaces issue --- .../front-end-checkout-coupons.test.js | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js index a481dc58cef..446c3af5c6a 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-checkout-coupons.test.js @@ -5,7 +5,7 @@ const { shopper, merchant, - createCoupon, + createCoupon, createSimpleProduct, uiUnblocked } = require( '@woocommerce/e2e-utils' ); @@ -26,8 +26,8 @@ const runCheckoutApplyCouponsTest = () => { let couponFixedProduct; beforeAll(async () => { await merchant.login(); - await createSimpleProduct(); - couponFixedCart = await createCoupon(); + await createSimpleProduct(); + couponFixedCart = await createCoupon(); couponPercentage = await createCoupon('50', 'Percentage discount'); couponFixedProduct = await createCoupon('5', 'Fixed product discount'); await merchant.logout(); @@ -37,29 +37,29 @@ const runCheckoutApplyCouponsTest = () => { await shopper.goToShop(); await shopper.addToCartFromShopPage('Simple product'); await uiUnblocked(); - await shopper.goToCheckout(); + await shopper.goToCheckout(); - // Apply Fixed cart discount coupon + // Apply Fixed cart discount coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); await expect(page).toFill('#coupon_code', couponFixedCart); await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - + // Wait for page to expand total calculations to avoid flakyness await page.waitForSelector('.order-total'); // Verify discount applied and order total await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); await page.waitForSelector('.order-total .amount', {text: '$4.99'}); - + // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - - // Apply Percentage discount coupon + + // Apply Percentage discount coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); await expect(page).toFill('#coupon_code', couponPercentage); @@ -68,13 +68,13 @@ const runCheckoutApplyCouponsTest = () => { await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$4.99'}); await page.waitForSelector('.order-total .amount', {text: '$5.00'}); - + // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - // Apply Fixed product discount coupon + // Apply Fixed product discount coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); await expect(page).toFill('#coupon_code', couponFixedProduct); @@ -83,8 +83,8 @@ const runCheckoutApplyCouponsTest = () => { await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); await page.waitForSelector('.order-total .amount', {text: '$4.99'}); - - // Try to apply the same coupon + + // Try to apply the same coupon await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); await expect(page).toFill('#coupon_code', couponFixedProduct); @@ -92,7 +92,7 @@ const runCheckoutApplyCouponsTest = () => { await uiUnblocked(); await page.waitForSelector('.woocommerce-error', { text: 'Coupon code already applied!' }); - // Try to apply multiple coupons + // Try to apply multiple coupons await expect(page).toClick('a', {text: 'Click here to enter your code'}); await uiUnblocked(); await expect(page).toFill('#coupon_code', couponFixedCart); @@ -104,13 +104,13 @@ const runCheckoutApplyCouponsTest = () => { // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); - await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); - await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - - // Verify the total amount after all coupons removal - await page.waitForSelector('.order-total .amount', {text: '$9.99'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Verify the total amount after all coupons removal + await page.waitForSelector('.order-total .amount', {text: '$9.99'}); }); }); }; From 760a879cb9aa4def74b574ac4de598cdf38e6277 Mon Sep 17 00:00:00 2001 From: Veljko V Date: Mon, 11 Jan 2021 16:45:31 +0100 Subject: [PATCH 056/120] Fix white spaces issue --- .../shopper/front-end-cart-coupons.test.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js index d77bf76274b..288c11eea48 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-cart-coupons.test.js @@ -5,7 +5,7 @@ const { shopper, merchant, - createCoupon, + createCoupon, createSimpleProduct, uiUnblocked } = require( '@woocommerce/e2e-utils' ); @@ -26,8 +26,8 @@ const runCartApplyCouponsTest = () => { let couponFixedProduct; beforeAll(async () => { await merchant.login(); - await createSimpleProduct(); - couponFixedCart = await createCoupon(); + await createSimpleProduct(); + couponFixedCart = await createCoupon(); couponPercentage = await createCoupon('50', 'Percentage discount'); couponFixedProduct = await createCoupon('5', 'Fixed product discount'); await merchant.logout(); @@ -44,19 +44,19 @@ const runCartApplyCouponsTest = () => { await expect(page).toClick('button', {text: 'Apply coupon'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); - + // Wait for page to expand total calculations to avoid flakyness await page.waitForSelector('.order-total'); // Verify discount applied and order total await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); await page.waitForSelector('.order-total .amount', {text: '$4.99'}); - + // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - + // Apply Percentage discount coupon await expect(page).toFill('#coupon_code', couponPercentage); await expect(page).toClick('button', {text: 'Apply coupon'}); @@ -64,7 +64,7 @@ const runCartApplyCouponsTest = () => { await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$4.99'}); await page.waitForSelector('.order-total .amount', {text: '$5.00'}); - + // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); @@ -77,7 +77,7 @@ const runCartApplyCouponsTest = () => { await page.waitForSelector('.woocommerce-message', {text: 'Coupon code applied successfully.'}); await page.waitForSelector('.cart-discount .amount', {text: '$5.00'}); await page.waitForSelector('.order-total .amount', {text: '$4.99'}); - + // Try to apply the same coupon await expect(page).toFill('#coupon_code', couponFixedProduct); await expect(page).toClick('button', {text: 'Apply coupon'}); @@ -94,13 +94,13 @@ const runCartApplyCouponsTest = () => { // Remove coupon await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); - await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); await expect(page).toClick('.woocommerce-remove-coupon', {text: '[Remove]'}); await uiUnblocked(); - await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); - - // Verify the total amount after all coupons removal - await page.waitForSelector('.order-total .amount', {text: '$9.99'}); + await page.waitForSelector('.woocommerce-message', {text: 'Coupon has been removed.'}); + + // Verify the total amount after all coupons removal + await page.waitForSelector('.order-total .amount', {text: '$9.99'}); }); }); }; From 10eb9f832e7a1b5a2a5c520d9c648925d84e5bde Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Mon, 11 Jan 2021 16:21:52 +0100 Subject: [PATCH 057/120] Remove one of the duplicate handlers for 'adjust_download_permissions' --- includes/class-woocommerce.php | 3 --- src/Internal/DownloadPermissionsAdjuster.php | 8 -------- 2 files changed, 11 deletions(-) diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 22d6efd223b..6a03eecbb55 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -8,7 +8,6 @@ defined( 'ABSPATH' ) || exit; -use Automattic\WooCommerce\Internal\DownloadPermissionsAdjuster; use Automattic\WooCommerce\Proxies\LegacyProxy; /** @@ -203,8 +202,6 @@ final class WooCommerce { add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 ); add_action( 'activated_plugin', array( $this, 'activated_plugin' ) ); add_action( 'deactivated_plugin', array( $this, 'deactivated_plugin' ) ); - - DownloadPermissionsAdjuster::init_hooks(); } /** diff --git a/src/Internal/DownloadPermissionsAdjuster.php b/src/Internal/DownloadPermissionsAdjuster.php index 18fdea7478c..2169c14e5ff 100644 --- a/src/Internal/DownloadPermissionsAdjuster.php +++ b/src/Internal/DownloadPermissionsAdjuster.php @@ -26,14 +26,6 @@ final class DownloadPermissionsAdjuster { add_action('adjust_download_permissions', array($this, 'adjust_download_permissions'), 10, 1); } - /** - * Initialize the hooks used by this class. - */ - public static function init_hooks() - { - add_action('adjust_download_permissions', array(wc_get_container()->get(self::class), 'adjust_download_permissions'), 10, 1); - } - /** * Schedule a download permissions adjustment for a product if necessary. * This should be executed whenever a product is saved. From afe7bdc6b34ef7377a12fab235e857c6eedf26f5 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Mon, 11 Jan 2021 16:22:47 +0100 Subject: [PATCH 058/120] Add a test for the added functionality of LegacyProxy. --- .../ExampleClasses/ClassWithLoadMethod.php | 35 +++++++++++++++++++ tests/php/src/Proxies/LegacyProxyTest.php | 12 +++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/php/src/Internal/DependencyManagement/ExampleClasses/ClassWithLoadMethod.php diff --git a/tests/php/src/Internal/DependencyManagement/ExampleClasses/ClassWithLoadMethod.php b/tests/php/src/Internal/DependencyManagement/ExampleClasses/ClassWithLoadMethod.php new file mode 100644 index 00000000000..cd06a276fac --- /dev/null +++ b/tests/php/src/Internal/DependencyManagement/ExampleClasses/ClassWithLoadMethod.php @@ -0,0 +1,35 @@ +assertEquals( array( 'foo', 'bar' ), \ClassWithSingleton::$instance_args ); } + /** + * @testdox 'get_instance_of' uses the 'load' static method in classes that implement it, passing the supplied arguments. + */ + public function test_get_instance_of_class_with_load_method_gets_an_instance_of_the_appropriate_class() { + // ClassWithLoadMethod is in the root namespace and thus can't be autoloaded. + require_once dirname( __DIR__ ) . '/Internal/DependencyManagement/ExampleClasses/ClassWithLoadMethod.php'; + + $loaded = $this->sut->get_instance_of( \ClassWithLoadMethod::class, 'foo', 'bar' ); + $this->assertSame( \ClassWithLoadMethod::$loaded, $loaded ); + $this->assertEquals( array( 'foo', 'bar' ), \ClassWithLoadMethod::$loaded_args ); + } + /** * @testdox 'get_instance_of' can be used to get an instance of a class implementing WC_Queue_Interface. */ From 2d83db8b66d87baf8487cf2b1a62fd72afa9b443 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Tue, 12 Jan 2021 12:07:38 +0100 Subject: [PATCH 059/120] Add tests for DownloadPermissionsAdjuster::maybe_schedule_adjust_download_permissions Also replace direct invocations of functions with usages of the LegacyProxy whenever needed, and code style ajustments. --- src/Internal/DownloadPermissionsAdjuster.php | 47 +++--- .../DownloadPermissionsAdjusterTest.php | 135 ++++++++++++++++++ 2 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 tests/php/src/Internal/DownloadPermissionsAdjusterTest.php diff --git a/src/Internal/DownloadPermissionsAdjuster.php b/src/Internal/DownloadPermissionsAdjuster.php index 2169c14e5ff..bd379859124 100644 --- a/src/Internal/DownloadPermissionsAdjuster.php +++ b/src/Internal/DownloadPermissionsAdjuster.php @@ -10,20 +10,23 @@ defined( 'ABSPATH' ) || exit; /** * Class to adjust download permissions on product save. */ -final class DownloadPermissionsAdjuster { +class DownloadPermissionsAdjuster { /** + * The downloads data store to use. + * * @var WC_Data_Store */ private $downloads_data_store; /** * Class initialization, to be executed when the class is resolved by the container. + * + * @internal */ - public function init() - { - $this->downloads_data_store = WC()->get_instance_of( \WC_Data_Store::class, 'customer-download'); - add_action('adjust_download_permissions', array($this, 'adjust_download_permissions'), 10, 1); + final public function init() { + $this->downloads_data_store = WC()->get_instance_of( \WC_Data_Store::class, 'customer-download' ); + add_action( 'adjust_download_permissions', array( $this, 'adjust_download_permissions' ), 10, 1 ); } /** @@ -34,22 +37,30 @@ final class DownloadPermissionsAdjuster { */ public function maybe_schedule_adjust_download_permissions( \WC_Product $product ) { $children_ids = $product->get_children(); - if (!$children_ids) { + if ( ! $children_ids ) { return; } $scheduled_action_args = array( $product->get_id() ); - $already_scheduled_actions = as_get_scheduled_actions( - array( - 'hook' => 'adjust_download_permissions', - 'args' => $scheduled_action_args, - 'status' => \ActionScheduler_Store::STATUS_PENDING - ) - ); + $already_scheduled_actions = + WC()->call_function( + 'as_get_scheduled_actions', + array( + 'hook' => 'adjust_download_permissions', + 'args' => $scheduled_action_args, + 'status' => \ActionScheduler_Store::STATUS_PENDING, + ), + 'ids' + ); - if(empty($already_scheduled_actions)) { - as_schedule_single_action(time() + 1, 'adjust_download_permissions', $scheduled_action_args); + if ( empty( $already_scheduled_actions ) ) { + WC()->call_function( + 'as_schedule_single_action', + WC()->call_function( 'time' ) + 1, + 'adjust_download_permissions', + $scheduled_action_args + ); } } @@ -72,12 +83,12 @@ final class DownloadPermissionsAdjuster { $product = wc_get_product( $product_id ); $children_ids = $product->get_children(); - if (!$children_ids) { + if ( ! $children_ids ) { return; } - $parent_downloads = $this->get_download_files_and_permissions($product); - if (!$parent_downloads) { + $parent_downloads = $this->get_download_files_and_permissions( $product ); + if ( ! $parent_downloads ) { return; } diff --git a/tests/php/src/Internal/DownloadPermissionsAdjusterTest.php b/tests/php/src/Internal/DownloadPermissionsAdjusterTest.php new file mode 100644 index 00000000000..cdddc7c5e9d --- /dev/null +++ b/tests/php/src/Internal/DownloadPermissionsAdjusterTest.php @@ -0,0 +1,135 @@ +sut = new DownloadPermissionsAdjuster(); + $this->sut->init(); + } + + /** + * @testdox DownloadPermissionsAdjuster class hooks on 'adjust_download_permissions' on initialization. + */ + public function test_class_hooks_on_adjust_download_permissions() + { + remove_all_actions('adjust_download_permissions'); + $this->assertFalse(has_action('adjust_download_permissions')); + $this->setUp(); + $this->assertTrue(has_action('adjust_download_permissions')); + } + + /** + * @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the product has no children. + */ + public function test_no_adjustment_is_scheduled_if_product_has_no_children() { + $as_get_scheduled_actions_invoked = false; + $as_schedule_single_action_invoked = false; + + $this->register_legacy_proxy_function_mocks( + array( + 'as_get_scheduled_actions' => function($args, $return_format) use(&$as_get_scheduled_actions_invoked) { + $as_get_scheduled_actions_invoked = true; + }, + 'as_schedule_single_action' => function($timestamp, $hook, $args) use(&$as_schedule_single_action_invoked) { + $as_schedule_single_action_invoked = true; + } + ) + ); + + $product = ProductHelper::create_simple_product(); + $this->sut->maybe_schedule_adjust_download_permissions($product); + + $this->assertFalse($as_get_scheduled_actions_invoked); + $this->assertFalse($as_schedule_single_action_invoked); + } + + /** + * @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the an adjustment is already pending. + */ + public function test_no_adjustment_is_scheduled_if_already_scheduled() { + $as_get_scheduled_actions_args = null; + $as_schedule_single_action_invoked = false; + + $this->register_legacy_proxy_function_mocks( + array( + 'as_get_scheduled_actions' => function($args, $return_format) use(&$as_get_scheduled_actions_args) { + $as_get_scheduled_actions_args = $args; + return array(1); + }, + 'as_schedule_single_action' => function($timestamp, $hook, $args) use(&$as_schedule_single_action_invoked) { + $as_schedule_single_action_invoked = true; + } + ) + ); + + $product = ProductHelper::create_variation_product(); + $this->sut->maybe_schedule_adjust_download_permissions($product); + + $expected_get_scheduled_actions_args = array( + 'hook' => 'adjust_download_permissions', + 'args' => array($product->get_id()), + 'status' => \ActionScheduler_Store::STATUS_PENDING + ); + $this->assertEquals($expected_get_scheduled_actions_args, $as_get_scheduled_actions_args); + $this->assertFalse($as_schedule_single_action_invoked); + } + + /** + * @testdox 'maybe_schedule_adjust_download_permissions' schedules an adjustment if not scheduled already. + */ + public function test_no_adjustment_is_scheduled_if_not_yet_scheduled() { + $as_get_scheduled_actions_args = null; + $as_schedule_single_action_args = null; + + $this->register_legacy_proxy_function_mocks( + array( + 'as_get_scheduled_actions' => function($params, $return_format) use(&$as_get_scheduled_actions_args) { + $as_get_scheduled_actions_args = $params; + return array(); + }, + 'as_schedule_single_action' => function($timestamp, $hook, $args) use(&$as_schedule_single_action_args) { + $as_schedule_single_action_args = array( $timestamp, $hook, $args); + }, + 'time' => function() { return 0; } + ) + ); + + $product = ProductHelper::create_variation_product(); + $this->sut->maybe_schedule_adjust_download_permissions($product); + + $expected_get_scheduled_actions_args = array( + 'hook' => 'adjust_download_permissions', + 'args' => array($product->get_id()), + 'status' => \ActionScheduler_Store::STATUS_PENDING + ); + $this->assertEquals($expected_get_scheduled_actions_args, $as_get_scheduled_actions_args); + + $expected_as_schedule_single_action_args = array( + 1, + 'adjust_download_permissions', + array( $product->get_id() ) + ); + $this->assertEquals($expected_as_schedule_single_action_args, $as_schedule_single_action_args); + } +} From a283433f4dee8ba6a5ed764687c419272309a3b4 Mon Sep 17 00:00:00 2001 From: budzanowski Date: Tue, 12 Jan 2021 14:41:55 +0100 Subject: [PATCH 060/120] Add block utils unit tests. --- .../blocks/class-wc-tests-blocks-utils.php | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 tests/legacy/unit-tests/blocks/class-wc-tests-blocks-utils.php diff --git a/tests/legacy/unit-tests/blocks/class-wc-tests-blocks-utils.php b/tests/legacy/unit-tests/blocks/class-wc-tests-blocks-utils.php new file mode 100644 index 00000000000..907846f88f2 --- /dev/null +++ b/tests/legacy/unit-tests/blocks/class-wc-tests-blocks-utils.php @@ -0,0 +1,113 @@ + 'blocks-page', + 'title' => 'Checkout', + 'content' => '
', + ); + + $page_id = wc_create_page( $page['name'], '', $page['title'], $page['content'] ); + + $this->assertTrue( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/checkout' ) ); + $this->assertFalse( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/cart' ) ); + } + + /** + * @group block-utils + * Test: has_block_in_page. + * + */ + public function test_has_block_in_page_on_page_with_no_blocks() { + $page = array( + 'name' => 'shortcode-page', + 'title' => 'Checkout', + 'content' => ' [woocommerce_checkout] ', + ); + + $page_id = wc_create_page( $page['name'], '', $page['title'], $page['content'] ); + + $this->assertFalse( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/checkout' ) ); + $this->assertFalse( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/cart' ) ); + } + + /** + * @group block-utils + * Test: has_block_in_page. + * + */ + public function test_has_block_in_page_on_page_with_multiple_blocks() { + $page = array( + 'name' => 'shortcode-page', + 'title' => 'Checkout', + 'content' => ' + + + + + + +

test

+ ', + ); + + $page_id = wc_create_page( $page['name'], '', $page['title'], $page['content'] ); + + $this->assertTrue( WC_Blocks_Utils::has_block_in_page( $page_id, 'woocommerce/featured-product' ) ); + $this->assertTrue( WC_Blocks_Utils::has_block_in_page( $page_id, 'core/heading' ) ); + } + + /** + * @group block-utils + * Test: get_all_blocks_from_page. + * + */ + public function test_get_all_blocks_from_page() { + $page = array( + 'name' => 'cart', + 'title' => 'Checkout', + 'content' => '

test1

test2

', + ); + + wc_create_page( $page['name'], 'woocommerce_cart_page_id', $page['title'], $page['content'] ); + + $expected = array( + 0 => array( + 'blockName' => 'core/heading', + 'attrs' => array(), + 'innerBlocks' => array(), + 'innerHTML' => '

test1

', + 'innerContent' => array( + 0 => '

test1

', + ), + ), + 1 => array( + 'blockName' => 'core/heading', + 'attrs' => array(), + 'innerBlocks' => array(), + 'innerHTML' => '

test2

', + 'innerContent' => array( + 0 => '

test2

', + ), + ), + ); + + $blocks = WC_Blocks_Utils::get_blocks_from_page( 'core/heading', 'cart' ); + + $this->assertEquals( $expected, $blocks ); + } +} From 6041c10ea369e4e97c2370238c26361ec79fcf98 Mon Sep 17 00:00:00 2001 From: roykho Date: Tue, 12 Jan 2021 07:37:00 -0800 Subject: [PATCH 061/120] Add notice to deprecate old reports closes #27789 --- includes/admin/views/html-report-by-date.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/includes/admin/views/html-report-by-date.php b/includes/admin/views/html-report-by-date.php index 860226a9ccc..81310b7d7f0 100644 --- a/includes/admin/views/html-report-by-date.php +++ b/includes/admin/views/html-report-by-date.php @@ -8,9 +8,17 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } - ?> - +
+

+ + WooCommerce Analytics or learn more about the new experience in the WooCommerce Analytics documentation.', 'woocommerce' ), esc_url( wc_admin_url( '&path=/analytics/overview' ) ) ) ); + ?> + +

+
From 8d3803452d575af52abdc3a02e8963de616abd51 Mon Sep 17 00:00:00 2001 From: Steve Grunwell Date: Tue, 12 Jan 2021 12:53:22 -0500 Subject: [PATCH 062/120] Refactor the WC_Shipping_Zone_Data_Store::read() method This commit re-works the `WC_Shipping_Zone_Data_Store::read()` method in the following ways: 1. Remove a confusing conditional (`if ( 0 !== $zone->get_id() || '0' !== $zone->get_id() ) { ... }`) 2. Return early if we're dealing with Zone 0, eliminating additional conditional steps 3. Add documentation for the "woocommerce_shipping_zone_loaded" action hook --- .../class-wc-shipping-zone-data-store.php | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/includes/data-stores/class-wc-shipping-zone-data-store.php b/includes/data-stores/class-wc-shipping-zone-data-store.php index 453ec877f15..fe9c54d2159 100644 --- a/includes/data-stores/class-wc-shipping-zone-data-store.php +++ b/includes/data-stores/class-wc-shipping-zone-data-store.php @@ -74,33 +74,41 @@ class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shippin public function read( &$zone ) { global $wpdb; - $zone_data = false; - - if ( 0 !== $zone->get_id() || '0' !== $zone->get_id() ) { - $zone_data = $wpdb->get_row( - $wpdb->prepare( - "SELECT zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1", - $zone->get_id() - ) - ); - } - + // Zone 0 is used as a default if no other zones fit. if ( 0 === $zone->get_id() || '0' === $zone->get_id() ) { $this->read_zone_locations( $zone ); $zone->set_zone_name( __( 'Locations not covered by your other zones', 'woocommerce' ) ); $zone->read_meta_data(); $zone->set_object_read( true ); + + /** + * Indicate that the WooCommerce shipping zone has been loaded. + * + * @param WC_Shipping_Zone $zone The shipping zone that has been loaded. + */ do_action( 'woocommerce_shipping_zone_loaded', $zone ); - } elseif ( $zone_data ) { - $zone->set_zone_name( $zone_data->zone_name ); - $zone->set_zone_order( $zone_data->zone_order ); - $this->read_zone_locations( $zone ); - $zone->read_meta_data(); - $zone->set_object_read( true ); - do_action( 'woocommerce_shipping_zone_loaded', $zone ); - } else { + return; + } + + $zone_data = $wpdb->get_row( + $wpdb->prepare( + "SELECT zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1", + $zone->get_id() + ) + ); + + if ( ! $zone_data ) { throw new Exception( __( 'Invalid data store.', 'woocommerce' ) ); } + + $zone->set_zone_name( $zone_data->zone_name ); + $zone->set_zone_order( $zone_data->zone_order ); + $this->read_zone_locations( $zone ); + $zone->read_meta_data(); + $zone->set_object_read( true ); + + /** This action is documented in includes/datastores/class-wc-shipping-zone-data-store.php. */ + do_action( 'woocommerce_shipping_zone_loaded', $zone ); } /** From e32dd6c4d37ecd8fca8eab740678652d53f0efd5 Mon Sep 17 00:00:00 2001 From: Steve Grunwell Date: Tue, 12 Jan 2021 18:26:00 +0000 Subject: [PATCH 063/120] Add a new WC_Shipping_Zone_Data_Store_CPT_Test class to support the refactoring of WC_Shipping_Zone_Data_Store_CPT::read() --- ...class-wc-shipping-zone-data-store-test.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/php/includes/data-stores/class-wc-shipping-zone-data-store-test.php diff --git a/tests/php/includes/data-stores/class-wc-shipping-zone-data-store-test.php b/tests/php/includes/data-stores/class-wc-shipping-zone-data-store-test.php new file mode 100644 index 00000000000..cd8d1dbb6ba --- /dev/null +++ b/tests/php/includes/data-stores/class-wc-shipping-zone-data-store-test.php @@ -0,0 +1,48 @@ +set_zone_name( 'California' ); + $zone->set_zone_order( 3 ); + $zone->add_location( 'US:CA', 'state' ); + $zone->save(); + + $datastore = new WC_Shipping_Zone_Data_Store(); + $datastore->read( $zone ); + $this->assertSame( 'California', $zone->get_zone_name() ); + $this->assertSame( 3, $zone->get_zone_order() ); + $this->assertGreaterThan( 0, did_action( 'woocommerce_shipping_zone_loaded' ) ); + } + + /** + * @testdox read() sets default properties for shipping zone with ID 0. + */ + public function test_read_for_shipping_zone_zero() { + $zone = new WC_Shipping_Zone( 0 ); + + $datastore = new WC_Shipping_Zone_Data_Store(); + $datastore->read( $zone ); + $this->assertSame( 0, $zone->get_zone_order() ); + $this->assertGreaterThan( 0, did_action( 'woocommerce_shipping_zone_loaded' ) ); + } + + /** + * @testdox read() throws an exception if the zone ID cannot be found. + */ + public function test_read_with_invalid_zone_id() { + $this->expectException( \Exception::class ); + + $zone = new WC_Shipping_Zone( -1 ); + + $datastore = new WC_Shipping_Zone_Data_Store(); + $datastore->read( $zone ); + } +} From 2ec002defa2a1a82b61fafbeaf73c8d3326ffa94 Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Wed, 13 Jan 2021 12:11:15 +0100 Subject: [PATCH 064/120] Add unit tests for DownloadPermissionsAdjuster::adjust_download_permissions --- src/Internal/DownloadPermissionsAdjuster.php | 17 +- .../DownloadPermissionsAdjusterTest.php | 297 +++++++++++++++--- 2 files changed, 269 insertions(+), 45 deletions(-) diff --git a/src/Internal/DownloadPermissionsAdjuster.php b/src/Internal/DownloadPermissionsAdjuster.php index bd379859124..be650621086 100644 --- a/src/Internal/DownloadPermissionsAdjuster.php +++ b/src/Internal/DownloadPermissionsAdjuster.php @@ -131,11 +131,10 @@ class DownloadPermissionsAdjuster { * @return array[] Information about the downloadable files and permissions for the product. */ private function get_download_files_and_permissions( \WC_Product $product ) { - $result = array( + $result = array( 'permission_data_by_file_order_user' => array(), 'download_ids_by_file_url' => array(), ); - $downloads = $product->get_downloads(); foreach ( $downloads as $download ) { $result['download_ids_by_file_url'][ $download->get_file() ] = $download->get_id(); @@ -144,12 +143,14 @@ class DownloadPermissionsAdjuster { $permissions = $this->downloads_data_store->get_downloads( array( 'product_id' => $product->get_id() ) ); foreach ( $permissions as $permission ) { $permission_data = (array) $permission->data; - $file = $downloads[ $permission_data['download_id'] ]->get_file(); - $data = array( - 'file' => $file, - 'data' => (array) $permission->data, - ); - $result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data; + if ( array_key_exists( $permission_data['download_id'], $downloads ) ) { + $file = $downloads[ $permission_data['download_id'] ]->get_file(); + $data = array( + 'file' => $file, + 'data' => (array) $permission->data, + ); + $result['permission_data_by_file_order_user'][ "${file}:${permission_data['user_id']}:${permission_data['order_id']}" ] = $data; + } } return $result; diff --git a/tests/php/src/Internal/DownloadPermissionsAdjusterTest.php b/tests/php/src/Internal/DownloadPermissionsAdjusterTest.php index cdddc7c5e9d..e10b5e5214c 100644 --- a/tests/php/src/Internal/DownloadPermissionsAdjusterTest.php +++ b/tests/php/src/Internal/DownloadPermissionsAdjusterTest.php @@ -11,8 +11,8 @@ use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper; /** * Tests for DownloadPermissionsAdjuster. */ -class DownloadPermissionsAdjusterTest extends \WC_Unit_Test_Case -{ +class DownloadPermissionsAdjusterTest extends \WC_Unit_Test_Case { + /** * The system under test. * @@ -26,110 +26,333 @@ class DownloadPermissionsAdjusterTest extends \WC_Unit_Test_Case public function setUp() { $this->sut = new DownloadPermissionsAdjuster(); $this->sut->init(); + + // This is needed for "product->set_downloads" to work without actual files. + add_filter( + 'woocommerce_downloadable_file_allowed_mime_types', + function() { + return array( 'foo' => 'nonsense/foo' ); + } + ); + add_filter( + 'woocommerce_downloadable_file_exists', + function( $exists, $filename ) { + return true; + }, + 2, + 10 + ); } /** * @testdox DownloadPermissionsAdjuster class hooks on 'adjust_download_permissions' on initialization. */ - public function test_class_hooks_on_adjust_download_permissions() - { - remove_all_actions('adjust_download_permissions'); - $this->assertFalse(has_action('adjust_download_permissions')); + public function test_class_hooks_on_adjust_download_permissions() { + remove_all_actions( 'adjust_download_permissions' ); + $this->assertFalse( has_action( 'adjust_download_permissions' ) ); $this->setUp(); - $this->assertTrue(has_action('adjust_download_permissions')); + $this->assertTrue( has_action( 'adjust_download_permissions' ) ); } /** * @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the product has no children. */ public function test_no_adjustment_is_scheduled_if_product_has_no_children() { - $as_get_scheduled_actions_invoked = false; + $as_get_scheduled_actions_invoked = false; $as_schedule_single_action_invoked = false; $this->register_legacy_proxy_function_mocks( array( - 'as_get_scheduled_actions' => function($args, $return_format) use(&$as_get_scheduled_actions_invoked) { + 'as_get_scheduled_actions' => function( $args, $return_format ) use ( &$as_get_scheduled_actions_invoked ) { $as_get_scheduled_actions_invoked = true; }, - 'as_schedule_single_action' => function($timestamp, $hook, $args) use(&$as_schedule_single_action_invoked) { + 'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_invoked ) { $as_schedule_single_action_invoked = true; - } + }, ) ); $product = ProductHelper::create_simple_product(); - $this->sut->maybe_schedule_adjust_download_permissions($product); + $this->sut->maybe_schedule_adjust_download_permissions( $product ); - $this->assertFalse($as_get_scheduled_actions_invoked); - $this->assertFalse($as_schedule_single_action_invoked); + $this->assertFalse( $as_get_scheduled_actions_invoked ); + $this->assertFalse( $as_schedule_single_action_invoked ); } /** * @testdox 'maybe_schedule_adjust_download_permissions' does nothing if the an adjustment is already pending. */ public function test_no_adjustment_is_scheduled_if_already_scheduled() { - $as_get_scheduled_actions_args = null; + $as_get_scheduled_actions_args = null; $as_schedule_single_action_invoked = false; $this->register_legacy_proxy_function_mocks( array( - 'as_get_scheduled_actions' => function($args, $return_format) use(&$as_get_scheduled_actions_args) { + 'as_get_scheduled_actions' => function( $args, $return_format ) use ( &$as_get_scheduled_actions_args ) { $as_get_scheduled_actions_args = $args; - return array(1); + return array( 1 ); }, - 'as_schedule_single_action' => function($timestamp, $hook, $args) use(&$as_schedule_single_action_invoked) { + 'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_invoked ) { $as_schedule_single_action_invoked = true; - } + }, ) ); $product = ProductHelper::create_variation_product(); - $this->sut->maybe_schedule_adjust_download_permissions($product); + $this->sut->maybe_schedule_adjust_download_permissions( $product ); $expected_get_scheduled_actions_args = array( - 'hook' => 'adjust_download_permissions', - 'args' => array($product->get_id()), - 'status' => \ActionScheduler_Store::STATUS_PENDING + 'hook' => 'adjust_download_permissions', + 'args' => array( $product->get_id() ), + 'status' => \ActionScheduler_Store::STATUS_PENDING, ); - $this->assertEquals($expected_get_scheduled_actions_args, $as_get_scheduled_actions_args); - $this->assertFalse($as_schedule_single_action_invoked); + $this->assertEquals( $expected_get_scheduled_actions_args, $as_get_scheduled_actions_args ); + $this->assertFalse( $as_schedule_single_action_invoked ); } /** * @testdox 'maybe_schedule_adjust_download_permissions' schedules an adjustment if not scheduled already. */ public function test_no_adjustment_is_scheduled_if_not_yet_scheduled() { - $as_get_scheduled_actions_args = null; + $as_get_scheduled_actions_args = null; $as_schedule_single_action_args = null; $this->register_legacy_proxy_function_mocks( array( - 'as_get_scheduled_actions' => function($params, $return_format) use(&$as_get_scheduled_actions_args) { + 'as_get_scheduled_actions' => function( $params, $return_format ) use ( &$as_get_scheduled_actions_args ) { $as_get_scheduled_actions_args = $params; return array(); }, - 'as_schedule_single_action' => function($timestamp, $hook, $args) use(&$as_schedule_single_action_args) { - $as_schedule_single_action_args = array( $timestamp, $hook, $args); + 'as_schedule_single_action' => function( $timestamp, $hook, $args ) use ( &$as_schedule_single_action_args ) { + $as_schedule_single_action_args = array( $timestamp, $hook, $args ); }, - 'time' => function() { return 0; } + 'time' => function() { + return 0; }, ) ); $product = ProductHelper::create_variation_product(); - $this->sut->maybe_schedule_adjust_download_permissions($product); + $this->sut->maybe_schedule_adjust_download_permissions( $product ); $expected_get_scheduled_actions_args = array( - 'hook' => 'adjust_download_permissions', - 'args' => array($product->get_id()), - 'status' => \ActionScheduler_Store::STATUS_PENDING + 'hook' => 'adjust_download_permissions', + 'args' => array( $product->get_id() ), + 'status' => \ActionScheduler_Store::STATUS_PENDING, ); - $this->assertEquals($expected_get_scheduled_actions_args, $as_get_scheduled_actions_args); + $this->assertEquals( $expected_get_scheduled_actions_args, $as_get_scheduled_actions_args ); $expected_as_schedule_single_action_args = array( 1, 'adjust_download_permissions', - array( $product->get_id() ) + array( $product->get_id() ), ); - $this->assertEquals($expected_as_schedule_single_action_args, $as_schedule_single_action_args); + $this->assertEquals( $expected_as_schedule_single_action_args, $as_schedule_single_action_args ); + } + + /** + * @testdox 'adjust_download_permissions' creates child download permissions when they are missing (see method comment for details). + */ + public function test_adjust_download_permissions_creates_additional_permissions_if_not_exist() { + $download = array( + 'name' => 'the_file', + 'file' => 'the_file.foo', + ); + + $product = ProductHelper::create_variation_product(); + $product->set_downloads( array( $download ) ); + $product->save(); + $parent_download_id = current( $product->get_downloads() )->get_id(); + + $child = wc_get_product( current( $product->get_children() ) ); + $child->set_downloads( array( $download ) ); + $child->save(); + $child_download_id = current( $child->get_downloads() )->get_id(); + + $data_for_data_store = + array( + $product->get_id() => + array( + (object) array( + 'data' => array( + 'download_id' => $parent_download_id, + 'user_id' => 1234, + 'order_id' => 5678, + 'downloads_remaining' => 34, + 'access_granted' => '2000-01-01', + 'access_expires' => '2034-02-27', + ), + ), + ), + ); + + $data_store = $this->create_mock_data_store( $data_for_data_store ); + + $this->setUp(); + $this->sut->adjust_download_permissions( $product->get_id() ); + + $expected_created_data = array( + 'download_id' => $child_download_id, + 'user_id' => 1234, + 'order_id' => 5678, + 'product_id' => $child->get_id(), + 'downloads_remaining' => 34, + 'access_granted' => '2000-01-01', + 'access_expires' => '2034-02-27', + ); + + $this->assertEquals( $expected_created_data, $data_store->created_data ); + } + + /** + * @testdox 'adjust_download_permissions' doesn't create child download permissions that already exist. + */ + public function test_adjust_download_permissions_dont_create_additional_permissions_if_already_exists() { + $download = array( + 'name' => 'the_file', + 'file' => 'the_file.foo', + ); + + $product = ProductHelper::create_variation_product(); + $product->set_downloads( array( $download ) ); + $product->save(); + $parent_download_id = current( $product->get_downloads() )->get_id(); + + $child = wc_get_product( current( $product->get_children() ) ); + $child->set_downloads( array( $download ) ); + $child->save(); + $child_download_id = current( $child->get_downloads() )->get_id(); + + $data_for_data_store = + array( + $product->get_id() => + array( + (object) array( + 'data' => array( + 'download_id' => $parent_download_id, + 'user_id' => 1234, + 'order_id' => 5678, + ), + ), + ), + $child->get_id() => + array( + (object) array( + 'data' => array( + 'download_id' => $child_download_id, + 'user_id' => 1234, + 'order_id' => 5678, + ), + ), + ), + ); + + $data_store = $this->create_mock_data_store( $data_for_data_store ); + + $this->setUp(); + $this->sut->adjust_download_permissions( $product->get_id() ); + + $this->assertEmpty( $data_store->created_data ); + } + + /** + * @testdox 'adjust_download_permissions' creates child download permissions when one exists but for a different order or customer id. + * + * @testWith [9999, 5678] + * [1234, 9999] + * @param int $user_id User id the child download permission exists for. + * @param int $order_id Order id the child download permission exists for. + */ + public function test_adjust_download_permissions_creates_additional_permissions_if_exists_but_not_matching( $user_id, $order_id ) { + $download = array( + 'name' => 'the_file', + 'file' => 'the_file.foo', + ); + + $product = ProductHelper::create_variation_product(); + $product->set_downloads( array( $download ) ); + $product->save(); + $parent_download_id = current( $product->get_downloads() )->get_id(); + + $child = wc_get_product( current( $product->get_children() ) ); + $child->set_downloads( array( $download ) ); + $child->save(); + $child_download_id = current( $child->get_downloads() )->get_id(); + + $data_for_data_store = + array( + $product->get_id() => + array( + (object) array( + 'data' => array( + 'download_id' => $parent_download_id, + 'user_id' => 1234, + 'order_id' => 5678, + ), + ), + ), + $child->get_id() => + array( + (object) array( + 'data' => array( + 'download_id' => $child_download_id, + 'user_id' => $user_id, + 'order_id' => $order_id, + ), + ), + ), + ); + + $data_store = $this->create_mock_data_store( $data_for_data_store ); + + $this->setUp(); + $this->sut->adjust_download_permissions( $product->get_id() ); + + $expected = array( + 'download_id' => $child_download_id, + 'user_id' => 1234, + 'order_id' => 5678, + 'product_id' => $child->get_id(), + ); + + $this->assertEquals( $expected, $data_store->created_data ); + } + + /** + * Create and register a mock customer downloads data store. + * + * @param array $data An array where keys are product ids, and values are what 'get_downloads' will return for that input. + * @return object An object that mocks the customer downloads data store. + */ + private function create_mock_data_store( $data ) { + // phpcs:disable Squiz.Commenting + $data_store = new class($data) { + private $data; + public $created_data = null; + + public function __construct( $data ) { + $this->data = $data; + } + + public function get_downloads( $params ) { + if ( array_key_exists( $params['product_id'], $this->data ) ) { + return $this->data[ $params['product_id'] ]; + } else { + return array(); + } + } + + public function create_from_data( $data ) { + $this->created_data = $data; + } + }; + // phpcs:enable Squiz.Commenting + + $this->register_legacy_proxy_class_mocks( + array( + 'WC_Data_Store' => $data_store, + ) + ); + + return $data_store; } } From 8e7a59d9597a26da5247b5c63ac06d642783bf79 Mon Sep 17 00:00:00 2001 From: roykho Date: Wed, 13 Jan 2021 07:00:37 -0800 Subject: [PATCH 065/120] Change location of the notice so it shows up in all reports --- includes/admin/views/html-admin-page-reports.php | 10 ++++++++++ includes/admin/views/html-report-by-date.php | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/includes/admin/views/html-admin-page-reports.php b/includes/admin/views/html-admin-page-reports.php index a5ef713ee1a..89a97326c43 100644 --- a/includes/admin/views/html-admin-page-reports.php +++ b/includes/admin/views/html-admin-page-reports.php @@ -9,6 +9,16 @@ if ( ! defined( 'ABSPATH' ) ) { ?>
+
+

+ + WooCommerce Analytics or learn more about the new experience in the WooCommerce Analytics documentation.', 'woocommerce' ), esc_url( wc_admin_url( '&path=/analytics/overview' ) ) ) ); + ?> + +

+