From 7f4c0d033449b35700f068965ea9b6451eee6588 Mon Sep 17 00:00:00 2001
From: Gerhard
Date: Thu, 20 Jun 2019 14:43:19 +0200
Subject: [PATCH 001/153] Store template cache value without ABSPATH to avoid
issues with multi container environments.
---
includes/wc-core-functions.php | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 67d245dbcdf..918536acab7 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -185,7 +185,16 @@ function wc_get_template_part( $slug, $name = '' ) {
);
}
- wp_cache_set( $cache_key, $template, 'woocommerce' );
+ // Remove ABSPATH from template cache to avoid issues with multi container having different paths.
+ if ( 0 === strpos( $template, ABSPATH ) ) {
+ $template = str_replace( ABSPATH, '', $template );
+ wp_cache_set( $cache_key, $template, 'woocommerce' );
+ }
+ }
+
+ // Add back ABSPATH to template location so it loads correctly, only if it does not exist yet.
+ if ( false === strpos( $template, ABSPATH ) ) {
+ $template = ABSPATH . $template;
}
// Allow 3rd party plugins to filter template file from their plugin.
@@ -210,7 +219,16 @@ function wc_get_template( $template_name, $args = array(), $template_path = '',
if ( ! $template ) {
$template = wc_locate_template( $template_name, $template_path, $default_path );
- wp_cache_set( $cache_key, $template, 'woocommerce' );
+ // Remove ABSPATH from template cache to avoid issues with multi container having different paths.
+ if ( 0 === strpos( $template, ABSPATH ) ) {
+ $template = str_replace( ABSPATH, '', $template );
+ wp_cache_set( $cache_key, $template, 'woocommerce' );
+ }
+ }
+
+ // Add back ABSPATH to template location so it loads correctly, only if it does not exist yet.
+ if ( false === strpos( $template, ABSPATH ) ) {
+ $template = ABSPATH . $template;
}
// Allow 3rd party plugin filter template file from their plugin.
From c4096a1d8f0a021f1ee0cd7901538410b831cc12 Mon Sep 17 00:00:00 2001
From: Gerhard
Date: Thu, 20 Jun 2019 15:10:58 +0200
Subject: [PATCH 002/153] Cater for blank paths, do not prepend ABSPATH when
template path blank.
---
includes/wc-core-functions.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 918536acab7..203ceec8239 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -193,7 +193,7 @@ function wc_get_template_part( $slug, $name = '' ) {
}
// Add back ABSPATH to template location so it loads correctly, only if it does not exist yet.
- if ( false === strpos( $template, ABSPATH ) ) {
+ if ( false === strpos( $template, ABSPATH ) && ! empty( $template ) ) {
$template = ABSPATH . $template;
}
@@ -227,7 +227,7 @@ function wc_get_template( $template_name, $args = array(), $template_path = '',
}
// Add back ABSPATH to template location so it loads correctly, only if it does not exist yet.
- if ( false === strpos( $template, ABSPATH ) ) {
+ if ( false === strpos( $template, ABSPATH ) && ! empty( $template ) ) {
$template = ABSPATH . $template;
}
From 8a046ab0cdc816a2ba53df53c30f102578a9303c Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Thu, 30 Jan 2020 16:24:07 -0800
Subject: [PATCH 003/153] Cleaned up the template path replacement and fixed
the wc_cache_set positioning
---
includes/wc-core-functions.php | 28 +++++++--------
.../util/class-wc-tests-core-functions.php | 34 +++++++++++++++++++
2 files changed, 48 insertions(+), 14 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 381bcaf3b3f..2d23abffd80 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -185,17 +185,16 @@ function wc_get_template_part( $slug, $name = '' ) {
);
}
- // Remove ABSPATH from template cache to avoid issues with multi container having different paths.
+ // Don't cache the absolute path so that it can be shared between web servers with different paths.
if ( 0 === strpos( $template, ABSPATH ) ) {
- $template = str_replace( ABSPATH, '', $template );
- wp_cache_set( $cache_key, $template, 'woocommerce' );
+ $template = str_replace( ABSPATH, '{{ABSPATH}}', $template );
}
+
+ wp_cache_set( $cache_key, $template, 'woocommerce' );
}
- // Add back ABSPATH to template location so it loads correctly, only if it does not exist yet.
- if ( false === strpos( $template, ABSPATH ) && ! empty( $template ) ) {
- $template = ABSPATH . $template;
- }
+ // Make sure that the absolute path to the template is resolved.
+ $template = str_replace( '{{ABSPATH}}', ABSPATH, $template );
// Allow 3rd party plugins to filter template file from their plugin.
$template = apply_filters( 'wc_get_template_part', $template, $slug, $name );
@@ -219,17 +218,18 @@ function wc_get_template( $template_name, $args = array(), $template_path = '',
if ( ! $template ) {
$template = wc_locate_template( $template_name, $template_path, $default_path );
- // Remove ABSPATH from template cache to avoid issues with multi container having different paths.
+
+ // Don't cache the absolute path so that it can be shared between web servers with different paths.
if ( 0 === strpos( $template, ABSPATH ) ) {
- $template = str_replace( ABSPATH, '', $template );
- wp_cache_set( $cache_key, $template, 'woocommerce' );
+ // Use a token that we can easily replace.
+ $template = str_replace( ABSPATH, '{{ABSPATH}}', $template );
}
+
+ wp_cache_set( $cache_key, $template, 'woocommerce' );
}
- // Add back ABSPATH to template location so it loads correctly, only if it does not exist yet.
- if ( false === strpos( $template, ABSPATH ) && ! empty( $template ) ) {
- $template = ABSPATH . $template;
- }
+ // Make sure that the absolute path to the template is resolved.
+ $template = str_replace( '{{ABSPATH}}', ABSPATH, $template );
// Allow 3rd party plugin filter template file from their plugin.
$filter_template = apply_filters( 'wc_get_template', $template, $template_name, $args, $template_path, $default_path );
diff --git a/tests/unit-tests/util/class-wc-tests-core-functions.php b/tests/unit-tests/util/class-wc-tests-core-functions.php
index 4ae30ddb806..e59a987fe56 100644
--- a/tests/unit-tests/util/class-wc-tests-core-functions.php
+++ b/tests/unit-tests/util/class-wc-tests-core-functions.php
@@ -604,6 +604,28 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertNotEmpty( $template );
}
+ /**
+ * This test ensures that the absolute path to template files is replaced with a token. We do this so
+ * that the path can be made relative to each installation, and the cache can be shared.
+ */
+ public function test_wc_get_template_cleans_absolute_path() {
+ add_filter( 'woocommerce_locate_template', array( $this, 'force_template_path' ), 10, 2 );
+
+ ob_start();
+ try {
+ wc_get_template( 'global/wrapper-start.php' );
+ } catch ( \Exception $exception ) {
+ // Since the file doesn't really exist this is going to throw an exception (which is fine for our test).
+ }
+ ob_end_clean();
+
+ remove_filter( 'woocommerce_locatsdae_template', array( $this, 'force_template_path' ) );
+
+ $file_path = wp_cache_get( sanitize_key( 'template-global/wrapper-start.php---' . WC_VERSION ), 'woocommerce' );
+
+ $this->assertEquals( '{{ABSPATH}}global/wrapper-start.php', $file_path );
+ }
+
/**
* Test wc_get_image_size function.
*
@@ -965,4 +987,16 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertInstanceOf( 'WC_Session', $this->wc->session );
}
+
+ /**
+ * Allows us to force the template path. Since the ABSPATH is to /tmp/wordpress in tests, we need to do this
+ * in order to keep the paths consistent for testing purposes.
+ *
+ * @param string $template The path to the template file.
+ * @param string $template_name The name of the template file.
+ * @return string The path to be used instead.
+ */
+ public function force_template_path( $template, $template_name ) {
+ return ABSPATH . $template_name;
+ }
}
From b6046930c6187b29bc124c90f7201b4ab2a0e2fb Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Thu, 30 Jan 2020 18:12:00 -0800
Subject: [PATCH 004/153] Corrected a misspelled filter name
---
tests/unit-tests/util/class-wc-tests-core-functions.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/unit-tests/util/class-wc-tests-core-functions.php b/tests/unit-tests/util/class-wc-tests-core-functions.php
index e59a987fe56..63311972ad4 100644
--- a/tests/unit-tests/util/class-wc-tests-core-functions.php
+++ b/tests/unit-tests/util/class-wc-tests-core-functions.php
@@ -619,7 +619,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
}
ob_end_clean();
- remove_filter( 'woocommerce_locatsdae_template', array( $this, 'force_template_path' ) );
+ remove_filter( 'woocommerce_locate_template', array( $this, 'force_template_path' ) );
$file_path = wp_cache_get( sanitize_key( 'template-global/wrapper-start.php---' . WC_VERSION ), 'woocommerce' );
From f1b6b488b80292a37a7d8594ef1ff2838fe58a51 Mon Sep 17 00:00:00 2001
From: Lee Willis
Date: Fri, 20 Mar 2020 14:58:12 +0000
Subject: [PATCH 005/153] Add label to unlabelled taxonomies
---
includes/class-wc-post-types.php | 2 ++
1 file changed, 2 insertions(+)
diff --git a/includes/class-wc-post-types.php b/includes/class-wc-post-types.php
index 7de16656db5..ab9dcbc07d2 100644
--- a/includes/class-wc-post-types.php
+++ b/includes/class-wc-post-types.php
@@ -59,6 +59,7 @@ class WC_Post_Types {
'query_var' => is_admin(),
'rewrite' => false,
'public' => false,
+ 'label' => _x( 'Product type', 'Taxonomy name', 'woocommerce' ),
)
)
);
@@ -75,6 +76,7 @@ class WC_Post_Types {
'query_var' => is_admin(),
'rewrite' => false,
'public' => false,
+ 'label' => _x( 'Product visibility', 'Taxonomy name', 'woocommerce' ),
)
)
);
From 8f3d8f0495bbbc9bbad6e5dc132f2e1c03354965 Mon Sep 17 00:00:00 2001
From: vedanshujain
Date: Mon, 30 Mar 2020 18:05:13 +0000
Subject: [PATCH 006/153] Also cache with WC_ABSPATH to account for when WC is
out of ABSPATH.
We already substitute ABSPATH with {{ABSPATH}} token to make sure that exact template path is not cached to support deployment with multiple servers.
This patch also add tokenizing WC_ABSPATH to account for when WooCommerce is installed outside of ABSPATH.
---
includes/wc-core-functions.php | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 2d23abffd80..c607bbbd34a 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -190,11 +190,16 @@ function wc_get_template_part( $slug, $name = '' ) {
$template = str_replace( ABSPATH, '{{ABSPATH}}', $template );
}
+ if ( 0 === strpos( $template, WC_ABSPATH ) ) {
+ $template = str_replace( WC_ABSPATH, '{{WC_ABSPATH}}', $template );
+ }
+
wp_cache_set( $cache_key, $template, 'woocommerce' );
}
// Make sure that the absolute path to the template is resolved.
$template = str_replace( '{{ABSPATH}}', ABSPATH, $template );
+ $template = str_replace( '{{WC_ABSPATH}}', WC_ABSPATH, $template );
// Allow 3rd party plugins to filter template file from their plugin.
$template = apply_filters( 'wc_get_template_part', $template, $slug, $name );
From e270dfce2e8f0ffa7fcf468936447eb9835bc466 Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Sun, 5 Apr 2020 12:06:04 -0700
Subject: [PATCH 007/153] Extracted the template path tokenization so that it
can be applied to more than one define more readily
---
includes/wc-core-functions.php | 86 ++++++++++++++++++++++++++--------
1 file changed, 66 insertions(+), 20 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 2c0bc76d147..2ce2981f83d 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -150,6 +150,42 @@ function wc_update_order( $args ) {
return wc_create_order( $args );
}
+/**
+ * Given a path, this will convert any of the subpaths into their corresponding tokens.
+ *
+ * @since 4.1.0
+ * @param string $path The absolute path to tokenize.
+ * @param array $path_tokens An array keyed with the token, containing paths that should be replaced.
+ * @return string The tokenized path.
+ */
+function wc_tokenize_path( $path, $path_tokens ) {
+ foreach ( $path_tokens as $token => $token_path ) {
+ if ( 0 !== strpos( $path, $token_path ) ) {
+ continue;
+ }
+
+ $path = str_replace( $token_path, '{{' . $token . '}}', $path );
+ }
+
+ return $path;
+}
+
+/**
+ * Given a tokenized path, this will expand the tokens to their full path.
+ *
+ * @since 4.1.0
+ * @param string $path The absolute path to expand.
+ * @param array $path_tokens An array keyed with the token, containing paths that should be expanded.
+ * @return string The absolute path.
+ */
+function wc_untokenize_path( $path, $path_tokens ) {
+ foreach ( $path_tokens as $token => $token_path ) {
+ $path = str_replace( '{{' . $token . '}}', $token_path, $path );
+ }
+
+ return $path;
+}
+
/**
* Get template part (for templates like the shop-loop).
*
@@ -188,21 +224,24 @@ function wc_get_template_part( $slug, $name = '' ) {
}
// Don't cache the absolute path so that it can be shared between web servers with different paths.
- if ( 0 === strpos( $template, ABSPATH ) ) {
- $template = str_replace( ABSPATH, '{{ABSPATH}}', $template );
- }
+ $cache_path = wc_tokenize_path(
+ $template,
+ array(
+ 'ABSPATH' => ABSPATH,
+ )
+ );
- if ( 0 === strpos( $template, WC_ABSPATH ) ) {
- $template = str_replace( WC_ABSPATH, '{{WC_ABSPATH}}', $template );
- }
-
- wp_cache_set( $cache_key, $template, 'woocommerce' );
+ wp_cache_set( $cache_key, $cache_path, 'woocommerce' );
+ } else {
+ // Make sure that the absolute path to the template is resolved.
+ $template = wc_untokenize_path(
+ $template,
+ array(
+ 'ABSPATH' => ABSPATH,
+ )
+ );
}
- // Make sure that the absolute path to the template is resolved.
- $template = str_replace( '{{ABSPATH}}', ABSPATH, $template );
- $template = str_replace( '{{WC_ABSPATH}}', WC_ABSPATH, $template );
-
// Allow 3rd party plugins to filter template file from their plugin.
$template = apply_filters( 'wc_get_template_part', $template, $slug, $name );
@@ -227,17 +266,24 @@ function wc_get_template( $template_name, $args = array(), $template_path = '',
$template = wc_locate_template( $template_name, $template_path, $default_path );
// Don't cache the absolute path so that it can be shared between web servers with different paths.
- if ( 0 === strpos( $template, ABSPATH ) ) {
- // Use a token that we can easily replace.
- $template = str_replace( ABSPATH, '{{ABSPATH}}', $template );
- }
+ $cache_path = wc_tokenize_path(
+ $template,
+ array(
+ 'ABSPATH' => ABSPATH,
+ )
+ );
- wp_cache_set( $cache_key, $template, 'woocommerce' );
+ wp_cache_set( $cache_key, $cache_path, 'woocommerce' );
+ } else {
+ // Make sure that the absolute path to the template is resolved.
+ $template = wc_untokenize_path(
+ $template,
+ array(
+ 'ABSPATH' => ABSPATH,
+ )
+ );
}
- // Make sure that the absolute path to the template is resolved.
- $template = str_replace( '{{ABSPATH}}', ABSPATH, $template );
-
// Allow 3rd party plugin filter template file from their plugin.
$filter_template = apply_filters( 'wc_get_template', $template, $template_name, $args, $template_path, $default_path );
From 4aab99614a40c8b756214072e056382a77b97f74 Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Sun, 5 Apr 2020 12:15:39 -0700
Subject: [PATCH 008/153] Added tests for path tokenization/untokenization
---
.../util/class-wc-tests-core-functions.php | 66 +++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/tests/unit-tests/util/class-wc-tests-core-functions.php b/tests/unit-tests/util/class-wc-tests-core-functions.php
index ac186ab74fd..8ec37acccaf 100644
--- a/tests/unit-tests/util/class-wc-tests-core-functions.php
+++ b/tests/unit-tests/util/class-wc-tests-core-functions.php
@@ -569,6 +569,72 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertEmpty( wc_get_template_part( 'nothinghere' ) );
}
+ /**
+ * Tests the wc_tokenize_path function.
+ */
+ public function test_wc_tokenize_path() {
+ $path = wc_tokenize_path( ABSPATH . '/test', array() );
+ $this->assertEquals( ABSPATH . '/test', $path );
+
+ $path = wc_tokenize_path(
+ ABSPATH . '/test',
+ array(
+ 'ABSPATH' => ABSPATH,
+ )
+ );
+ $this->assertEquals( '{{ABSPATH}}/test', $path );
+
+ $path = wc_tokenize_path(
+ ABSPATH . '/test',
+ array(
+ 'WP_CONTENT_DIR' => WP_CONTENT_DIR,
+ )
+ );
+ $this->assertEquals( ABSPATH . '/test', $path );
+
+ $path = wc_tokenize_path(
+ WP_CONTENT_DIR . '/test',
+ array(
+ 'WP_CONTENT_DIR' => WP_CONTENT_DIR,
+ 'ABSPATH' => ABSPATH,
+ )
+ );
+ $this->assertEquals( '{{WP_CONTENT_DIR}}/test', $path );
+ }
+
+ /**
+ * Tests the wc_untokenize_path function.
+ */
+ public function test_wc_untokenize_path() {
+ $path = wc_untokenize_path( '{{ABSPATH}}/test', array() );
+ $this->assertEquals( '{{ABSPATH}}/test', $path );
+
+ $path = wc_untokenize_path(
+ '{{ABSPATH}}/test',
+ array(
+ 'ABSPATH' => ABSPATH,
+ )
+ );
+ $this->assertEquals( ABSPATH . '/test', $path );
+
+ $path = wc_untokenize_path(
+ '{{ABSPATH}}/test',
+ array(
+ 'WP_CONTENT_DIR' => WP_CONTENT_DIR,
+ )
+ );
+ $this->assertEquals( '{{ABSPATH}}/test', $path );
+
+ $path = wc_untokenize_path(
+ '{{WP_CONTENT_DIR}}/test',
+ array(
+ 'WP_CONTENT_DIR' => WP_CONTENT_DIR,
+ 'ABSPATH' => ABSPATH,
+ )
+ );
+ $this->assertEquals( WP_CONTENT_DIR . '/test', $path );
+ }
+
/**
* Test wc_get_template.
*
From 33e81654a9d5766edf8169f4a433741a5b6cb398 Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Sun, 5 Apr 2020 12:41:39 -0700
Subject: [PATCH 009/153] Made the path tokenization deterministic
Since the tokens are replaced in a first-discovered first-replaced order, we may accidentally create tokenized paths like '{{ABSPATH}}/test' instead of the desired '{{WP_CONTENT_DIR}}test'. By ordering them according to specificity however, we ensure that we tokenize as much of the path as possible.
---
includes/wc-core-functions.php | 19 +++++++++
.../util/class-wc-tests-core-functions.php | 42 ++++++++++---------
2 files changed, 41 insertions(+), 20 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 2ce2981f83d..ec2bc429d22 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -159,6 +159,25 @@ function wc_update_order( $args ) {
* @return string The tokenized path.
*/
function wc_tokenize_path( $path, $path_tokens ) {
+ // Order most to least specific so that the token can encompass as much of the path as possible.
+ uasort(
+ $path_tokens,
+ function ( $a, $b ) {
+ $a = strlen( $a );
+ $b = strlen( $b );
+
+ if ( $a > $b ) {
+ return -1;
+ }
+
+ if ( $b > $a ) {
+ return 1;
+ }
+
+ return 0;
+ }
+ );
+
foreach ( $path_tokens as $token => $token_path ) {
if ( 0 !== strpos( $path, $token_path ) ) {
continue;
diff --git a/tests/unit-tests/util/class-wc-tests-core-functions.php b/tests/unit-tests/util/class-wc-tests-core-functions.php
index 8ec37acccaf..28508b6abbb 100644
--- a/tests/unit-tests/util/class-wc-tests-core-functions.php
+++ b/tests/unit-tests/util/class-wc-tests-core-functions.php
@@ -283,7 +283,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
public function test_wc_get_log_file_path() {
$log_dir = trailingslashit( WC_LOG_DIR );
$hash_name = sanitize_file_name( wp_hash( 'unit-tests' ) );
- $date_suffix = date( 'Y-m-d', time() );
+ $date_suffix = date( 'Y-m-d', time() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$this->assertEquals( $log_dir . 'unit-tests-' . $date_suffix . '-' . $hash_name . '.log', wc_get_log_file_path( 'unit-tests' ) );
}
@@ -573,66 +573,66 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
* Tests the wc_tokenize_path function.
*/
public function test_wc_tokenize_path() {
- $path = wc_tokenize_path( ABSPATH . '/test', array() );
- $this->assertEquals( ABSPATH . '/test', $path );
+ $path = wc_tokenize_path( ABSPATH . 'test', array() );
+ $this->assertEquals( ABSPATH . 'test', $path );
$path = wc_tokenize_path(
- ABSPATH . '/test',
+ ABSPATH . 'test',
array(
'ABSPATH' => ABSPATH,
)
);
- $this->assertEquals( '{{ABSPATH}}/test', $path );
+ $this->assertEquals( '{{ABSPATH}}test', $path );
$path = wc_tokenize_path(
- ABSPATH . '/test',
+ ABSPATH . 'test',
array(
'WP_CONTENT_DIR' => WP_CONTENT_DIR,
)
);
- $this->assertEquals( ABSPATH . '/test', $path );
+ $this->assertEquals( ABSPATH . 'test', $path );
$path = wc_tokenize_path(
- WP_CONTENT_DIR . '/test',
+ WP_CONTENT_DIR . 'test',
array(
- 'WP_CONTENT_DIR' => WP_CONTENT_DIR,
'ABSPATH' => ABSPATH,
+ 'WP_CONTENT_DIR' => WP_CONTENT_DIR,
)
);
- $this->assertEquals( '{{WP_CONTENT_DIR}}/test', $path );
+ $this->assertEquals( '{{WP_CONTENT_DIR}}test', $path );
}
/**
* Tests the wc_untokenize_path function.
*/
public function test_wc_untokenize_path() {
- $path = wc_untokenize_path( '{{ABSPATH}}/test', array() );
- $this->assertEquals( '{{ABSPATH}}/test', $path );
+ $path = wc_untokenize_path( '{{ABSPATH}}test', array() );
+ $this->assertEquals( '{{ABSPATH}}test', $path );
$path = wc_untokenize_path(
- '{{ABSPATH}}/test',
+ '{{ABSPATH}}test',
array(
'ABSPATH' => ABSPATH,
)
);
- $this->assertEquals( ABSPATH . '/test', $path );
+ $this->assertEquals( ABSPATH . 'test', $path );
$path = wc_untokenize_path(
- '{{ABSPATH}}/test',
+ '{{ABSPATH}}test',
array(
'WP_CONTENT_DIR' => WP_CONTENT_DIR,
)
);
- $this->assertEquals( '{{ABSPATH}}/test', $path );
+ $this->assertEquals( '{{ABSPATH}}test', $path );
$path = wc_untokenize_path(
- '{{WP_CONTENT_DIR}}/test',
+ '{{WP_CONTENT_DIR}}test',
array(
'WP_CONTENT_DIR' => WP_CONTENT_DIR,
'ABSPATH' => ABSPATH,
)
);
- $this->assertEquals( WP_CONTENT_DIR . '/test', $path );
+ $this->assertEquals( WP_CONTENT_DIR . 'test', $path );
}
/**
@@ -682,7 +682,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
ob_start();
try {
wc_get_template( 'global/wrapper-start.php' );
- } catch ( \Exception $exception ) {
+ } catch ( \Exception $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
// Since the file doesn't really exist this is going to throw an exception (which is fine for our test).
}
ob_end_clean();
@@ -1044,7 +1044,9 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertInstanceOf( 'WC_Customer', $this->wc->customer );
$this->assertInstanceOf( 'WC_Session', $this->wc->session );
- $this->wc->cart = $this->wc->customer = $this->wc->session = null;
+ $this->wc->cart = null;
+ $this->wc->customer = null;
+ $this->wc->session = null;
$this->assertNull( $this->wc->cart );
$this->assertNull( $this->wc->customer );
$this->assertNull( $this->wc->session );
From a7c0dec33a0054f807819e3b3cc9ce914e6d3ff2 Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Sun, 5 Apr 2020 13:07:49 -0700
Subject: [PATCH 010/153] Added a function to fetch all of the path define
tokens that may be present in template paths
---
includes/wc-core-functions.php | 53 ++++++++++---------
.../util/class-wc-tests-core-functions.php | 9 ++++
2 files changed, 38 insertions(+), 24 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index ec2bc429d22..c972adc2aea 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -205,6 +205,31 @@ function wc_untokenize_path( $path, $path_tokens ) {
return $path;
}
+/**
+ * Fetches an array containing all of the configurable path constants to be used in tokenization.
+ *
+ * @return array The key is the define and the path is the constant.
+ */
+function wc_get_path_define_tokens() {
+ $defines = array(
+ 'ABSPATH',
+ 'WC_ABSPATH',
+ 'WP_CONTENT_DIR',
+ 'WP_PLUGIN_DIR',
+ 'PLUGINDIR',
+ 'WP_THEME_DIR',
+ );
+
+ $path_tokens = array();
+ foreach ( $defines as $define ) {
+ if ( defined( $define ) ) {
+ $path_tokens[ $define ] = constant( $define );
+ }
+ }
+
+ return apply_filters( 'wc_get_path_define_tokens', $path_tokens );
+}
+
/**
* Get template part (for templates like the shop-loop).
*
@@ -243,22 +268,12 @@ function wc_get_template_part( $slug, $name = '' ) {
}
// Don't cache the absolute path so that it can be shared between web servers with different paths.
- $cache_path = wc_tokenize_path(
- $template,
- array(
- 'ABSPATH' => ABSPATH,
- )
- );
+ $cache_path = wc_tokenize_path( $template, wc_get_path_define_tokens() );
wp_cache_set( $cache_key, $cache_path, 'woocommerce' );
} else {
// Make sure that the absolute path to the template is resolved.
- $template = wc_untokenize_path(
- $template,
- array(
- 'ABSPATH' => ABSPATH,
- )
- );
+ $template = wc_untokenize_path( $template, wc_get_path_define_tokens() );
}
// Allow 3rd party plugins to filter template file from their plugin.
@@ -285,22 +300,12 @@ function wc_get_template( $template_name, $args = array(), $template_path = '',
$template = wc_locate_template( $template_name, $template_path, $default_path );
// Don't cache the absolute path so that it can be shared between web servers with different paths.
- $cache_path = wc_tokenize_path(
- $template,
- array(
- 'ABSPATH' => ABSPATH,
- )
- );
+ $cache_path = wc_tokenize_path( $template, wc_get_path_define_tokens() );
wp_cache_set( $cache_key, $cache_path, 'woocommerce' );
} else {
// Make sure that the absolute path to the template is resolved.
- $template = wc_untokenize_path(
- $template,
- array(
- 'ABSPATH' => ABSPATH,
- )
- );
+ $template = wc_untokenize_path( $template, wc_get_path_define_tokens() );
}
// Allow 3rd party plugin filter template file from their plugin.
diff --git a/tests/unit-tests/util/class-wc-tests-core-functions.php b/tests/unit-tests/util/class-wc-tests-core-functions.php
index 28508b6abbb..5db7b9dfec4 100644
--- a/tests/unit-tests/util/class-wc-tests-core-functions.php
+++ b/tests/unit-tests/util/class-wc-tests-core-functions.php
@@ -635,6 +635,15 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertEquals( WP_CONTENT_DIR . 'test', $path );
}
+ /**
+ * Tests the wc_get_path_define_tokens function.
+ */
+ public function test_wc_get_path_define_tokens() {
+ $defines = wc_get_path_define_tokens();
+ $this->assertArrayHasKey( 'ABSPATH', $defines );
+ $this->assertEquals( ABSPATH, $defines['ABSPATH'] );
+ }
+
/**
* Test wc_get_template.
*
From b43ad106efae5dedecb2eb226d98ef31ec7abc34 Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Mon, 6 Apr 2020 07:17:01 -0700
Subject: [PATCH 011/153] Added WPMU_PLUGIN_DIR and removed the path define
token for ABSPATH
There's no need for the ABSPATH define since the WC plugin is always a child of a plugin directory anyway.
---
includes/wc-core-functions.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index c972adc2aea..24b2273a154 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -213,9 +213,9 @@ function wc_untokenize_path( $path, $path_tokens ) {
function wc_get_path_define_tokens() {
$defines = array(
'ABSPATH',
- 'WC_ABSPATH',
'WP_CONTENT_DIR',
'WP_PLUGIN_DIR',
+ 'WPMU_PLUGIN_DIR',
'PLUGINDIR',
'WP_THEME_DIR',
);
From 2fedf9306e6642dee414caf95d00b6c19a713aaa Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Tue, 7 Apr 2020 10:25:57 -0700
Subject: [PATCH 012/153] Corrected the filter prefix for
get_path_define_tokens
---
includes/wc-core-functions.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 24b2273a154..b9fe3a41651 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -227,7 +227,7 @@ function wc_get_path_define_tokens() {
}
}
- return apply_filters( 'wc_get_path_define_tokens', $path_tokens );
+ return apply_filters( 'woocommerce_get_path_define_tokens', $path_tokens );
}
/**
From fbdbecbcf6453de94cde552494637999b08467c2 Mon Sep 17 00:00:00 2001
From: Sagar Tamang
Date: Thu, 23 Apr 2020 01:20:21 +0545
Subject: [PATCH 013/153] Hide only the tab content under the tab wrapper
---
assets/js/frontend/single-product.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/frontend/single-product.js b/assets/js/frontend/single-product.js
index 30b3d6fd991..618791c6a47 100644
--- a/assets/js/frontend/single-product.js
+++ b/assets/js/frontend/single-product.js
@@ -9,7 +9,7 @@ jQuery( function( $ ) {
$( 'body' )
// Tabs
.on( 'init', '.wc-tabs-wrapper, .woocommerce-tabs', function() {
- $( '.wc-tab, .woocommerce-tabs .panel:not(.panel .panel)' ).hide();
+ $( this ).find( '.wc-tab, .woocommerce-tabs .panel:not(.panel .panel)' ).hide();
var hash = window.location.hash;
var url = window.location.href;
From ab8ee194c8aff1710774ecd236fe2ea223d06e53 Mon Sep 17 00:00:00 2001
From: Sagar Tamang
Date: Sat, 25 Apr 2020 21:10:11 +0545
Subject: [PATCH 014/153] Fix: Show password toggle not working in checkout
page
---
assets/js/frontend/woocommerce.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/assets/js/frontend/woocommerce.js b/assets/js/frontend/woocommerce.js
index 6c6047558cd..8c0414fff1b 100644
--- a/assets/js/frontend/woocommerce.js
+++ b/assets/js/frontend/woocommerce.js
@@ -81,15 +81,17 @@ jQuery( function( $ ) {
// Show password visiblity hover icon on woocommerce forms
$( '.woocommerce form .woocommerce-Input[type="password"]' ).wrap( ' ' );
+ // Add 'password-input' class to the password wrapper in checkout page.
+ $( '.woocommerce form input' ).filter(':password').parent('span').addClass('password-input');
$( '.password-input' ).append( ' ' );
$( '.show-password-input' ).click(
function() {
$( this ).toggleClass( 'display-password' );
if ( $( this ).hasClass( 'display-password' ) ) {
- $( this ).siblings( ['input[name^="password"]', 'input[type="password"]'] ).prop( 'type', 'text' );
+ $( this ).siblings( ['input[type="password"]'] ).prop( 'type', 'text' );
} else {
- $( this ).siblings( 'input[name^="password"]' ).prop( 'type', 'password' );
+ $( this ).siblings( 'input[type="text"]' ).prop( 'type', 'password' );
}
}
);
From fc683bdb80f6f6d822f253a785b6f68ef2becac0 Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Tue, 21 Apr 2020 17:43:15 -0500
Subject: [PATCH 015/153] Update unit tests to account for issue 24000
---
tests/legacy/unit-tests/cart/cart.php | 43 +++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index 7c0fe0cb972..1ba5290e64b 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2051,6 +2051,49 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$this->assertEquals( 70.86, WC()->cart->get_total( 'edit' ) );
}
+ /**
+ * Test that adding a variation with URL parameter increases the quantity appropriately
+ * as described in issue 24000.
+ */
+ public function test_add_variation_with_url() {
+ add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+ update_option( 'woocommerce_cart_redirect_after_add', 'no' );
+ WC()->cart->empty_cart();
+
+ $product = WC_Helper_Product::create_variation_product();
+ $variations = $product->get_available_variations();
+ $variation = array_pop( $variations );
+
+ // Add variation with add_to_cart_action.
+ $_REQUEST['add-to-cart'] = $variation['variation_id'];
+ WC_Form_Handler::add_to_cart_action( false );
+ $notices = WC()->session->get( 'wc_notices', array() );
+
+ // Reset filter / REQUEST variables.
+ unset( $_REQUEST['add-to-cart'] );
+ remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+
+ // Check if the item is in the cart.
+ $this->assertEquals( 1, count( WC()->cart->get_cart_contents() ) );
+ $this->assertEquals( 1, WC()->cart->get_cart_contents_count() );
+
+ // Add variation using parent id.
+ WC()->cart->add_to_cart(
+ $product->get_id(),
+ 1,
+ $variation['variation_id'],
+ array(
+ 'attribute_pa_size' => 'huge',
+ 'attribute_pa_color' => 'red',
+ 'attribute_pa_number' => '2',
+ )
+ );
+
+ // Check that the second add to cart call increases the quantity of the existing cart-item.
+ $this->assertEquals( 1, count( WC()->cart->get_cart_contents() ) );
+ $this->assertEquals( 2, WC()->cart->get_cart_contents_count() );
+ }
+
/**
* Helper function. Adds 1.5 taxable fees to cart.
*/
From 31bdce3725334c4677e1ecc88dc57eefa2d4f3f7 Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Tue, 21 Apr 2020 18:03:21 -0500
Subject: [PATCH 016/153] Fix typo in test_add_variation_with_url test (use
British spelling of colour)
---
tests/legacy/unit-tests/cart/cart.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index 1ba5290e64b..fa02219875b 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2084,7 +2084,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$variation['variation_id'],
array(
'attribute_pa_size' => 'huge',
- 'attribute_pa_color' => 'red',
+ 'attribute_pa_colour' => 'red',
'attribute_pa_number' => '2',
)
);
From 0c2b97ead29668e15c3645b6361d83e9d0e9124f Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Tue, 21 Apr 2020 18:11:45 -0500
Subject: [PATCH 017/153] Load variation attributes when adding to cart by
variation id
---
includes/class-wc-form-handler.php | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php
index 3565b642a6b..43544f64c85 100644
--- a/includes/class-wc-form-handler.php
+++ b/includes/class-wc-form-handler.php
@@ -862,11 +862,12 @@ class WC_Form_Handler {
*/
private static function add_to_cart_handler_variable( $product_id ) {
try {
- $variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $missing_attributes = array();
- $variations = array();
- $adding_to_cart = wc_get_product( $product_id );
+ $variation_id = empty( $_REQUEST['variation_id'] ) ? '' : absint( wp_unslash( $_REQUEST['variation_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( wp_unslash( $_REQUEST['quantity'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $missing_attributes = array();
+ $variations = array();
+ $variation_attributes = array();
+ $adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
return false;
@@ -874,6 +875,7 @@ class WC_Form_Handler {
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
+ $variation_attributes = $adding_to_cart->get_variation_attributes();
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
@@ -904,6 +906,9 @@ class WC_Form_Handler {
}
}
+ // Merge variation attributes and posted attributes.
+ $posted_and_variation_attributes = array_merge( $posted_attributes, $variation_attributes );
+
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
$data_store = WC_Data_Store::load( 'product' );
@@ -932,8 +937,8 @@ class WC_Form_Handler {
*
* If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
*/
- if ( isset( $posted_attributes[ $attribute_key ] ) ) {
- $value = $posted_attributes[ $attribute_key ];
+ if ( isset( $posted_and_variation_attributes[ $attribute_key ] ) ) {
+ $value = $posted_and_variation_attributes[ $attribute_key ];
// Allow if valid or show error.
if ( $valid_value === $value ) {
From f8066a81419ffbae24a27f98f9352b5e6b9b7d8e Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Fri, 24 Apr 2020 13:48:00 -0500
Subject: [PATCH 018/153] Swap order of array_merge so that posted attributes
do not get clobbered
---
includes/class-wc-form-handler.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php
index 43544f64c85..25e0e97cf62 100644
--- a/includes/class-wc-form-handler.php
+++ b/includes/class-wc-form-handler.php
@@ -907,7 +907,7 @@ class WC_Form_Handler {
}
// Merge variation attributes and posted attributes.
- $posted_and_variation_attributes = array_merge( $posted_attributes, $variation_attributes );
+ $posted_and_variation_attributes = array_merge( $variation_attributes, $posted_attributes );
// If no variation ID is set, attempt to get a variation ID from posted attributes.
if ( empty( $variation_id ) ) {
From 3d0bfd8ee248369988298123c509979a8535850c Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Fri, 24 Apr 2020 13:52:16 -0500
Subject: [PATCH 019/153] Add test to check for notice when invalid attribute
is provided for a variant
---
tests/legacy/unit-tests/cart/cart.php | 30 +++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index fa02219875b..d374430c6c4 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2094,6 +2094,36 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$this->assertEquals( 2, WC()->cart->get_cart_contents_count() );
}
+ /**
+ * Test that adding a variation via URL parameter fails when specifying a value for the attribute
+ * that differs from a value belonging to that variant.
+ */
+ public function test_add_variation_with_invalid_attribute() {
+ add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+ update_option( 'woocommerce_cart_redirect_after_add', 'no' );
+ WC()->cart->empty_cart();
+
+ $product = WC_Helper_Product::create_variation_product();
+ $variations = $product->get_available_variations();
+ $variation = array_pop( $variations );
+
+ // Attempt adding variation with add_to_cart_action, specifying a different colour.
+ $_REQUEST['add-to-cart'] = $variation['variation_id'];
+ $_REQUEST['attribute_pa_colour'] = 'green';
+ WC_Form_Handler::add_to_cart_action( false );
+ $notices = WC()->session->get( 'wc_notices', array() );
+
+ // Reset filter / REQUEST variables.
+ unset( $_REQUEST['add-to-cart'] );
+ unset( $_REQUEST['attribute_pa_colour'] );
+ remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+
+ // Check that the notices contain an error message about an invalid colour.
+ $this->assertArrayHasKey( 'error', $notices );
+ $this->assertCount( 1, $notices['error'] );
+ $this->assertEquals( 'Invalid value posted for colour', $notices['error'][0]['notice'] );
+ }
+
/**
* Helper function. Adds 1.5 taxable fees to cart.
*/
From 3f70f70f3a2df1d3417c3653464e2da8d234c7e3 Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Fri, 24 Apr 2020 13:54:12 -0500
Subject: [PATCH 020/153] Switch to assertCount for count assertions in test
---
tests/legacy/unit-tests/cart/cart.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index d374430c6c4..5e0fa7d414a 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2074,7 +2074,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
// Check if the item is in the cart.
- $this->assertEquals( 1, count( WC()->cart->get_cart_contents() ) );
+ $this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 1, WC()->cart->get_cart_contents_count() );
// Add variation using parent id.
@@ -2090,7 +2090,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
);
// Check that the second add to cart call increases the quantity of the existing cart-item.
- $this->assertEquals( 1, count( WC()->cart->get_cart_contents() ) );
+ $this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 2, WC()->cart->get_cart_contents_count() );
}
From 790c8ae8ae5378652071ff61f49dc5cd5d7c021a Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Fri, 24 Apr 2020 14:02:14 -0500
Subject: [PATCH 021/153] Update the test to make sure there are no error
notices
---
tests/legacy/unit-tests/cart/cart.php | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index 5e0fa7d414a..8768395d352 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2077,6 +2077,9 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 1, WC()->cart->get_cart_contents_count() );
+ // Check that there are no error notices.
+ $this->assertArrayNotHasKey( 'error', $notices );
+
// Add variation using parent id.
WC()->cart->add_to_cart(
$product->get_id(),
@@ -2088,10 +2091,14 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
'attribute_pa_number' => '2',
)
);
+ $notices = WC()->session->get( 'wc_notices', array() );
// Check that the second add to cart call increases the quantity of the existing cart-item.
$this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 2, WC()->cart->get_cart_contents_count() );
+
+ // Check that there are no error notices.
+ $this->assertArrayNotHasKey( 'error', $notices );
}
/**
From 3f47608228ad7b4ecac6c44d7c3902464d2af869 Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Fri, 24 Apr 2020 14:20:21 -0500
Subject: [PATCH 022/153] Test adding a variation with 'any' attributes
---
tests/legacy/unit-tests/cart/cart.php | 32 +++++++++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index 8768395d352..4a04dabfd11 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2055,7 +2055,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
* Test that adding a variation with URL parameter increases the quantity appropriately
* as described in issue 24000.
*/
- public function test_add_variation_with_url() {
+ public function test_add_variation_by_url() {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
@@ -2105,7 +2105,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
* Test that adding a variation via URL parameter fails when specifying a value for the attribute
* that differs from a value belonging to that variant.
*/
- public function test_add_variation_with_invalid_attribute() {
+ public function test_add_variation_by_url_with_invalid_attribute() {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
@@ -2131,6 +2131,34 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$this->assertEquals( 'Invalid value posted for colour', $notices['error'][0]['notice'] );
}
+ /**
+ * Test that adding a variation via URL parameter succeeds when some attributes belong to the
+ * variation and others are specificed via URL parameter.
+ */
+ public function test_add_variation_by_url_with_valid_attribute() {
+ add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+ update_option( 'woocommerce_cart_redirect_after_add', 'no' );
+ WC()->cart->empty_cart();
+
+ $product = WC_Helper_Product::create_variation_product();
+ $variations = $product->get_available_variations();
+ $variation = array_shift( $variations );
+
+ // Attempt adding variation with add_to_cart_action, specifying attributes not defined in the variation.
+ $_REQUEST['add-to-cart'] = $variation['variation_id'];
+ $_REQUEST['attribute_pa_colour'] = 'red';
+ $_REQUEST['attribute_pa_number'] = '1';
+ WC_Form_Handler::add_to_cart_action( false );
+ $notices = WC()->session->get( 'wc_notices', array() );
+
+ // Check if the item is in the cart.
+ $this->assertCount( 1, WC()->cart->get_cart_contents() );
+ $this->assertEquals( 1, WC()->cart->get_cart_contents_count() );
+
+ // Check that there are no error notices.
+ $this->assertArrayNotHasKey( 'error', $notices );
+ }
+
/**
* Helper function. Adds 1.5 taxable fees to cart.
*/
From c0a72c9185019d85bc617161423cd79daf5abf30 Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Fri, 24 Apr 2020 14:21:03 -0500
Subject: [PATCH 023/153] Reset notices at the start of each test
---
tests/legacy/unit-tests/cart/cart.php | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index 4a04dabfd11..51ab38ec256 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2059,6 +2059,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
$product = WC_Helper_Product::create_variation_product();
$variations = $product->get_available_variations();
@@ -2109,6 +2110,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
$product = WC_Helper_Product::create_variation_product();
$variations = $product->get_available_variations();
@@ -2139,6 +2141,7 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
update_option( 'woocommerce_cart_redirect_after_add', 'no' );
WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
$product = WC_Helper_Product::create_variation_product();
$variations = $product->get_available_variations();
From 53c905f49388a65db882ff7caf72694b2f53c707 Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Fri, 24 Apr 2020 14:22:48 -0500
Subject: [PATCH 024/153] Reset request vars in any attribute test
---
tests/legacy/unit-tests/cart/cart.php | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index 51ab38ec256..f28e36ac4af 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2154,6 +2154,12 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
WC_Form_Handler::add_to_cart_action( false );
$notices = WC()->session->get( 'wc_notices', array() );
+ // Reset filter / REQUEST variables.
+ unset( $_REQUEST['add-to-cart'] );
+ unset( $_REQUEST['attribute_pa_colour'] );
+ unset( $_REQUEST['attribute_pa_number'] );
+ remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+
// Check if the item is in the cart.
$this->assertCount( 1, WC()->cart->get_cart_contents() );
$this->assertEquals( 1, WC()->cart->get_cart_contents_count() );
From a0a8cf7ab38b4ebffc5ca72f36dd9488addaa57a Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Tue, 5 May 2020 14:16:01 -0500
Subject: [PATCH 025/153] Add test to ensure that a notice is displayed when an
any attribute is omitted
---
tests/legacy/unit-tests/cart/cart.php | 34 +++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/tests/legacy/unit-tests/cart/cart.php b/tests/legacy/unit-tests/cart/cart.php
index f28e36ac4af..67759435034 100644
--- a/tests/legacy/unit-tests/cart/cart.php
+++ b/tests/legacy/unit-tests/cart/cart.php
@@ -2168,6 +2168,40 @@ class WC_Tests_Cart extends WC_Unit_Test_Case {
$this->assertArrayNotHasKey( 'error', $notices );
}
+ /**
+ * Test that adding a varition via URL parameter fails when an 'any' attribute is missing.
+ */
+ public function test_add_variation_by_url_fails_with_missing_any_attribute() {
+ add_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+ update_option( 'woocommerce_cart_redirect_after_add', 'no' );
+ WC()->cart->empty_cart();
+ WC()->session->set( 'wc_notices', null );
+
+ $product = WC_Helper_Product::create_variation_product();
+ $variations = $product->get_available_variations();
+ $variation = array_shift( $variations );
+
+ // Attempt adding variation with add_to_cart_action, without specifying attribute_pa_colour.
+ $_REQUEST['add-to-cart'] = $variation['variation_id'];
+ $_REQUEST['attribute_pa_number'] = '0';
+ WC_Form_Handler::add_to_cart_action( false );
+ $notices = WC()->session->get( 'wc_notices', array() );
+
+ // Reset filter / REQUEST variables.
+ unset( $_REQUEST['add-to-cart'] );
+ unset( $_REQUEST['attribute_pa_number'] );
+ remove_filter( 'woocommerce_add_to_cart_redirect', '__return_false' );
+
+ // Verify that there is nothing in the cart.
+ $this->assertCount( 0, WC()->cart->get_cart_contents() );
+ $this->assertEquals( 0, WC()->cart->get_cart_contents_count() );
+
+ // Check that the notices contain an error message about an invalid colour.
+ $this->assertArrayHasKey( 'error', $notices );
+ $this->assertCount( 1, $notices['error'] );
+ $this->assertEquals( 'colour is a required field', $notices['error'][0]['notice'] );
+ }
+
/**
* Helper function. Adds 1.5 taxable fees to cart.
*/
From 2db4da7055ab81a72df179dd6799c4cb77dca67b Mon Sep 17 00:00:00 2001
From: Jonathan Sadowski
Date: Tue, 5 May 2020 14:16:37 -0500
Subject: [PATCH 026/153] Update variation_attributes to filter out any
attributes, as they must be specified
---
includes/class-wc-form-handler.php | 2 ++
1 file changed, 2 insertions(+)
diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php
index 25e0e97cf62..28a78df71de 100644
--- a/includes/class-wc-form-handler.php
+++ b/includes/class-wc-form-handler.php
@@ -876,6 +876,8 @@ class WC_Form_Handler {
// If the $product_id was in fact a variation ID, update the variables.
if ( $adding_to_cart->is_type( 'variation' ) ) {
$variation_attributes = $adding_to_cart->get_variation_attributes();
+ // Filter out 'any' variations, which are empty, as they need to be explicitly specified while adding to cart.
+ $variation_attributes = array_filter( $variation_attributes );
$variation_id = $product_id;
$product_id = $adding_to_cart->get_parent_id();
$adding_to_cart = wc_get_product( $product_id );
From 8e21f10b3d09ad14de3d45a3ba710a89b6974aa0 Mon Sep 17 00:00:00 2001
From: Claudio Sanches
Date: Wed, 6 May 2020 14:07:46 -0300
Subject: [PATCH 027/153] Update since tags
---
includes/wc-core-functions.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index b9fe3a41651..30e5de4f4f8 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -153,7 +153,7 @@ function wc_update_order( $args ) {
/**
* Given a path, this will convert any of the subpaths into their corresponding tokens.
*
- * @since 4.1.0
+ * @since 4.2.0
* @param string $path The absolute path to tokenize.
* @param array $path_tokens An array keyed with the token, containing paths that should be replaced.
* @return string The tokenized path.
@@ -192,7 +192,7 @@ function wc_tokenize_path( $path, $path_tokens ) {
/**
* Given a tokenized path, this will expand the tokens to their full path.
*
- * @since 4.1.0
+ * @since 4.2.0
* @param string $path The absolute path to expand.
* @param array $path_tokens An array keyed with the token, containing paths that should be expanded.
* @return string The absolute path.
From 7634f54235b900e0581ae3fd9b19fa481ec1e1cd Mon Sep 17 00:00:00 2001
From: Claudio Sanches
Date: Wed, 6 May 2020 23:20:56 -0300
Subject: [PATCH 028/153] Use "Options -Indexes" for redirect download method
---
includes/class-wc-install.php | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index a219d7d8323..6debbd914fd 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -1206,15 +1206,12 @@ CREATE TABLE {$wpdb->prefix}wc_tax_rate_classes (
'file' => 'index.html',
'content' => '',
),
- );
-
- if ( 'redirect' !== $download_method ) {
- $files[] = array(
+ array(
'base' => $upload_dir['basedir'] . '/woocommerce_uploads',
'file' => '.htaccess',
- 'content' => 'deny from all',
- );
- }
+ 'content' => 'redirect' === $download_method ? 'Options -Indexes' : 'deny from all',
+ ),
+ );
foreach ( $files as $file ) {
if ( wp_mkdir_p( $file['base'] ) && ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) ) {
From 39633855f3e4b438cfffd7f8a5b7d7edf87638bc Mon Sep 17 00:00:00 2001
From: Claudio Sanches
Date: Wed, 6 May 2020 23:21:31 -0300
Subject: [PATCH 029/153] Only fetch the uploads info and do not attempt to
create the uploads directory
---
includes/class-wc-install.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index 6debbd914fd..0fa82cf35fd 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -1187,7 +1187,7 @@ CREATE TABLE {$wpdb->prefix}wc_tax_rate_classes (
}
// Install files and folders for uploading files and prevent hotlinking.
- $upload_dir = wp_upload_dir();
+ $upload_dir = wp_get_upload_dir();
$download_method = get_option( 'woocommerce_file_download_method', 'force' );
$files = array(
From dcabbcb964946a3d0367f4cc8f4fc92e81e60b15 Mon Sep 17 00:00:00 2001
From: Claudio Sanches
Date: Wed, 6 May 2020 23:25:33 -0300
Subject: [PATCH 030/153] Open file in binary mode
---
includes/class-wc-install.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index 0fa82cf35fd..4061f1a7c9d 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -1215,7 +1215,7 @@ CREATE TABLE {$wpdb->prefix}wc_tax_rate_classes (
foreach ( $files as $file ) {
if ( wp_mkdir_p( $file['base'] ) && ! file_exists( trailingslashit( $file['base'] ) . $file['file'] ) ) {
- $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'w' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
+ $file_handle = @fopen( trailingslashit( $file['base'] ) . $file['file'], 'wb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
if ( $file_handle ) {
fwrite( $file_handle, $file['content'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
fclose( $file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
From 16ec0007cfa3c839b9ad973d485987063a5a1629 Mon Sep 17 00:00:00 2001
From: Claudio Sanches
Date: Wed, 6 May 2020 23:42:33 -0300
Subject: [PATCH 031/153] Set "Options -Indexes" for redirect download method
Stop the .htaccess to get removed and stop directory listing
---
includes/admin/class-wc-admin-settings.php | 34 ++++++++++++----------
1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/includes/admin/class-wc-admin-settings.php b/includes/admin/class-wc-admin-settings.php
index 837ffbc8230..3a68ae50739 100644
--- a/includes/admin/class-wc-admin-settings.php
+++ b/includes/admin/class-wc-admin-settings.php
@@ -869,25 +869,29 @@ if ( ! class_exists( 'WC_Admin_Settings', false ) ) :
* If using force or x-sendfile, this ensures the .htaccess is in place.
*/
public static function check_download_folder_protection() {
- $upload_dir = wp_upload_dir();
- $downloads_url = $upload_dir['basedir'] . '/woocommerce_uploads';
+ $upload_dir = wp_get_upload_dir();
+ $downloads_path = $upload_dir['basedir'] . '/woocommerce_uploads';
$download_method = get_option( 'woocommerce_file_download_method' );
+ $file_path = $downloads_path . '/.htaccess';
+ $file_content = 'redirect' === $download_method ? 'Options -Indexes' : 'deny from all';
+ $create = false;
- if ( 'redirect' === $download_method ) {
-
- // Redirect method - don't protect.
- if ( file_exists( $downloads_url . '/.htaccess' ) ) {
- unlink( $downloads_url . '/.htaccess' ); // @codingStandardsIgnoreLine
- }
+ if ( wp_mkdir_p( $downloads_path ) && ! file_exists( $file_path ) ) {
+ $create = true;
} else {
+ $current_content = @file_get_contents( $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
- // Force method - protect, add rules to the htaccess file.
- if ( ! file_exists( $downloads_url . '/.htaccess' ) ) {
- $file_handle = @fopen( $downloads_url . '/.htaccess', 'w' ); // @codingStandardsIgnoreLine
- if ( $file_handle ) {
- fwrite( $file_handle, 'deny from all' ); // @codingStandardsIgnoreLine
- fclose( $file_handle ); // @codingStandardsIgnoreLine
- }
+ if ( $current_content !== $file_content ) {
+ unlink( $file_path );
+ $create = true;
+ }
+ }
+
+ if ( $create ) {
+ $file_handle = @fopen( $file_path, 'wb' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
+ if ( $file_handle ) {
+ fwrite( $file_handle, $file_content ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
+ fclose( $file_handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
}
}
}
From 611d5aac828d364018b2fdce13d02ae721c61f68 Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Thu, 7 May 2020 11:43:18 +0200
Subject: [PATCH 032/153] Added packages to list of contributors.
---
Gruntfile.js | 38 +++++++++++++++++++++++++++-----------
1 file changed, 27 insertions(+), 11 deletions(-)
diff --git a/Gruntfile.js b/Gruntfile.js
index 0a8316eb8c9..f64f9a05845 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -215,11 +215,32 @@ module.exports = function( grunt ) {
},
contributors: {
command: [
- 'echo "Generating contributor list since <%= fromDate %>"',
+ 'echo "WooCommerce Admin " > contributors.md',
+ 'echo "Generating contributor list for WC Admin since <%= fromDate %>"',
+ './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-admin --fromDate <%= fromDate %>' +
+ ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
+ 'echo "WooCommerce Blocks " >> contributors.md',
+ 'echo "Generating contributor list for WC Blocks since <%= fromDate %>"',
+ './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-gutenberg-products-block' +
+ ' --fromDate <%= fromDate %> --authToken <%= authToken %> --cols 6 --sortBy contributions --format html' +
+ ' --sortOrder desc --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
+ 'echo "Action Scheduler " >> contributors.md',
+ 'echo "Generating contributor list for Action Scheduler since <%= fromDate %>"',
+ './node_modules/.bin/githubcontrib --owner woocommerce --repo action-scheduler --fromDate <%= fromDate %>' +
+ ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
+ 'echo "REST API " >> contributors.md',
+ 'echo "Generating contributor list for REST API since <%= fromDate %>"',
+ './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-rest-api --fromDate <%= fromDate %>' +
+ ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
+ 'echo "WooCommerce core " >> contributors.md',
+ 'echo "Generating contributor list for WC core since <%= fromDate %>"',
'./node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce --fromDate <%= fromDate %>' +
- ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format md --sortOrder desc' +
- ' --showlogin true --sha <%= sha %> --filter renovate-bot > contributors.md'
- ].join( '&&' )
+ ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md'
+ ].join('&&')
}
},
@@ -230,17 +251,12 @@ module.exports = function( grunt ) {
{
config: 'fromDate',
type: 'input',
- message: 'What date (YYYY-MM-DD) should we get contributions since?'
- },
- {
- config: 'sha',
- type: 'input',
- message: 'What branch should we get contributors from?'
+ message: 'What date (YYYY-MM-DD) should we get contributions since? (i.e. date of previous release)'
},
{
config: 'authToken',
type: 'input',
- message: '(optional) Provide a personal access token.' +
+ message: 'Provide a personal access token (you must).' +
' This will allow 5000 requests per hour rather than 60 - use if nothing is generated.'
}
]
From 626bf9a3b3533c878b64f592b8f94c94a7c4683b Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Thu, 7 May 2020 14:16:07 +0200
Subject: [PATCH 033/153] Renamed the file to produce correct filename.
---
Gruntfile.js | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/Gruntfile.js b/Gruntfile.js
index f64f9a05845..dc30ea6fd2c 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -215,31 +215,32 @@ module.exports = function( grunt ) {
},
contributors: {
command: [
- 'echo "WooCommerce Admin " > contributors.md',
+ 'echo "WooCommerce Admin " > contributors.html',
'echo "Generating contributor list for WC Admin since <%= fromDate %>"',
'./node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-admin --fromDate <%= fromDate %>' +
' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
- 'echo "WooCommerce Blocks " >> contributors.md',
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
+ 'echo "WooCommerce Blocks " >> contributors.html',
'echo "Generating contributor list for WC Blocks since <%= fromDate %>"',
'./node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-gutenberg-products-block' +
' --fromDate <%= fromDate %> --authToken <%= authToken %> --cols 6 --sortBy contributions --format html' +
- ' --sortOrder desc --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
- 'echo "Action Scheduler " >> contributors.md',
+ ' --sortOrder desc --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
+ 'echo "Action Scheduler " >> contributors.html',
'echo "Generating contributor list for Action Scheduler since <%= fromDate %>"',
'./node_modules/.bin/githubcontrib --owner woocommerce --repo action-scheduler --fromDate <%= fromDate %>' +
' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
- 'echo "REST API " >> contributors.md',
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
+ 'echo "REST API " >> contributors.html',
'echo "Generating contributor list for REST API since <%= fromDate %>"',
'./node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-rest-api --fromDate <%= fromDate %>' +
' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md',
- 'echo "WooCommerce core " >> contributors.md',
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
+ 'echo "WooCommerce core " >> contributors.html',
'echo "Generating contributor list for WC core since <%= fromDate %>"',
'./node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce --fromDate <%= fromDate %>' +
' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.md'
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
+ 'echo "Output generated to contributors.html."',
].join('&&')
}
},
From e64ab46a5261bedead671de4de526423b00a3044 Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Mon, 11 May 2020 10:12:31 +0200
Subject: [PATCH 034/153] coding standards fix
---
Gruntfile.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Gruntfile.js b/Gruntfile.js
index dc30ea6fd2c..19541404aa1 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -241,7 +241,7 @@ module.exports = function( grunt ) {
' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
'echo "Output generated to contributors.html."',
- ].join('&&')
+ ].join( '&&' )
}
},
From c7a5cb200f06fea7711ef762e0b7edc16f894a23 Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Mon, 11 May 2020 10:16:30 +0200
Subject: [PATCH 035/153] WC core first
---
Gruntfile.js | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/Gruntfile.js b/Gruntfile.js
index 19541404aa1..a812e5db4d9 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -215,7 +215,13 @@ module.exports = function( grunt ) {
},
contributors: {
command: [
- 'echo "WooCommerce Admin " > contributors.html',
+ 'echo "WooCommerce core " > contributors.html',
+ 'echo "Generating contributor list for WC core since <%= fromDate %>"',
+ './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce --fromDate <%= fromDate %>' +
+ ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
+ ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
+ 'echo "Output generated to contributors.html."',
+ 'echo "WooCommerce Admin " >> contributors.html',
'echo "Generating contributor list for WC Admin since <%= fromDate %>"',
'./node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-admin --fromDate <%= fromDate %>' +
' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
@@ -235,12 +241,6 @@ module.exports = function( grunt ) {
'./node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-rest-api --fromDate <%= fromDate %>' +
' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
- 'echo "WooCommerce core " >> contributors.html',
- 'echo "Generating contributor list for WC core since <%= fromDate %>"',
- './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce --fromDate <%= fromDate %>' +
- ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
- 'echo "Output generated to contributors.html."',
].join( '&&' )
}
},
From 4480637015c573eda393fbc9fcd7ea0c51fade14 Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Mon, 11 May 2020 10:20:14 +0200
Subject: [PATCH 036/153] Add the contributors.html to gitignore.
Leaving the contributors.md there, as the contributors file was previously generated as .md and might still be around in some directories, etc.
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 9b99f73835d..8c055716d85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@ tests/cli/vendor
# Composer
/vendor/
contributors.md
+contributors.html
# Packages
/packages/*
From 80eb5ae931e9a04cc43bc91bd8066f875d7b4c03 Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Mon, 11 May 2020 11:00:42 +0200
Subject: [PATCH 037/153] Moved the contributors script from Grunt to bash.
---
Gruntfile.js | 56 ---------------------------------------------
bin/contributors.sh | 32 ++++++++++++++++++++++++++
2 files changed, 32 insertions(+), 56 deletions(-)
create mode 100755 bin/contributors.sh
diff --git a/Gruntfile.js b/Gruntfile.js
index a812e5db4d9..141134fccc1 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -213,56 +213,6 @@ module.exports = function( grunt ) {
e2e_tests_grep: {
command: 'npm run --silent test:grep "' + grunt.option( 'grep' ) + '"'
},
- contributors: {
- command: [
- 'echo "WooCommerce core " > contributors.html',
- 'echo "Generating contributor list for WC core since <%= fromDate %>"',
- './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce --fromDate <%= fromDate %>' +
- ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
- 'echo "Output generated to contributors.html."',
- 'echo "WooCommerce Admin " >> contributors.html',
- 'echo "Generating contributor list for WC Admin since <%= fromDate %>"',
- './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-admin --fromDate <%= fromDate %>' +
- ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
- 'echo "WooCommerce Blocks " >> contributors.html',
- 'echo "Generating contributor list for WC Blocks since <%= fromDate %>"',
- './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-gutenberg-products-block' +
- ' --fromDate <%= fromDate %> --authToken <%= authToken %> --cols 6 --sortBy contributions --format html' +
- ' --sortOrder desc --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
- 'echo "Action Scheduler " >> contributors.html',
- 'echo "Generating contributor list for Action Scheduler since <%= fromDate %>"',
- './node_modules/.bin/githubcontrib --owner woocommerce --repo action-scheduler --fromDate <%= fromDate %>' +
- ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
- 'echo "REST API " >> contributors.html',
- 'echo "Generating contributor list for REST API since <%= fromDate %>"',
- './node_modules/.bin/githubcontrib --owner woocommerce --repo woocommerce-rest-api --fromDate <%= fromDate %>' +
- ' --authToken <%= authToken %> --cols 6 --sortBy contributions --format html --sortOrder desc' +
- ' --showlogin true --filter "renovate-bot,apps/renovate,renovate,renovate[bot]" >> contributors.html',
- ].join( '&&' )
- }
- },
-
- prompt: {
- contributors: {
- options: {
- questions: [
- {
- config: 'fromDate',
- type: 'input',
- message: 'What date (YYYY-MM-DD) should we get contributions since? (i.e. date of previous release)'
- },
- {
- config: 'authToken',
- type: 'input',
- message: 'Provide a personal access token (you must).' +
- ' This will allow 5000 requests per hour rather than 60 - use if nothing is generated.'
- }
- ]
- }
- }
},
// PHP Code Sniffer.
@@ -312,7 +262,6 @@ module.exports = function( grunt ) {
grunt.loadNpmTasks( 'grunt-contrib-copy' );
grunt.loadNpmTasks( 'grunt-contrib-watch' );
grunt.loadNpmTasks( 'grunt-contrib-clean' );
- grunt.loadNpmTasks( 'grunt-prompt' );
// Register tasks.
grunt.registerTask( 'default', [
@@ -346,11 +295,6 @@ module.exports = function( grunt ) {
'css'
]);
- grunt.registerTask( 'contributors', [
- 'prompt:contributors',
- 'shell:contributors'
- ]);
-
// Only an alias to 'default' task.
grunt.registerTask( 'dev', [
'default'
diff --git a/bin/contributors.sh b/bin/contributors.sh
new file mode 100755
index 00000000000..821fd834c90
--- /dev/null
+++ b/bin/contributors.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+read -p 'What date (YYYY-MM-DD) should we get contributions since? (i.e. date of previous release): ' from_date
+read -sp 'Provide a personal access token (you must): ' auth_token
+
+ignored_users="renovate-bot,apps/renovate,renovate,renovate[bot]"
+output_file="contributors.html"
+common_arguments="--owner woocommerce --fromDate $from_date --authToken $auth_token --cols 6 --sortBy contributions --format html --sortOrder desc --showlogin true --filter $ignored_users"
+
+echo ""
+
+echo "WooCommerce core " > $output_file
+echo "Generating contributor list for WC core since $from_date"
+./node_modules/.bin/githubcontrib --repo woocommerce $common_arguments >> $output_file
+
+echo "WooCommerce Admin " >> $output_file
+echo "Generating contributor list for WC Admin since $from_date"
+./node_modules/.bin/githubcontrib --repo woocommerce-admin $common_arguments >> $output_file
+
+echo "WooCommerce Blocks " >> $output_file
+echo "Generating contributor list for WC Blocks since $from_date"
+./node_modules/.bin/githubcontrib --repo woocommerce-gutenberg-products-block $common_arguments >> $output_file
+
+echo "Action Scheduler " >> $output_file
+echo "Generating contributor list for Action Scheduler since $from_date"
+./node_modules/.bin/githubcontrib --repo action-scheduler $common_arguments >> $output_file
+
+echo "REST API " >> $output_file
+echo "Generating contributor list for REST API since $from_date"
+./node_modules/.bin/githubcontrib --repo woocommerce-rest-api $common_arguments >> $output_file
+
+echo "Output generated to $output_file."
From b7294df2d587d96deb90b316f247e3345494527e Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Mon, 11 May 2020 11:02:08 +0200
Subject: [PATCH 038/153] Removed the old way to run e2e tests.
---
Gruntfile.js | 30 ------------------------------
1 file changed, 30 deletions(-)
diff --git a/Gruntfile.js b/Gruntfile.js
index 141134fccc1..d831b5da2b2 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -198,23 +198,6 @@ module.exports = function( grunt ) {
}
},
- // Exec shell commands.
- shell: {
- options: {
- stdout: true,
- stderr: true
- },
- e2e_test: {
- command: 'npm run --silent test:single tests/e2e-tests/' + grunt.option( 'file' )
- },
- e2e_tests: {
- command: 'npm run --silent test'
- },
- e2e_tests_grep: {
- command: 'npm run --silent test:grep "' + grunt.option( 'grep' ) + '"'
- },
- },
-
// PHP Code Sniffer.
phpcs: {
options: {
@@ -250,7 +233,6 @@ module.exports = function( grunt ) {
// Load NPM tasks to be used here.
grunt.loadNpmTasks( 'grunt-sass' );
- grunt.loadNpmTasks( 'grunt-shell' );
grunt.loadNpmTasks( 'grunt-phpcs' );
grunt.loadNpmTasks( 'grunt-rtlcss' );
grunt.loadNpmTasks( 'grunt-postcss' );
@@ -299,16 +281,4 @@ module.exports = function( grunt ) {
grunt.registerTask( 'dev', [
'default'
]);
-
- grunt.registerTask( 'e2e-tests', [
- 'shell:e2e_tests'
- ]);
-
- grunt.registerTask( 'e2e-tests-grep', [
- 'shell:e2e_tests_grep'
- ]);
-
- grunt.registerTask( 'e2e-test', [
- 'shell:e2e_test'
- ]);
};
From 7ddd2aa3873a0d17b73baf66a1fe58f1959c6f83 Mon Sep 17 00:00:00 2001
From: Peter Fabian
Date: Mon, 11 May 2020 13:52:57 +0200
Subject: [PATCH 039/153] Removed grunt-prompt and grunt-shell packages.
---
package-lock.json | 223 ----------------------------------------------
package.json | 2 -
2 files changed, 225 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 382e2bf58d0..71dc34b7ba4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8092,12 +8092,6 @@
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
"dev": true
},
- "exit-hook": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
- "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
- "dev": true
- },
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
@@ -9996,161 +9990,6 @@
}
}
},
- "grunt-prompt": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/grunt-prompt/-/grunt-prompt-1.3.3.tgz",
- "integrity": "sha1-xbQ77DqimqaWKsZhGolnEvy6Z5E=",
- "dev": true,
- "requires": {
- "inquirer": "^0.11.0",
- "lodash": "^3.10.1"
- },
- "dependencies": {
- "ansi-escapes": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
- "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=",
- "dev": true
- },
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "ansi-styles": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
- "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
- "dev": true
- },
- "chalk": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
- "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
- "dev": true,
- "requires": {
- "ansi-styles": "^2.2.1",
- "escape-string-regexp": "^1.0.2",
- "has-ansi": "^2.0.0",
- "strip-ansi": "^3.0.0",
- "supports-color": "^2.0.0"
- }
- },
- "cli-cursor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
- "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
- "dev": true,
- "requires": {
- "restore-cursor": "^1.0.1"
- }
- },
- "cli-width": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz",
- "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=",
- "dev": true
- },
- "figures": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
- "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
- "dev": true,
- "requires": {
- "escape-string-regexp": "^1.0.5",
- "object-assign": "^4.1.0"
- }
- },
- "inquirer": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.11.4.tgz",
- "integrity": "sha1-geM3ToNhvq/y2XAWIG01nQsy+k0=",
- "dev": true,
- "requires": {
- "ansi-escapes": "^1.1.0",
- "ansi-regex": "^2.0.0",
- "chalk": "^1.0.0",
- "cli-cursor": "^1.0.1",
- "cli-width": "^1.0.1",
- "figures": "^1.3.5",
- "lodash": "^3.3.1",
- "readline2": "^1.0.1",
- "run-async": "^0.1.0",
- "rx-lite": "^3.1.2",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.0",
- "through": "^2.3.6"
- }
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "lodash": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
- "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
- "dev": true
- },
- "onetime": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
- "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
- "dev": true
- },
- "restore-cursor": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
- "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
- "dev": true,
- "requires": {
- "exit-hook": "^1.0.0",
- "onetime": "^1.0.0"
- }
- },
- "run-async": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
- "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=",
- "dev": true,
- "requires": {
- "once": "^1.3.0"
- }
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "supports-color": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
- "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
- "dev": true
- }
- }
- },
"grunt-rtlcss": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/grunt-rtlcss/-/grunt-rtlcss-2.0.2.tgz",
@@ -10209,34 +10048,6 @@
"integrity": "sha512-90s27H7FoCDcA8C8+R0GwC+ntYD3lG6S/jqcavWm3bn9RiJTmSfOvfbFa1PXx4NbBWuiGQMLfQTj/JvvqT5w6A==",
"dev": true
},
- "grunt-shell": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/grunt-shell/-/grunt-shell-3.0.1.tgz",
- "integrity": "sha512-C8eR4frw/NmIFIwSvzSLS4wOQBUzC+z6QhrKPzwt/tlaIqlzH35i/O2MggVOBj2Sh1tbaAqpASWxGiGsi4JMIQ==",
- "dev": true,
- "requires": {
- "chalk": "^2.4.1",
- "npm-run-path": "^2.0.0",
- "strip-ansi": "^5.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
- "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
- "dev": true
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "requires": {
- "ansi-regex": "^4.1.0"
- }
- }
- }
- },
"grunt-stylelint": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/grunt-stylelint/-/grunt-stylelint-0.14.0.tgz",
@@ -17452,34 +17263,6 @@
}
}
},
- "readline2": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
- "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "mute-stream": "0.0.5"
- },
- "dependencies": {
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "mute-stream": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
- "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=",
- "dev": true
- }
- }
- },
"realpath-native": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz",
@@ -18017,12 +17800,6 @@
"integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=",
"dev": true
},
- "rx-lite": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
- "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=",
- "dev": true
- },
"rxjs": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
diff --git a/package.json b/package.json
index d39aab8703b..26a29b49bbc 100644
--- a/package.json
+++ b/package.json
@@ -47,10 +47,8 @@
"grunt-contrib-watch": "1.1.0",
"grunt-phpcs": "0.4.0",
"grunt-postcss": "0.9.0",
- "grunt-prompt": "1.3.3",
"grunt-rtlcss": "2.0.2",
"grunt-sass": "3.1.0",
- "grunt-shell": "3.0.1",
"grunt-stylelint": "0.14.0",
"gruntify-eslint": "5.0.0",
"husky": "4.2.5",
From 1d99c343e7cd6efc2a02713ce8c6cef741e7e550 Mon Sep 17 00:00:00 2001
From: Claudio Sanches
Date: Tue, 12 May 2020 15:36:25 -0300
Subject: [PATCH 040/153] Added unit tests
---
tests/legacy/unit-tests/admin/settings.php | 35 ++++++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 tests/legacy/unit-tests/admin/settings.php
diff --git a/tests/legacy/unit-tests/admin/settings.php b/tests/legacy/unit-tests/admin/settings.php
new file mode 100644
index 00000000000..585d996e14e
--- /dev/null
+++ b/tests/legacy/unit-tests/admin/settings.php
@@ -0,0 +1,35 @@
+assertEquals( 'deny from all', $file_content );
+
+ // Test with "redirect" downloads method.
+ update_option( 'woocommerce_file_download_method', 'redirect' );
+ WC_Admin_Settings::check_download_folder_protection();
+ $file_content = @file_get_contents( $file_path );
+ $this->assertEquals( 'Options -Indexes', $file_content );
+
+ update_option( 'woocommerce_file_download_method', $default );
+ }
+}
From 9d711a6afa3b1adf2824989bd1052bae1b85f370 Mon Sep 17 00:00:00 2001
From: Sergey Ratushnuy
Date: Thu, 26 Dec 2019 12:02:47 +0200
Subject: [PATCH 041/153] Add filters for status_widget_stock_rows queries
---
includes/admin/class-wc-admin-dashboard.php | 73 ++++++++++++++-------
1 file changed, 49 insertions(+), 24 deletions(-)
diff --git a/includes/admin/class-wc-admin-dashboard.php b/includes/admin/class-wc-admin-dashboard.php
index 33621090e1d..28fd096e164 100644
--- a/includes/admin/class-wc-admin-dashboard.php
+++ b/includes/admin/class-wc-admin-dashboard.php
@@ -214,36 +214,61 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) :
$lowinstock_count = get_transient( $transient_name );
if ( false === $lowinstock_count ) {
- $lowinstock_count = (int) $wpdb->get_var(
- $wpdb->prepare(
- "SELECT COUNT( product_id )
- FROM {$wpdb->wc_product_meta_lookup} AS lookup
- INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID
- WHERE stock_quantity <= %d
- AND stock_quantity > %d
- AND posts.post_status = 'publish'",
- $stock,
- $nostock
- )
- );
- set_transient( $transient_name, $lowinstock_count, DAY_IN_SECONDS * 30 );
+ /**
+ * Status widget low in stock count pre query.
+ *
+ * @since 5.3
+ * @param null|string $low_in_stock_count Low in stock count, by default null.
+ * @param int $stock Low stock amount.
+ * @param int $nostock No stock amount
+ */
+ $lowinstock_count = apply_filters( 'woocommerce_status_widget_low_in_stock_count_pre_query', null, $stock, $nostock );
+
+ if ( ! is_null( $lowinstock_count ) ) {
+ $lowinstock_count = $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT COUNT( product_id )
+ FROM {$wpdb->wc_product_meta_lookup} AS lookup
+ INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID
+ WHERE stock_quantity <= %d
+ AND stock_quantity > %d
+ AND posts.post_status = 'publish'",
+ $stock,
+ $nostock
+ )
+ );
+ }
+
+ set_transient( $transient_name, (int) $lowinstock_count, DAY_IN_SECONDS * 30 );
}
$transient_name = 'wc_outofstock_count';
$outofstock_count = get_transient( $transient_name );
if ( false === $outofstock_count ) {
- $outofstock_count = (int) $wpdb->get_var(
- $wpdb->prepare(
- "SELECT COUNT( product_id )
- FROM {$wpdb->wc_product_meta_lookup} AS lookup
- INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID
- WHERE stock_quantity <= %d
- AND posts.post_status = 'publish'",
- $nostock
- )
- );
- set_transient( $transient_name, $outofstock_count, DAY_IN_SECONDS * 30 );
+ /**
+ * Status widget out of stock count pre query.
+ *
+ * @since 5.3
+ * @param null|string $outofstock_count Out of stock count, by default null.
+ * @param int $nostock No stock amount
+ */
+ $outofstock_count = apply_filters( 'woocommerce_status_widget_out_of_stock_count_pre_query', null, $nostock );
+
+ if ( ! is_null( $outofstock_count ) ) {
+ $outofstock_count = (int) $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT COUNT( product_id )
+ FROM {$wpdb->wc_product_meta_lookup} AS lookup
+ INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID
+ WHERE stock_quantity <= %d
+ AND posts.post_status = 'publish'",
+ $nostock
+ )
+ );
+ }
+
+ set_transient( $transient_name, (int) $outofstock_count, DAY_IN_SECONDS * 30 );
}
?>
From 07087c79a7302bd4cb6b9e9d2849be4ee678e77a Mon Sep 17 00:00:00 2001
From: Joshua Flowers
Date: Wed, 13 May 2020 16:49:54 +0300
Subject: [PATCH 042/153] Always show tracks function in footer
---
includes/tracks/class-wc-site-tracking.php | 20 +++-----------------
1 file changed, 3 insertions(+), 17 deletions(-)
diff --git a/includes/tracks/class-wc-site-tracking.php b/includes/tracks/class-wc-site-tracking.php
index 82697a05791..781e78e9a12 100644
--- a/includes/tracks/class-wc-site-tracking.php
+++ b/includes/tracks/class-wc-site-tracking.php
@@ -82,29 +82,15 @@ class WC_Site_Tracking {
-
-
Date: Wed, 13 May 2020 16:53:57 +0300
Subject: [PATCH 043/153] Add property to check if wcTracks is enabled
---
includes/tracks/class-wc-site-tracking.php | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/includes/tracks/class-wc-site-tracking.php b/includes/tracks/class-wc-site-tracking.php
index 781e78e9a12..84c45a62aad 100644
--- a/includes/tracks/class-wc-site-tracking.php
+++ b/includes/tracks/class-wc-site-tracking.php
@@ -65,7 +65,12 @@ class WC_Site_Tracking {
+
Date: Thu, 14 May 2020 22:26:13 -0600
Subject: [PATCH 045/153] Give plugins an entry point into the VariationForm
object
---
assets/js/frontend/add-to-cart-variation.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/frontend/add-to-cart-variation.js b/assets/js/frontend/add-to-cart-variation.js
index 9c0a7b77e5e..49b9085e75f 100644
--- a/assets/js/frontend/add-to-cart-variation.js
+++ b/assets/js/frontend/add-to-cart-variation.js
@@ -43,7 +43,7 @@
// Init after gallery.
setTimeout( function() {
$form.trigger( 'check_variations' );
- $form.trigger( 'wc_variation_form' );
+ $form.trigger( 'wc_variation_form', self );
self.loading = false;
}, 100 );
};
From d1672ff42e776aa8df0777309faa1744688339a6 Mon Sep 17 00:00:00 2001
From: Kathy Daring
Date: Thu, 14 May 2020 22:27:40 -0600
Subject: [PATCH 046/153] optionally accept a custom array of chosen
attributes. This will make it easier for "Swatches" and "Radio" input plugins
to interact with the variations script without replacing the entire thing.
---
assets/js/frontend/add-to-cart-variation.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/assets/js/frontend/add-to-cart-variation.js b/assets/js/frontend/add-to-cart-variation.js
index 49b9085e75f..e9541a1691b 100644
--- a/assets/js/frontend/add-to-cart-variation.js
+++ b/assets/js/frontend/add-to-cart-variation.js
@@ -160,9 +160,9 @@
/**
* Looks for matching variations for current selected attributes.
*/
- VariationForm.prototype.onFindVariation = function( event ) {
+ VariationForm.prototype.onFindVariation = function( event, chosenAttributes ) {
var form = event.data.variationForm,
- attributes = form.getChosenAttributes(),
+ attributes = 'undefined' !== typeof chosenAttributes ? chosenAttributes : form.getChosenAttributes(),
currentAttributes = attributes.data;
if ( attributes.count === attributes.chosenCount ) {
From cecfee4f57e757e01c395df70a44e95785ecc1d7 Mon Sep 17 00:00:00 2001
From: Kathy Daring
Date: Thu, 14 May 2020 22:28:39 -0600
Subject: [PATCH 047/153] to account for fields not being present,
make sure ajax doesn't fire if there are no found attributes
---
assets/js/frontend/add-to-cart-variation.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/frontend/add-to-cart-variation.js b/assets/js/frontend/add-to-cart-variation.js
index e9541a1691b..c7c603725ee 100644
--- a/assets/js/frontend/add-to-cart-variation.js
+++ b/assets/js/frontend/add-to-cart-variation.js
@@ -165,7 +165,7 @@
attributes = 'undefined' !== typeof chosenAttributes ? chosenAttributes : form.getChosenAttributes(),
currentAttributes = attributes.data;
- if ( attributes.count === attributes.chosenCount ) {
+ if ( attributes.count && attributes.count === attributes.chosenCount ) {
if ( form.useAjax ) {
if ( form.xhr ) {
form.xhr.abort();
From 33b0bef8c590903285952517064446235e009d40 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?=
Date: Tue, 25 Feb 2020 15:21:53 +0100
Subject: [PATCH 048/153] Add is_enabled=true check in
wc_get_shipping_method_count()
---
includes/wc-core-functions.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index e197a0ea64c..c39f5c3487e 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -1506,7 +1506,7 @@ function wc_get_shipping_method_count( $include_legacy = false ) {
return absint( $transient_value['value'] );
}
- $method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods" ) );
+ $method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE is_enabled=true" ) );
if ( $include_legacy ) {
// Count activated methods that don't support shipping zones.
From de7ca06b4134d5fa49885ac5b0d040717858686d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?=
Date: Wed, 26 Feb 2020 15:08:28 +0100
Subject: [PATCH 049/153] Set is_enabled=1 instead of is_enabled=true
---
includes/wc-core-functions.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index c39f5c3487e..40a9ec30896 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -1506,7 +1506,7 @@ function wc_get_shipping_method_count( $include_legacy = false ) {
return absint( $transient_value['value'] );
}
- $method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE is_enabled=true" ) );
+ $method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE is_enabled=1" ) );
if ( $include_legacy ) {
// Count activated methods that don't support shipping zones.
From b01f3aae93d1ce0b6b715150804d906c753d95d2 Mon Sep 17 00:00:00 2001
From: Boro Sitnikovski
Date: Fri, 15 May 2020 11:21:29 +0200
Subject: [PATCH 050/153] Add some additional information and be specific about
babel file
---
tests/e2e/env/README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tests/e2e/env/README.md b/tests/e2e/env/README.md
index 6ce824a29dd..6219b459b8d 100644
--- a/tests/e2e/env/README.md
+++ b/tests/e2e/env/README.md
@@ -15,7 +15,7 @@ The `@woocommerce/e2e-environment` package exports configuration objects that ca
### Babel Config
-Extend your project's Babel config to contain the expected presets for E2E testing.
+Extend your project's `babel.config.js` to contain the expected presets for E2E testing.
```js
const { babelConfig: e2eBabelConfig } = require( '@woocommerce/e2e-environment' );
@@ -185,3 +185,7 @@ Stop Docker
```bash
npm explore @woocommerce/e2e-environment -- npm run docker:down
```
+
+## Additional information
+
+Refer to [`tests/e2e/specs`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e/specs) for some test examples, and [`tests/e2e`](https://github.com/woocommerce/woocommerce/tree/master/tests/e2e) for general information on e2e tests.
From ad3b9fc8616816cd1602ba49639610a0dcd5a34f Mon Sep 17 00:00:00 2001
From: Boro Sitnikovski
Date: Fri, 15 May 2020 11:21:59 +0200
Subject: [PATCH 051/153] Fix version mismatch error
---
tests/e2e/env/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/e2e/env/README.md b/tests/e2e/env/README.md
index 6219b459b8d..801ef213d32 100644
--- a/tests/e2e/env/README.md
+++ b/tests/e2e/env/README.md
@@ -109,7 +109,7 @@ module.exports = {
The E2E environment will look for a `docker-compose.yaml` file in your project root. This will be combined with the base Docker config in the package. This is where you'll map your local project files into the Docker container(s).
```yaml
-version: '3.7'
+version: '3.3'
services:
From 973e50ab64af9239178f772333ac42afa3164088 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?=
Date: Fri, 15 May 2020 11:19:25 +0200
Subject: [PATCH 052/153] Add param to wc_get_shipping_method_count()
---
includes/wc-core-functions.php | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 40a9ec30896..1ecb985e5cd 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -1493,9 +1493,12 @@ function wc_postcode_location_matcher( $postcode, $objects, $object_id_key, $obj
*
* @since 2.6.0
* @param bool $include_legacy Count legacy shipping methods too.
+ * @param bool $enabled_only Whether non-legacy shipping methods should be
+ * restricted to enabled ones. It doesn't affect
+ * legacy shipping methods. @since 4.2.0.
* @return int
*/
-function wc_get_shipping_method_count( $include_legacy = false ) {
+function wc_get_shipping_method_count( $include_legacy = false, $enabled_only = false ) {
global $wpdb;
$transient_name = $include_legacy ? 'wc_shipping_method_count_legacy' : 'wc_shipping_method_count';
@@ -1506,7 +1509,8 @@ function wc_get_shipping_method_count( $include_legacy = false ) {
return absint( $transient_value['value'] );
}
- $method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE is_enabled=1" ) );
+ $where_clause = $enabled_only ? 'WHERE is_enabled=1' : '';
+ $method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods ${where_clause}" ) );
if ( $include_legacy ) {
// Count activated methods that don't support shipping zones.
From f34e73d40272819170b201647aaa5b5f668eb6ca Mon Sep 17 00:00:00 2001
From: Boro Sitnikovski
Date: Fri, 15 May 2020 11:28:21 +0200
Subject: [PATCH 053/153] Add a comment on @babel/preset-env
---
tests/e2e/env/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/e2e/env/README.md b/tests/e2e/env/README.md
index 801ef213d32..f7030d045b4 100644
--- a/tests/e2e/env/README.md
+++ b/tests/e2e/env/README.md
@@ -15,7 +15,7 @@ The `@woocommerce/e2e-environment` package exports configuration objects that ca
### Babel Config
-Extend your project's `babel.config.js` to contain the expected presets for E2E testing.
+Make sure you `npm install @babel/preset-env --save` if you have not already done so. Afterwards, extend your project's `babel.config.js` to contain the expected presets for E2E testing.
```js
const { babelConfig: e2eBabelConfig } = require( '@woocommerce/e2e-environment' );
From 9a34c4fe75332cfcd88a49379547f4cb111b9ec1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?=
Date: Fri, 15 May 2020 11:29:29 +0200
Subject: [PATCH 054/153] Update WC shipping settings so no shipping zones
banner appears when all are deactivated
---
includes/admin/settings/class-wc-settings-shipping.php | 2 +-
includes/wc-core-functions.php | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/includes/admin/settings/class-wc-settings-shipping.php b/includes/admin/settings/class-wc-settings-shipping.php
index f34eb287011..11e66d59a5d 100644
--- a/includes/admin/settings/class-wc-settings-shipping.php
+++ b/includes/admin/settings/class-wc-settings-shipping.php
@@ -286,7 +286,7 @@ class WC_Settings_Shipping extends WC_Settings_Page {
* Show zones
*/
protected function zones_screen() {
- $method_count = wc_get_shipping_method_count();
+ $method_count = wc_get_shipping_method_count( false, true );
wp_localize_script(
'wc-shipping-zones',
diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php
index 1ecb985e5cd..3119d7ba08b 100644
--- a/includes/wc-core-functions.php
+++ b/includes/wc-core-functions.php
@@ -1495,7 +1495,7 @@ function wc_postcode_location_matcher( $postcode, $objects, $object_id_key, $obj
* @param bool $include_legacy Count legacy shipping methods too.
* @param bool $enabled_only Whether non-legacy shipping methods should be
* restricted to enabled ones. It doesn't affect
- * legacy shipping methods. @since 4.2.0.
+ * legacy shipping methods. @since 4.3.0.
* @return int
*/
function wc_get_shipping_method_count( $include_legacy = false, $enabled_only = false ) {
From 07c6075dde5a1a0d3b09ba464d4540ba3030840c Mon Sep 17 00:00:00 2001
From: Ron Rennick
Date: Fri, 15 May 2020 16:58:50 -0300
Subject: [PATCH 055/153] use nonced logout link for store owner logout
---
tests/e2e/utils/flows.js | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/tests/e2e/utils/flows.js b/tests/e2e/utils/flows.js
index 50ea2716937..cb72820abb3 100644
--- a/tests/e2e/utils/flows.js
+++ b/tests/e2e/utils/flows.js
@@ -241,16 +241,15 @@ const StoreOwnerFlow = {
},
logout: async () => {
- await page.goto(baseUrl + 'wp-login.php?action=logout', {
+ // Log out link in admin bar is not visible so can't be clicked directly.
+ const logoutLinks = await page.$$eval(
+ '#wp-admin-bar-logout a',
+ ( am ) => am.filter( ( e ) => e.href ).map( ( e ) => e.href )
+ );
+
+ await page.goto( logoutLinks[ 0 ], {
waitUntil: 'networkidle0',
- });
-
- await expect(page).toMatch('You are attempting to log out');
-
- await Promise.all([
- page.waitForNavigation({ waitUntil: 'networkidle0' }),
- page.click('a'),
- ]);
+ } );
},
openAllOrdersView: async () => {
From 07b1887c2b42c69d6aa1de676945e9380db99400 Mon Sep 17 00:00:00 2001
From: Joshua Flowers
Date: Mon, 18 May 2020 19:11:29 +0300
Subject: [PATCH 056/153] Use var instead of const
---
includes/tracks/class-wc-site-tracking.php | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/includes/tracks/class-wc-site-tracking.php b/includes/tracks/class-wc-site-tracking.php
index 7699e43794c..f923b88ce13 100644
--- a/includes/tracks/class-wc-site-tracking.php
+++ b/includes/tracks/class-wc-site-tracking.php
@@ -102,13 +102,13 @@ class WC_Site_Tracking {
window.wcTracks.enable = function( callback = null ) {
window.wcTracks.isEnabled = true;
- const scriptUrl = '';
- const existingScript = document.querySelector( `script[src="${ scriptUrl }"]` );
+ var scriptUrl = '';
+ var existingScript = document.querySelector( `script[src="${ scriptUrl }"]` );
if ( existingScript ) {
return;
}
- const script = document.createElement('script');
+ var script = document.createElement('script');
script.src = scriptUrl;
document.body.append(script);
From d1ac3731a57dd373c48743a01d137bde7e7f0817 Mon Sep 17 00:00:00 2001
From: Boro Sitnikovski
Date: Mon, 18 May 2020 21:04:13 +0200
Subject: [PATCH 057/153] Add actions before/after shipping calculation
---
includes/abstracts/abstract-wc-shipping-method.php | 2 ++
1 file changed, 2 insertions(+)
diff --git a/includes/abstracts/abstract-wc-shipping-method.php b/includes/abstracts/abstract-wc-shipping-method.php
index c5922a75627..b62e4458a22 100644
--- a/includes/abstracts/abstract-wc-shipping-method.php
+++ b/includes/abstracts/abstract-wc-shipping-method.php
@@ -230,7 +230,9 @@ abstract class WC_Shipping_Method extends WC_Settings_API {
public function get_rates_for_package( $package ) {
$this->rates = array();
if ( $this->is_available( $package ) && ( empty( $package['ship_via'] ) || in_array( $this->id, $package['ship_via'] ) ) ) {
+ do_action( 'woocommerce_before_calculate_shipping', $package, $this );
$this->calculate_shipping( $package );
+ do_action( 'woocommerce_after_calculate_shipping', $package, $this );
}
return $this->rates;
}
From 7db709957b20c9561d27ed3884f4fd951fdb0d98 Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Tue, 19 May 2020 06:59:52 -0700
Subject: [PATCH 058/153] Increased the WordPress minimum version to 5.2 as per
our support policy
---
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/readme.txt b/readme.txt
index 8255ebeb860..51c5850231f 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,7 +1,7 @@
=== WooCommerce ===
Contributors: automattic, mikejolley, jameskoster, claudiosanches, kloon, rodrigosprimo, peterfabian1000, vedjain, jamosova, obliviousharmony
Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, downloads, payments, paypal, storefront, stripe, woo commerce
-Requires at least: 5.0
+Requires at least: 5.2
Tested up to: 5.4
Requires PHP: 7.0
Stable tag: 4.0.1
From df7db728557ff9c8d5ecde274380e5c331cd8fba Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Tue, 19 May 2020 07:00:32 -0700
Subject: [PATCH 059/153] Cleaned up our Travis config to suit the WP support
policy
---
.travis.yml | 27 ++++++++++++---------------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index b83de198cf3..106756dd041 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,9 +30,6 @@ env:
jobs:
fast_finish: true
include:
- - name: "Coding standard check"
- php: 7.4
- env: WP_VERSION=latest WP_MULTISITE=0 RUN_PHPCS=1
- name: "Core E2E Tests"
php: 7.4
env: WP_VERSION=latest WP_MULTISITE=0 RUN_E2E=1
@@ -44,24 +41,24 @@ jobs:
- npm run test:e2e
after_script:
- npm run docker:down
- - name: "Unit tests code coverage"
- php: 7.4
- env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1
- - name: "WooCommerce unit tests using WordPress nightly"
+ - name: "WP Nightly"
php: 7.4
env: WP_VERSION=nightly WP_MULTISITE=0
- - name: "WP latest - 1"
+ - name: "WP Latest"
+ php: 7.2
+ env: WP_VERSION=5.4 WP_MULTISITE=0
+ - name: "WP Latest - 1"
php: 7.2
env: WP_VERSION=5.3 WP_MULTISITE=0
- - name: "WP latest - 2"
+ - name: "WP Latest - 2"
php: 7.2
env: WP_VERSION=5.2 WP_MULTISITE=0
- - name: "WP 5.1"
- php: 7.2
- env: WP_VERSION=5.1 WP_MULTISITE=0
- - name: "WP 5.0"
- php: 7.0
- env: WP_VERSION=5.0 WP_MULTISITE=0
+ - name: "Code Standards"
+ php: 7.4
+ env: WP_VERSION=latest WP_MULTISITE=0 RUN_PHPCS=1
+ - name: "Code Coverage"
+ php: 7.4
+ env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1
allow_failures:
- php: 7.4
env: WP_VERSION=latest WP_MULTISITE=0 RUN_CODE_COVERAGE=1
From b7bca44beb357551e130006f2b1e7dc5cff9ab61 Mon Sep 17 00:00:00 2001
From: Joshua Flowers
Date: Tue, 19 May 2020 18:19:19 +0300
Subject: [PATCH 060/153] Call callback on tracking script load error
---
includes/tracks/class-wc-site-tracking.php | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/includes/tracks/class-wc-site-tracking.php b/includes/tracks/class-wc-site-tracking.php
index f923b88ce13..1890045fd9c 100644
--- a/includes/tracks/class-wc-site-tracking.php
+++ b/includes/tracks/class-wc-site-tracking.php
@@ -115,7 +115,14 @@ class WC_Site_Tracking {
// Callback after scripts have loaded.
script.onload = function() {
if ( 'function' === typeof callback ) {
- callback();
+ callback( true );
+ }
+ }
+
+ // Callback triggered if the script fails to load.
+ script.onerror = function() {
+ if ( 'function' === typeof callback ) {
+ callback( false );
}
}
}
From 326d8feb923159da70a7853c80ac18a8df8cdaf6 Mon Sep 17 00:00:00 2001
From: Christopher Allford
Date: Tue, 19 May 2020 16:16:50 -0700
Subject: [PATCH 061/153] Updated the Jetpack Autoloader
Jetpack made a change that requires some extra ignore entries from the latest release of the autoloader, otherwise debug logging warnings are thrown.
---
composer.json | 2 +-
composer.lock | 19 +++++++++++++++++--
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/composer.json b/composer.json
index 04f34d1911d..4b712371355 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,7 @@
"minimum-stability": "dev",
"require": {
"php": ">=7.0",
- "automattic/jetpack-autoloader": "^1.6.0",
+ "automattic/jetpack-autoloader": "^1.7.0",
"automattic/jetpack-constants": "^1.1",
"composer/installers": "1.7.0",
"maxmind-db/reader": "1.6.0",
diff --git a/composer.lock b/composer.lock
index eabea333cb2..bd5ad7397c3 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "9548fdb91087ffaa8f14374698bf402e",
+ "content-hash": "94c4cc4a9a0fc2ad6758cc6c7cb069bb",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@@ -2517,6 +2517,20 @@
"polyfill",
"portable"
],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
"time": "2020-05-08T16:50:20+00:00"
},
{
@@ -2921,5 +2935,6 @@
"platform-dev": [],
"platform-overrides": {
"php": "7.1"
- }
+ },
+ "plugin-api-version": "1.1.0"
}
From db58b51de38d993ade21f70e3c51963bfafbff58 Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Thu, 9 Apr 2020 16:32:40 +0200
Subject: [PATCH 062/153] Barebones implementation of a code hacker for unit
tests.
The "code hacker" is a class that hooks on filesystem events
(using stream_wrapper_unregister) in order to allow for dynamically
modifying the content of PHP code files while they are loaded.
The code hacker class allows registering hacks, which are
functions that take source code as input and return the modified code.
A hack can be a standalone function or a class with a "hack" method.
A few hacks are provided off the shelf. One allows mocking standalone
PHP functions (WP, WOO or not), another one allows mocking static
methods, and there's the one that removes the "final" qualifier
from a class definition. This helps unit testing stuff that would
otherwise be quite hard to test.
---
phpunit.xml | 3 +
.../code-hacking/code-hacker-test-hook.php | 64 +++++
tests/includes/code-hacking/code-hacker.php | 241 ++++++++++++++++++
.../code-hacking/hacks/bypass-finals-hack.php | 23 ++
.../includes/code-hacking/hacks/code-hack.php | 67 +++++
.../hacks/functions-mocker-hack.php | 53 ++++
.../code-hacking/hacks/static-mocker-hack.php | 87 +++++++
7 files changed, 538 insertions(+)
create mode 100644 tests/includes/code-hacking/code-hacker-test-hook.php
create mode 100644 tests/includes/code-hacking/code-hacker.php
create mode 100644 tests/includes/code-hacking/hacks/bypass-finals-hack.php
create mode 100644 tests/includes/code-hacking/hacks/code-hack.php
create mode 100644 tests/includes/code-hacking/hacks/functions-mocker-hack.php
create mode 100644 tests/includes/code-hacking/hacks/static-mocker-hack.php
diff --git a/phpunit.xml b/phpunit.xml
index 75b6a007d51..ee4324fae91 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -49,4 +49,7 @@
+
+
+
diff --git a/tests/includes/code-hacking/code-hacker-test-hook.php b/tests/includes/code-hacking/code-hacker-test-hook.php
new file mode 100644
index 00000000000..3841c0c01cd
--- /dev/null
+++ b/tests/includes/code-hacking/code-hacker-test-hook.php
@@ -0,0 +1,64 @@
+
+ *
+ *
+ *
+ * 2. Add the following to the test classes:
+ *
+ * public static function before_all($method_name) {
+ * CodeHacker::add_hack(...);
+ * //Register as many hacks as needed
+ * CodeHacker::enable();
+ * }
+ *
+ * $method_name is optional, 'before_all()' is also a valid method signature.
+ *
+ * You can also define a test-specific 'before_{$test_method_name}' hook.
+ * If both exist, first 'before_all' will be executed, then the test-specific one.
+ */
+final class CodeHackerTestHook implements BeforeTestHook {
+
+ public function executeBeforeTest( string $test ): void {
+ $parts = explode( '::', $test );
+ $class_name = $parts[0];
+ $method_name = $parts[1];
+
+ $methods = array( 'before_all', "before_{$method_name}" );
+ $methods = array_filter(
+ $methods,
+ function( $item ) use ( $class_name ) {
+ return method_exists( $class_name, $item );
+ }
+ );
+
+ if ( empty( $methods ) ) {
+ return;
+ }
+
+ // Make code hacker class and individual hack classes available to tests
+ include_once __DIR__ . '/code-hacker.php';
+ foreach ( glob( __DIR__ . '/hacks/*.php' ) as $hack_class_file ) {
+ include_once $hack_class_file;
+ }
+
+ $rc = new ReflectionClass( $class_name );
+
+ foreach ( $methods as $method ) {
+ if ( 0 === $rc->getMethod( $method_name )->getNumberOfParameters() ) {
+ $class_name::$method();
+ } else {
+ $class_name::$method( $method_name );
+ }
+ }
+ }
+}
diff --git a/tests/includes/code-hacking/code-hacker.php b/tests/includes/code-hacking/code-hacker.php
new file mode 100644
index 00000000000..5c2dc891018
--- /dev/null
+++ b/tests/includes/code-hacking/code-hacker.php
@@ -0,0 +1,241 @@
+handle );
+ }
+
+
+ public function dir_opendir( $path, $options ) {
+ $this->handle = $this->context
+ ? $this->native( 'opendir', $path, $this->context )
+ : $this->native( 'opendir', $path );
+ return (bool) $this->handle;
+ }
+
+
+ public function dir_readdir() {
+ return readdir( $this->handle );
+ }
+
+
+ public function dir_rewinddir() {
+ return rewinddir( $this->handle );
+ }
+
+
+ public function mkdir( $path, $mode, $options ) {
+ $recursive = (bool) ( $options & STREAM_MKDIR_RECURSIVE );
+ return $this->native( 'mkdir', $path, $mode, $recursive, $this->context );
+ }
+
+
+ public function rename( $pathFrom, $pathTo ) {
+ return $this->native( 'rename', $pathFrom, $pathTo, $this->context );
+ }
+
+
+ public function rmdir( $path, $options ) {
+ return $this->native( 'rmdir', $path, $this->context );
+ }
+
+
+ public function stream_cast( $castAs ) {
+ return $this->handle;
+ }
+
+
+ public function stream_close() {
+ fclose( $this->handle );
+ }
+
+
+ public function stream_eof() {
+ return feof( $this->handle );
+ }
+
+
+ public function stream_flush() {
+ return fflush( $this->handle );
+ }
+
+
+ public function stream_lock( $operation ) {
+ return $operation
+ ? flock( $this->handle, $operation )
+ : true;
+ }
+
+
+ public function stream_metadata( $path, $option, $value ) {
+ switch ( $option ) {
+ case STREAM_META_TOUCH:
+ $value += array( null, null );
+ return $this->native( 'touch', $path, $value[0], $value[1] );
+ case STREAM_META_OWNER_NAME:
+ case STREAM_META_OWNER:
+ return $this->native( 'chown', $path, $value );
+ case STREAM_META_GROUP_NAME:
+ case STREAM_META_GROUP:
+ return $this->native( 'chgrp', $path, $value );
+ case STREAM_META_ACCESS:
+ return $this->native( 'chmod', $path, $value );
+ }
+ }
+
+
+ public function stream_open( $path, $mode, $options, &$openedPath ) {
+ $usePath = (bool) ( $options & STREAM_USE_PATH );
+ if ( $mode === 'rb' && self::pathInWhitelist( $path ) && pathinfo( $path, PATHINFO_EXTENSION ) === 'php' ) {
+ $content = $this->native( 'file_get_contents', $path, $usePath, $this->context );
+ if ( $content === false ) {
+ return false;
+ }
+ $modified = self::hack( $content, $path );
+ if ( $modified !== $content ) {
+ $this->handle = tmpfile();
+ $this->native( 'fwrite', $this->handle, $modified );
+ $this->native( 'fseek', $this->handle, 0 );
+ return true;
+ }
+ }
+ $this->handle = $this->context
+ ? $this->native( 'fopen', $path, $mode, $usePath, $this->context )
+ : $this->native( 'fopen', $path, $mode, $usePath );
+ return (bool) $this->handle;
+ }
+
+
+ public function stream_read( $count ) {
+ return fread( $this->handle, $count );
+ }
+
+
+ public function stream_seek( $offset, $whence = SEEK_SET ) {
+ return fseek( $this->handle, $offset, $whence ) === 0;
+ }
+
+
+ public function stream_set_option( $option, $arg1, $arg2 ) {
+ }
+
+
+ public function stream_stat() {
+ return fstat( $this->handle );
+ }
+
+
+ public function stream_tell() {
+ return ftell( $this->handle );
+ }
+
+
+ public function stream_truncate( $newSize ) {
+ return ftruncate( $this->handle, $newSize );
+ }
+
+
+ public function stream_write( $data ) {
+ return fwrite( $this->handle, $data );
+ }
+
+
+ public function unlink( $path ) {
+ return $this->native( 'unlink', $path );
+ }
+
+
+ public function url_stat( $path, $flags ) {
+ $func = $flags & STREAM_URL_STAT_LINK ? 'lstat' : 'stat';
+ return $flags & STREAM_URL_STAT_QUIET
+ ? @$this->native( $func, $path )
+ : $this->native( $func, $path );
+ }
+
+
+ private function native( $func ) {
+ stream_wrapper_restore( self::PROTOCOL );
+ $res = call_user_func_array( $func, array_slice( func_get_args(), 1 ) );
+ stream_wrapper_unregister( self::PROTOCOL );
+ stream_wrapper_register( self::PROTOCOL, __CLASS__ );
+ return $res;
+ }
+
+
+ private static function hack( $code, $path ) {
+ foreach ( self::$hacks as $hack ) {
+ if ( is_callable( $hack ) ) {
+ $code = call_user_func( $hack, $code, $path );
+ } else {
+ $code = $hack->hack( $code, $path );
+ }
+ }
+
+ return $code;
+ }
+
+
+ private static function pathInWhitelist( $path ) {
+ if ( empty( self::$pathWhitelist ) ) {
+ return true;
+ }
+ foreach ( self::$pathWhitelist as $whitelistItem ) {
+ if ( substr( $path, -strlen( $whitelistItem ) ) === $whitelistItem ) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/tests/includes/code-hacking/hacks/bypass-finals-hack.php b/tests/includes/code-hacking/hacks/bypass-finals-hack.php
new file mode 100644
index 00000000000..b057541fa79
--- /dev/null
+++ b/tests/includes/code-hacking/hacks/bypass-finals-hack.php
@@ -0,0 +1,23 @@
+tokenize( $code );
+ $code = '';
+ foreach ( $tokens as $token ) {
+ $code .= $this->is_token_of_type( $token, T_FINAL ) ? '' : $this->token_to_string( $token );
+ }
+ }
+
+ return $code;
+ }
+}
diff --git a/tests/includes/code-hacking/hacks/code-hack.php b/tests/includes/code-hacking/hacks/code-hack.php
new file mode 100644
index 00000000000..237f44000e0
--- /dev/null
+++ b/tests/includes/code-hacking/hacks/code-hack.php
@@ -0,0 +1,67 @@
+= 70000 ? token_get_all( $code, TOKEN_PARSE ) : token_get_all( $code );
+ }
+
+ /**
+ * Check if a token is of a given type.
+ *
+ * @param mixed $token Token to check.
+ * @param int $type Type of token to check (see https://www.php.net/manual/en/tokens.php)
+ * @return bool True if it's a token of the given type, false otherwise.
+ */
+ protected function is_token_of_type( $token, $type ) {
+ return is_array( $token ) && $type === $token[0];
+ }
+
+ /**
+ * Converts a token to its string representation.
+ *
+ * @param $token Token to convert.
+ * @return mixed String representation of the token.
+ */
+ protected function token_to_string( $token ) {
+ return is_array( $token ) ? $token[1] : $token;
+ }
+
+ /**
+ * Checks if a string ends with a certain substring.
+ * This method is added to help processing path names within 'hack' methods needing to do so.
+ *
+ * @param string $haystack The string to search in.
+ * @param string $needle The substring to search for.
+ * @return bool True if the $haystack ends with $needle, false otherwise.
+ */
+ protected function string_ends_with( $haystack, $needle ) {
+ $length = strlen( $needle );
+ if ( $length == 0 ) {
+ return true;
+ }
+
+ return ( substr( $haystack, -$length ) === $needle );
+ }
+}
diff --git a/tests/includes/code-hacking/hacks/functions-mocker-hack.php b/tests/includes/code-hacking/hacks/functions-mocker-hack.php
new file mode 100644
index 00000000000..a751cc8a7b3
--- /dev/null
+++ b/tests/includes/code-hacking/hacks/functions-mocker-hack.php
@@ -0,0 +1,53 @@
+mock_class = $mock_class;
+
+ $rc = new ReflectionClass( $mock_class );
+
+ $static_methods = $rc->getMethods( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC );
+ $this->mocked_methods = array_map(
+ function( $item ) {
+ return $item->getName();
+ },
+ $static_methods
+ );
+ }
+
+ public function hack( $code, $path ) {
+ $tokens = $this->tokenize( $code );
+ $code = '';
+ $previous_token_is_object_operator = false;
+
+ foreach ( $tokens as $token ) {
+ if ( $this->is_token_of_type( $token, T_STRING ) && ! $previous_token_is_object_operator && in_array( $token[1], $this->mocked_methods ) ) {
+ $code .= "{$this->mock_class}::{$token[1]}";
+ } else {
+ $code .= $this->token_to_string( $token );
+ $previous_token_is_object_operator = $this->is_token_of_type( $token, T_DOUBLE_COLON ) || $this->is_token_of_type( $token, T_OBJECT_OPERATOR );
+ }
+ }
+
+ return $code;
+ }
+}
diff --git a/tests/includes/code-hacking/hacks/static-mocker-hack.php b/tests/includes/code-hacking/hacks/static-mocker-hack.php
new file mode 100644
index 00000000000..fae90a57f84
--- /dev/null
+++ b/tests/includes/code-hacking/hacks/static-mocker-hack.php
@@ -0,0 +1,87 @@
+source_class = $source_class;
+ $this->target_class = $mock_class;
+
+ $rc = new ReflectionClass( $mock_class );
+
+ $static_methods = $rc->getMethods( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC );
+ $static_methods = array_map(
+ function( $item ) {
+ return $item->getName();
+ },
+ $static_methods
+ );
+
+ $static_properties = $rc->getProperties( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC );
+ $static_properties = array_map(
+ function( $item ) {
+ return '$' . $item->getName();
+ },
+ $static_properties
+ );
+
+ $this->members_implemented_in_mock = array_merge( $static_methods, $static_properties );
+ }
+
+ public function hack( $code, $path ) {
+ $last_item = null;
+
+ if ( stripos( $code, $this->source_class . '::' ) !== false ) {
+ $tokens = $this->tokenize( $code );
+ $code = '';
+ $current_token = null;
+
+ while ( $current_token = current( $tokens ) ) {
+ if ( $this->is_token_of_type( $current_token, T_STRING ) && $this->source_class === $current_token[1] ) {
+ $next_token = next( $tokens );
+ if ( $this->is_token_of_type( $next_token, T_DOUBLE_COLON ) ) {
+ $called_member = next( $tokens )[1];
+ if ( in_array( $called_member, $this->members_implemented_in_mock ) ) {
+ // Reference to source class member that exists in mock class, replace.
+ $code .= "{$this->target_class}::{$called_member}";
+ } else {
+ // Reference to source class member that does NOT exists in mock class, leave unchanged.
+ $code .= "{$this->source_class}::{$called_member}";
+ }
+ } else {
+ // Reference to source class, but not followed by '::'.
+ $code .= $this->token_to_string( $current_token ) . $this->token_to_string( $next_token );
+ }
+ } else {
+ // Not a reference to source class.
+ $code .= $this->token_to_string( $current_token );
+ }
+ next( $tokens );
+ }
+ }
+
+ return $code;
+ }
+}
From 9a5b3b353d5eb3aa9e68349ba213454a578ad092 Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Fri, 10 Apr 2020 15:36:54 +0200
Subject: [PATCH 063/153] Add the @hack code annotation for tests.
Now @hack class and method annotations can be used to register
code hacks as an alternative to using before_ methods.
The syntax is /* @hack HackClassName param1 param2 */
where parameters will be passed to the class constructor.
If the class name ends with "Hack", then that suffix can be
omitted (e.g. "Foo" can be specified instead of "FooHack").
---
.../code-hacking/code-hacker-test-hook.php | 88 ++++++++++++++++---
1 file changed, 78 insertions(+), 10 deletions(-)
diff --git a/tests/includes/code-hacking/code-hacker-test-hook.php b/tests/includes/code-hacking/code-hacker-test-hook.php
index 3841c0c01cd..40ee09b2a5b 100644
--- a/tests/includes/code-hacking/code-hacker-test-hook.php
+++ b/tests/includes/code-hacking/code-hacker-test-hook.php
@@ -25,6 +25,24 @@ use PHPUnit\Runner\BeforeTestHook;
*
* You can also define a test-specific 'before_{$test_method_name}' hook.
* If both exist, first 'before_all' will be executed, then the test-specific one.
+ *
+ * 3. Additionally, you can register hacks via class/method annotations:
+ *
+ * /**
+ * * @hack HackClassName param1 param2
+ * * /
+ * class Some_Test
+ * {
+ * /**
+ * * @hack HackClassName param1 param2
+ * * /
+ * public function test_something() {
+ * }
+ * }
+ *
+ * If the class name ends with 'Hack' you can omit that suffix in the annotation (e.g. 'Foo' instead of 'FooHack').
+ * Parameters specified after the class name will be passed to the class constructor.
+ * Hacks defined as class annotations will be applied to all tests.
*/
final class CodeHackerTestHook implements BeforeTestHook {
@@ -33,6 +51,66 @@ final class CodeHackerTestHook implements BeforeTestHook {
$class_name = $parts[0];
$method_name = $parts[1];
+ // Make code hacker class and individual hack classes visible
+ include_once __DIR__ . '/code-hacker.php';
+ foreach ( glob( __DIR__ . '/hacks/*.php' ) as $hack_class_file ) {
+ include_once $hack_class_file;
+ }
+
+ $this->execute_before_methods( $class_name, $method_name );
+
+ $has_class_annotation_hacks = $this->add_hacks_from_annotations( new ReflectionClass( $class_name ) );
+ $has_method_annotaion_hacks = $this->add_hacks_from_annotations( new ReflectionMethod( $class_name, $method_name ) );
+ if ( $has_class_annotation_hacks || $has_method_annotaion_hacks ) {
+ CodeHacker::enable();
+ }
+ }
+
+ /**
+ * Apply hacks defined in @hack annotations.
+ *
+ * @param object $reflection_object The class or method reflection object whose doc comment will be parsed.
+ * @return bool True if at least one valid @hack annotation was found.
+ * @throws Exception
+ */
+ private function add_hacks_from_annotations( $reflection_object ) {
+ $annotations = \PHPUnit\Util\Test::parseAnnotations( $reflection_object->getDocComment() );
+ $hacks_added = false;
+
+ foreach ( $annotations as $id => $annotation_instances ) {
+ if ( $id !== 'hack' ) {
+ continue;
+ }
+
+ foreach ( $annotation_instances as $annotation ) {
+ preg_match_all( '/"(?:\\\\.|[^\\\\"])*"|\S+/', $annotation, $matches );
+ $params = $matches[0];
+
+ $hack_class = array_shift( $params );
+ if ( ! class_exists( $hack_class ) ) {
+ $original_hack_class = $hack_class;
+ $hack_class .= 'Hack';
+ if ( ! class_exists( $hack_class ) ) {
+ throw new Exception( "Hack class '{$original_hack_class}' defined via annotation in {$class_name}::{$method_name} doesn't exist." );
+ }
+ }
+
+ CodeHacker::add_hack( new $hack_class( ...$params ) );
+ $hacks_added = true;
+ }
+ }
+
+ return $hacks_added;
+ }
+
+ /**
+ * Run the 'before_all' and 'before_{test_method_name}' methods in a class.
+ *
+ * @param string $class_name Test class name.
+ * @param string $method_name Test method name.
+ * @throws ReflectionException
+ */
+ private function execute_before_methods( $class_name, $method_name ) {
$methods = array( 'before_all', "before_{$method_name}" );
$methods = array_filter(
$methods,
@@ -41,16 +119,6 @@ final class CodeHackerTestHook implements BeforeTestHook {
}
);
- if ( empty( $methods ) ) {
- return;
- }
-
- // Make code hacker class and individual hack classes available to tests
- include_once __DIR__ . '/code-hacker.php';
- foreach ( glob( __DIR__ . '/hacks/*.php' ) as $hack_class_file ) {
- include_once $hack_class_file;
- }
-
$rc = new ReflectionClass( $class_name );
foreach ( $methods as $method ) {
From 1a68abbc285fdf2bed0fa36dce70e80b6eb10b03 Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Sun, 12 Apr 2020 13:09:57 +0200
Subject: [PATCH 064/153] Miscellaneous code hacking fixes:
- Fix how CodeHackerTestHook::executeBeforeTest parses the test name,
to account for warnings and tests with data sets.
- CodeHackerTestHook now includes a executeAfterTest hook that
disables the code hacker (needed to prevent it from inadvertently
altering further tests). Also, clear_hacks is executed in
executeBeforeTest for the same reason.
- CodeHacker gets restore, clear_hacks and is_enabled methods
to support the changes in CodeHackerTestHook.
- FunctionsMockerHack fixed so that it doesn't modify strings
that are class method definitions.
- Added the WC_Unit_Test_Case::file_copy method, it must be used
instead of the PHP built-in "copy" in tests, otherwise tests
that run with the code hacker active will fail.
This is something to investigate.
---
.../code-hacking/code-hacker-test-hook.php | 27 ++++++++++++++++---
tests/includes/code-hacking/code-hacker.php | 25 +++++++++++++++--
.../includes/code-hacking/hacks/code-hack.php | 10 +++++++
.../hacks/functions-mocker-hack.php | 22 ++++++++++-----
.../framework/class-wc-unit-test-case.php | 23 ++++++++++++++++
tests/legacy/unit-tests/importer/product.php | 4 +--
.../class-wc-tests-maxmind-database.php | 2 +-
.../legacy/unit-tests/util/api-functions.php | 4 +--
8 files changed, 100 insertions(+), 17 deletions(-)
diff --git a/tests/includes/code-hacking/code-hacker-test-hook.php b/tests/includes/code-hacking/code-hacker-test-hook.php
index 40ee09b2a5b..c94753ca7c7 100644
--- a/tests/includes/code-hacking/code-hacker-test-hook.php
+++ b/tests/includes/code-hacking/code-hacker-test-hook.php
@@ -1,6 +1,7 @@
execute_before_methods( $class_name, $method_name );
$has_class_annotation_hacks = $this->add_hacks_from_annotations( new ReflectionClass( $class_name ) );
diff --git a/tests/includes/code-hacking/code-hacker.php b/tests/includes/code-hacking/code-hacker.php
index 5c2dc891018..d8b19e45e0a 100644
--- a/tests/includes/code-hacking/code-hacker.php
+++ b/tests/includes/code-hacking/code-hacker.php
@@ -33,9 +33,29 @@ class CodeHacker {
private static $hacks = array();
+ private static $enabled = false;
+
public static function enable() {
- stream_wrapper_unregister( self::PROTOCOL );
- stream_wrapper_register( self::PROTOCOL, __CLASS__ );
+ if ( ! self::$enabled ) {
+ stream_wrapper_unregister( self::PROTOCOL );
+ stream_wrapper_register( self::PROTOCOL, __CLASS__ );
+ self::$enabled = true;
+ }
+ }
+
+ public static function restore() {
+ if ( self::$enabled ) {
+ stream_wrapper_restore( self::PROTOCOL );
+ self::$enabled = false;
+ }
+ }
+
+ public static function clear_hacks() {
+ self::$hacks = array();
+ }
+
+ public static function is_enabled() {
+ return self::$enabled;
}
public static function add_hack( $hack ) {
@@ -98,6 +118,7 @@ class CodeHacker {
public function stream_close() {
+ // echo "***** CLOSE HANDLE: " . $this->handle . " \n";
fclose( $this->handle );
}
diff --git a/tests/includes/code-hacking/hacks/code-hack.php b/tests/includes/code-hacking/hacks/code-hack.php
index 237f44000e0..4fa75dbf787 100644
--- a/tests/includes/code-hacking/hacks/code-hack.php
+++ b/tests/includes/code-hacking/hacks/code-hack.php
@@ -38,6 +38,16 @@ abstract class CodeHack {
return is_array( $token ) && $type === $token[0];
}
+ /**
+ * Return the type of a given token.
+ *
+ * @param mixed $token Token to check.
+ * @return mixed|null Type of token (see https://www.php.net/manual/en/tokens.php), or null if it's a character.
+ */
+ protected function token_type_of( $token ) {
+ return is_array( $token ) ? $token[0] : null;
+ }
+
/**
* Converts a token to its string representation.
*
diff --git a/tests/includes/code-hacking/hacks/functions-mocker-hack.php b/tests/includes/code-hacking/hacks/functions-mocker-hack.php
index a751cc8a7b3..7a3f4cbf1c2 100644
--- a/tests/includes/code-hacking/hacks/functions-mocker-hack.php
+++ b/tests/includes/code-hacking/hacks/functions-mocker-hack.php
@@ -14,6 +14,13 @@ require_once __DIR__ . '/code-hack.php';
*/
final class FunctionsMockerHack extends CodeHack {
+ private static $non_global_function_tokens = array(
+ T_PAAMAYIM_NEKUDOTAYIM,
+ T_DOUBLE_COLON,
+ T_OBJECT_OPERATOR,
+ T_FUNCTION,
+ );
+
/**
* FunctionsMockerHack constructor.
*
@@ -35,16 +42,19 @@ final class FunctionsMockerHack extends CodeHack {
}
public function hack( $code, $path ) {
- $tokens = $this->tokenize( $code );
- $code = '';
- $previous_token_is_object_operator = false;
+ $tokens = $this->tokenize( $code );
+ $code = '';
+ $previous_token_is_non_global_function_qualifier = false;
foreach ( $tokens as $token ) {
- if ( $this->is_token_of_type( $token, T_STRING ) && ! $previous_token_is_object_operator && in_array( $token[1], $this->mocked_methods ) ) {
+ $token_type = $this->token_type_of( $token );
+ if ( T_WHITESPACE === $token_type ) {
+ $code .= $this->token_to_string( $token );
+ } elseif ( T_STRING === $token_type && ! $previous_token_is_non_global_function_qualifier && in_array( $token[1], $this->mocked_methods ) ) {
$code .= "{$this->mock_class}::{$token[1]}";
} else {
- $code .= $this->token_to_string( $token );
- $previous_token_is_object_operator = $this->is_token_of_type( $token, T_DOUBLE_COLON ) || $this->is_token_of_type( $token, T_OBJECT_OPERATOR );
+ $code .= $this->token_to_string( $token );
+ $previous_token_is_non_global_function_qualifier = in_array( $token_type, self::$non_global_function_tokens );
}
}
diff --git a/tests/legacy/framework/class-wc-unit-test-case.php b/tests/legacy/framework/class-wc-unit-test-case.php
index 8fb8d00336d..a1e1c8a686a 100644
--- a/tests/legacy/framework/class-wc-unit-test-case.php
+++ b/tests/legacy/framework/class-wc-unit-test-case.php
@@ -99,4 +99,27 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
$message = $message ? $message : "We're all doomed!";
throw new Exception( $message, $code );
}
+
+
+ /**
+ * Copies a file, temporarily disabling the code hacker.
+ * Use this instead of "copy" in tests for compatibility with the code hacker.
+ *
+ * TODO: Investigate why invoking "copy" within a test with the code hacker active causes the test to fail.
+ *
+ * @param string $source Path to the source file.
+ * @param string $dest The destination path.
+ * @param resource $context [optional] A valid context resource created with stream_context_create.
+ * @return bool true on success or false on failure.
+ */
+ public function file_copy( $source, $dest, $context = null ) {
+ if ( CodeHacker::is_enabled() ) {
+ CodeHacker::restore();
+ $result = copy( $source, $dest, $context );
+ CodeHacker::enable();
+ return $result;
+ } else {
+ return copy( $source, $dest, $context );
+ }
+ }
}
diff --git a/tests/legacy/unit-tests/importer/product.php b/tests/legacy/unit-tests/importer/product.php
index 4a8bad510da..89284f3d5ae 100644
--- a/tests/legacy/unit-tests/importer/product.php
+++ b/tests/legacy/unit-tests/importer/product.php
@@ -147,7 +147,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
* @return void
*/
public function test_server_file() {
- copy( $this->csv_file, ABSPATH . '/sample.csv' );
+ $this->file_copy( $this->csv_file, ABSPATH . '/sample.csv' );
$_POST['file_url'] = 'sample.csv';
$import_controller = new WC_Product_CSV_Importer_Controller();
$this->assertEquals( ABSPATH . 'sample.csv', $import_controller->handle_upload() );
@@ -644,7 +644,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
if ( false !== strpos( $url, 'http://demo.woothemes.com' ) ) {
if ( ! empty( $request['filename'] ) ) {
- copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
+ $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
}
$mocked_response = array(
diff --git a/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php b/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
index e4018d93824..6dddc237565 100644
--- a/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
+++ b/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
@@ -126,7 +126,7 @@ class WC_Tests_MaxMind_Database extends WC_Unit_Test_Case {
if ( 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=testing_license&suffix=tar.gz' === $url ) {
// We need to copy the file to where the request is supposed to have streamed it.
- copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/GeoLite2-Country.tar.gz', $request['filename'] );
+ $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/GeoLite2-Country.tar.gz', $request['filename'] );
$mocked_response = array(
'response' => array( 'code' => 200 ),
diff --git a/tests/legacy/unit-tests/util/api-functions.php b/tests/legacy/unit-tests/util/api-functions.php
index bdd1ebf7653..80d3f00ab96 100644
--- a/tests/legacy/unit-tests/util/api-functions.php
+++ b/tests/legacy/unit-tests/util/api-functions.php
@@ -236,14 +236,14 @@ class WC_Tests_API_Functions extends WC_Unit_Test_Case {
} elseif ( 'http://somedomain.com/invalid-image-2.png' === $url ) {
// image with an unsupported mime type.
// we need to manually copy the file as we are mocking the request. without this an empty file is created.
- copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/file.txt', $request['filename'] );
+ $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/file.txt', $request['filename'] );
$mocked_response = array(
'response' => array( 'code' => 200 ),
);
} elseif ( 'http://somedomain.com/' . $this->file_name === $url ) {
// we need to manually copy the file as we are mocking the request. without this an empty file is created.
- copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
+ $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
$mocked_response = array(
'response' => array( 'code' => 200 ),
From 57845ef8b81791894599383b5ffeb4cf6d52cb28 Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Mon, 13 Apr 2020 09:32:19 +0200
Subject: [PATCH 065/153] All code hacking files moved to src\Testing folder.
---
phpunit.xml | 2 +-
.../Testing/CodeHacking/CodeHacker.php | 4 ++-
.../CodeHacking/CodeHackerTestHook.php | 29 +++++++++++--------
.../CodeHacking/Hacks/BypassFinalsHack.php | 2 +-
.../Testing/CodeHacking/Hacks/CodeHack.php | 4 ++-
.../CodeHacking/Hacks/FunctionsMockerHack.php | 5 +++-
.../CodeHacking/Hacks/StaticMockerHack.php | 5 +++-
.../framework/class-wc-unit-test-case.php | 13 +++++----
8 files changed, 40 insertions(+), 24 deletions(-)
rename tests/includes/code-hacking/code-hacker.php => src/Testing/CodeHacking/CodeHacker.php (98%)
rename tests/includes/code-hacking/code-hacker-test-hook.php => src/Testing/CodeHacking/CodeHackerTestHook.php (87%)
rename tests/includes/code-hacking/hacks/bypass-finals-hack.php => src/Testing/CodeHacking/Hacks/BypassFinalsHack.php (88%)
rename tests/includes/code-hacking/hacks/code-hack.php => src/Testing/CodeHacking/Hacks/CodeHack.php (95%)
rename tests/includes/code-hacking/hacks/functions-mocker-hack.php => src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php (94%)
rename tests/includes/code-hacking/hacks/static-mocker-hack.php => src/Testing/CodeHacking/Hacks/StaticMockerHack.php (96%)
diff --git a/phpunit.xml b/phpunit.xml
index ee4324fae91..68ec7b7ca4a 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -50,6 +50,6 @@
-
+
diff --git a/tests/includes/code-hacking/code-hacker.php b/src/Testing/CodeHacking/CodeHacker.php
similarity index 98%
rename from tests/includes/code-hacking/code-hacker.php
rename to src/Testing/CodeHacking/CodeHacker.php
index d8b19e45e0a..d2a10c2d4f9 100644
--- a/tests/includes/code-hacking/code-hacker.php
+++ b/src/Testing/CodeHacking/CodeHacker.php
@@ -1,5 +1,7 @@
- *
+ *
*
*
* 2. Add the following to the test classes:
*
+ * use Automattic\WooCommerce\Testing\CodeHacking\CodeHacker;
+ * use Automattic\WooCommerce\Testing\CodeHacking\Hacks\...
+ *
* public static function before_all($method_name) {
* CodeHacker::add_hack(...);
* //Register as many hacks as needed
@@ -27,7 +36,8 @@ use PHPUnit\Runner\AfterTestHook;
* You can also define a test-specific 'before_{$test_method_name}' hook.
* If both exist, first 'before_all' will be executed, then the test-specific one.
*
- * 3. Additionally, you can register hacks via class/method annotations:
+ * 3. Additionally, you can register hacks via class/method annotations
+ * (note that then you don't need the `use`s anymore):
*
* /**
* * @hack HackClassName param1 param2
@@ -47,10 +57,6 @@ use PHPUnit\Runner\AfterTestHook;
*/
final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
- public function __construct() {
- include_once __DIR__ . '/code-hacker.php';
- }
-
public function executeAfterTest( string $test, float $time ): void {
CodeHacker::restore();
}
@@ -69,11 +75,6 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
$class_name = $parts[0];
$method_name = explode( ' ', $parts[1] )[0];
- // Make code hacker class and individual hack classes visible
- foreach ( glob( __DIR__ . '/hacks/*.php' ) as $hack_class_file ) {
- include_once $hack_class_file;
- }
-
CodeHacker::clear_hacks();
$this->execute_before_methods( $class_name, $method_name );
@@ -93,7 +94,7 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
* @throws Exception
*/
private function add_hacks_from_annotations( $reflection_object ) {
- $annotations = \PHPUnit\Util\Test::parseAnnotations( $reflection_object->getDocComment() );
+ $annotations = Test::parseAnnotations( $reflection_object->getDocComment() );
$hacks_added = false;
foreach ( $annotations as $id => $annotation_instances ) {
@@ -106,6 +107,10 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
$params = $matches[0];
$hack_class = array_shift( $params );
+ if(false === strpos( $hack_class, '\\' )) {
+ $hack_class = __NAMESPACE__ . '\\Hacks\\' . $hack_class;
+ }
+
if ( ! class_exists( $hack_class ) ) {
$original_hack_class = $hack_class;
$hack_class .= 'Hack';
diff --git a/tests/includes/code-hacking/hacks/bypass-finals-hack.php b/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
similarity index 88%
rename from tests/includes/code-hacking/hacks/bypass-finals-hack.php
rename to src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
index b057541fa79..e78c22bd511 100644
--- a/tests/includes/code-hacking/hacks/bypass-finals-hack.php
+++ b/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
@@ -1,6 +1,6 @@
Date: Thu, 16 Apr 2020 15:03:15 +0200
Subject: [PATCH 066/153] Fix code sniffer errors in CodeHacker and related
classes.
---
src/Testing/CodeHacking/CodeHacker.php | 114 +++++++++++++-----
.../CodeHacking/CodeHackerTestHook.php | 17 ++-
.../CodeHacking/Hacks/BypassFinalsHack.php | 9 ++
src/Testing/CodeHacking/Hacks/CodeHack.php | 12 +-
.../CodeHacking/Hacks/FunctionsMockerHack.php | 22 +++-
.../CodeHacking/Hacks/StaticMockerHack.php | 16 ++-
6 files changed, 147 insertions(+), 43 deletions(-)
diff --git a/src/Testing/CodeHacking/CodeHacker.php b/src/Testing/CodeHacking/CodeHacker.php
index d2a10c2d4f9..24f31d04f23 100644
--- a/src/Testing/CodeHacking/CodeHacker.php
+++ b/src/Testing/CodeHacking/CodeHacker.php
@@ -1,7 +1,19 @@
getNumberOfRequiredParameters();
+ }
+
+ private static function is_valid_hack_object( $callback ) {
+ if ( ! is_object( $callback ) ) {
+ return false;
+ }
+
+ $ro = new ReflectionObject( ( $callback ) );
+ try {
+ $rm = $ro->getMethod( 'hack' );
+ return $rm->isPublic() && ! $rm->isStatic() && 2 === $rm->getNumberOfRequiredParameters();
+ } catch ( ReflectionException $exception ) {
+ return false;
+ }
+ }
+
+ /**
+ * Set the white list of files to hack. If note set, all the PHP files will be hacked.
+ *
+ * @param array $path_white_list Paths of the files to hack, can be relative paths.
+ */
+ public static function set_white_list( array $path_white_list ) {
+ self::$path_white_list = $path_white_list;
}
@@ -89,7 +143,7 @@ class CodeHacker {
public function dir_readdir() {
- return readdir( $this->handle );
+ return readdir( $this->handle );
}
@@ -104,8 +158,8 @@ class CodeHacker {
}
- public function rename( $pathFrom, $pathTo ) {
- return $this->native( 'rename', $pathFrom, $pathTo, $this->context );
+ public function rename( $path_from, $path_to ) {
+ return $this->native( 'rename', $path_from, $path_to, $this->context );
}
@@ -114,13 +168,11 @@ class CodeHacker {
}
- public function stream_cast( $castAs ) {
+ public function stream_cast( $cast_as ) {
return $this->handle;
}
-
public function stream_close() {
- // echo "***** CLOSE HANDLE: " . $this->handle . " \n";
fclose( $this->handle );
}
@@ -159,11 +211,11 @@ class CodeHacker {
}
- public function stream_open( $path, $mode, $options, &$openedPath ) {
- $usePath = (bool) ( $options & STREAM_USE_PATH );
- if ( $mode === 'rb' && self::pathInWhitelist( $path ) && pathinfo( $path, PATHINFO_EXTENSION ) === 'php' ) {
- $content = $this->native( 'file_get_contents', $path, $usePath, $this->context );
- if ( $content === false ) {
+ public function stream_open( $path, $mode, $options, &$opened_path ) {
+ $use_path = (bool) ( $options & STREAM_USE_PATH );
+ if ( 'rb' === $mode && self::path_in_white_list( $path ) && 'php' === pathinfo( $path, PATHINFO_EXTENSION ) ) {
+ $content = $this->native( 'file_get_contents', $path, $use_path, $this->context );
+ if ( false === $content ) {
return false;
}
$modified = self::hack( $content, $path );
@@ -175,8 +227,8 @@ class CodeHacker {
}
}
$this->handle = $this->context
- ? $this->native( 'fopen', $path, $mode, $usePath, $this->context )
- : $this->native( 'fopen', $path, $mode, $usePath );
+ ? $this->native( 'fopen', $path, $mode, $use_path, $this->context )
+ : $this->native( 'fopen', $path, $mode, $use_path );
return (bool) $this->handle;
}
@@ -196,17 +248,17 @@ class CodeHacker {
public function stream_stat() {
- return fstat( $this->handle );
+ return fstat( $this->handle );
}
public function stream_tell() {
- return ftell( $this->handle );
+ return ftell( $this->handle );
}
- public function stream_truncate( $newSize ) {
- return ftruncate( $this->handle, $newSize );
+ public function stream_truncate( $new_size ) {
+ return ftruncate( $this->handle, $new_size );
}
@@ -250,15 +302,19 @@ class CodeHacker {
}
- private static function pathInWhitelist( $path ) {
- if ( empty( self::$pathWhitelist ) ) {
+ private static function path_in_white_list( $path ) {
+ if ( empty( self::$path_white_list ) ) {
return true;
}
- foreach ( self::$pathWhitelist as $whitelistItem ) {
- if ( substr( $path, -strlen( $whitelistItem ) ) === $whitelistItem ) {
+ foreach ( self::$path_white_list as $white_list_item ) {
+ if ( substr( $path, -strlen( $white_list_item ) ) === $white_list_item ) {
return true;
}
}
return false;
}
}
+
+//phpcs:enable Squiz.Commenting.FunctionComment.Missing, Squiz.Commenting.VariableComment.Missing
+//phpcs:enable WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors.Discouraged
+
diff --git a/src/Testing/CodeHacking/CodeHackerTestHook.php b/src/Testing/CodeHacking/CodeHackerTestHook.php
index 1f380ce7844..6ff74d99a23 100644
--- a/src/Testing/CodeHacking/CodeHackerTestHook.php
+++ b/src/Testing/CodeHacking/CodeHackerTestHook.php
@@ -1,4 +1,11 @@
getDocComment() );
$hacks_added = false;
foreach ( $annotations as $id => $annotation_instances ) {
- if ( $id !== 'hack' ) {
+ if ( 'hack' !== $id ) {
continue;
}
@@ -107,7 +114,7 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
$params = $matches[0];
$hack_class = array_shift( $params );
- if(false === strpos( $hack_class, '\\' )) {
+ if ( false === strpos( $hack_class, '\\' ) ) {
$hack_class = __NAMESPACE__ . '\\Hacks\\' . $hack_class;
}
@@ -132,7 +139,7 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
*
* @param string $class_name Test class name.
* @param string $method_name Test method name.
- * @throws ReflectionException
+ * @throws ReflectionException Error when instatiating a ReflectionClass.
*/
private function execute_before_methods( $class_name, $method_name ) {
$methods = array( 'before_all', "before_{$method_name}" );
@@ -154,3 +161,5 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
}
}
}
+
+// phpcs:enable Squiz.Commenting.FunctionComment.Missing, PHPCompatibility.FunctionDeclarations
diff --git a/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php b/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
index e78c22bd511..a9d8e97bc6b 100644
--- a/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
+++ b/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
@@ -1,4 +1,11 @@
= 70000 ? token_get_all( $code, TOKEN_PARSE ) : token_get_all( $code );
}
@@ -33,7 +39,7 @@ abstract class CodeHack {
* Check if a token is of a given type.
*
* @param mixed $token Token to check.
- * @param int $type Type of token to check (see https://www.php.net/manual/en/tokens.php)
+ * @param int $type Type of token to check (see https://www.php.net/manual/en/tokens.php).
* @return bool True if it's a token of the given type, false otherwise.
*/
protected function is_token_of_type( $token, $type ) {
@@ -53,7 +59,7 @@ abstract class CodeHack {
/**
* Converts a token to its string representation.
*
- * @param $token Token to convert.
+ * @param mixed $token Token to convert.
* @return mixed String representation of the token.
*/
protected function token_to_string( $token ) {
@@ -70,7 +76,7 @@ abstract class CodeHack {
*/
protected function string_ends_with( $haystack, $needle ) {
$length = strlen( $needle );
- if ( $length == 0 ) {
+ if ( 0 === $length ) {
return true;
}
diff --git a/src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php b/src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php
index 0e8b77c86b3..a6703928bbe 100644
--- a/src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php
+++ b/src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php
@@ -1,4 +1,11 @@
mock_class = $mock_class;
$rc = new ReflectionClass( $mock_class );
@@ -53,14 +65,16 @@ final class FunctionsMockerHack extends CodeHack {
$token_type = $this->token_type_of( $token );
if ( T_WHITESPACE === $token_type ) {
$code .= $this->token_to_string( $token );
- } elseif ( T_STRING === $token_type && ! $previous_token_is_non_global_function_qualifier && in_array( $token[1], $this->mocked_methods ) ) {
+ } elseif ( T_STRING === $token_type && ! $previous_token_is_non_global_function_qualifier && in_array( $token[1], $this->mocked_methods, true ) ) {
$code .= "{$this->mock_class}::{$token[1]}";
} else {
$code .= $this->token_to_string( $token );
- $previous_token_is_non_global_function_qualifier = in_array( $token_type, self::$non_global_function_tokens );
+ $previous_token_is_non_global_function_qualifier = in_array( $token_type, self::$non_global_function_tokens, true );
}
}
return $code;
}
}
+
+// phpcs:enable Squiz.Commenting.FunctionComment.Missing
diff --git a/src/Testing/CodeHacking/Hacks/StaticMockerHack.php b/src/Testing/CodeHacking/Hacks/StaticMockerHack.php
index d334a2fc8c3..3c066808a7c 100644
--- a/src/Testing/CodeHacking/Hacks/StaticMockerHack.php
+++ b/src/Testing/CodeHacking/Hacks/StaticMockerHack.php
@@ -1,4 +1,11 @@
source_class = $source_class;
$this->target_class = $mock_class;
@@ -61,12 +68,13 @@ final class StaticMockerHack extends CodeHack {
$code = '';
$current_token = null;
+ // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
while ( $current_token = current( $tokens ) ) {
if ( $this->is_token_of_type( $current_token, T_STRING ) && $this->source_class === $current_token[1] ) {
$next_token = next( $tokens );
if ( $this->is_token_of_type( $next_token, T_DOUBLE_COLON ) ) {
$called_member = next( $tokens )[1];
- if ( in_array( $called_member, $this->members_implemented_in_mock ) ) {
+ if ( in_array( $called_member, $this->members_implemented_in_mock, true ) ) {
// Reference to source class member that exists in mock class, replace.
$code .= "{$this->target_class}::{$called_member}";
} else {
@@ -88,3 +96,5 @@ final class StaticMockerHack extends CodeHack {
return $code;
}
}
+
+// phpcs:enable Squiz.Commenting.FunctionComment.Missing
From c7cf39bef4264aca77a1adb8bb74b83fb0f29bfd Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Thu, 16 Apr 2020 15:54:31 +0200
Subject: [PATCH 067/153] Add a README file for the code hacker.
---
src/Testing/CodeHacking/README.md | 141 ++++++++++++++++++++++++++++++
1 file changed, 141 insertions(+)
create mode 100644 src/Testing/CodeHacking/README.md
diff --git a/src/Testing/CodeHacking/README.md b/src/Testing/CodeHacking/README.md
new file mode 100644
index 00000000000..47a54e735b8
--- /dev/null
+++ b/src/Testing/CodeHacking/README.md
@@ -0,0 +1,141 @@
+# Code Hacking
+
+Code hacking is a mechanism that allows to temporarily modifying PHP code files while they are loaded. It's intended to ease unit testing code that would otherwise be very difficult or impossible to test (and **only** for this - see [An important note](#an-important-note) about that).
+
+The code hacker consists of the following classes:
+
+ * `CodeHacker`: the core class that performs the hacking and has some public configuration and activation methods.
+ * `CodeHackerTestHook`: a PHPUnit hook class that wires everything so that the code hacking mechanism can be used within unit tests.
+ * Hack classes inside the `Hack` folders: some predefined frequently used hacks.
+
+## How to use
+
+Let's go through an example.
+
+First, create a file named `class-wc-admin-hello-worlder` in `includes/admin/class-wc-admin-hello-worlder.php` with the following code:
+
+```
+say_hello( 'Nestor' );
+
+ $this->assertEquals( 'Hello Nestor, welcome to MSX world!', $actual );
+ $this->assertEquals( 'site_name', FunctionsMock::$_option_requested );
+ $this->assertEquals( 'Nestor', LoggerMock::$_logged );
+ }
+}
+```
+
+Then run `vendor/bin/phpunit tests/unit-tests/admin/class-wc-tests-admin-hello-worlder.php` and see the magic happen. Note that this works because `CodeHackerTestHook` has been registered in `phpunit.xml`.
+
+As you can see, the basic mechanism consists of creating a `public static before_[test_method_name]` method in the test class, where you register whatever hacks you need with `CodeHacker::add_hack` and finally you invoke `CodeHacker::enable()` to enable the hacking. `StaticMockerHack` and `FunctionsMockerHack` are two of the predefined hack classes inside the `Hack` folder, see their source code for details on how they work and how to use them.
+
+You can define a `before_all` method too, which will run before all the tests (and before the `before_[test_method_name]` method).
+
+You might be asking why special `before_` are required if we already have PHPUnit's `setUp` method. The answer is: by the time `setUp` runs the code files to test are already loaded, so there's no way to hack them.
+
+Alternatively, hacks can be defined using class and method annotations as well (with the added bonus that you don't need the `use` statements anymore):
+
+```
+/**
+ * @hack StaticMocker Logger LoggerMock
+ */
+class WC_Tests_Admin_Hello_Worlder extends WC_Unit_Test_Case {
+
+ /**
+ * @hack FunctionsMocker FunctionsMock
+ * @hack BypassFinals
+ */
+ public function test_say_hello() {
+ ...
+ }
+}
+```
+
+The syntax is `@hack HackClassName [hack class constructor parameters]`. Important bits:
+
+* Specify constructor parameters after the class name. If a parameter has a space, enclose it in quotation marks, `""`.
+* If the hack class is inside the `Automattic\WooCommerce\Testing\CodeHacking\Hacks` namespace you don't need to specify the namespace.
+* If the hack class name has the `Hack` suffix you can omit the suffix (e.g. `@hack FunctionsMocker` for the `FunctionsMockerHack` class).
+* If the annotation is applied to the test class definition the hack will be applied to all the tests within the class.
+* You don't need to `CodeHacker::enable()` when using `@hack`, this will be done for you.
+
+## Creating new hacks
+
+New hacks can be created and used the same way as the predefined ones. A hack is defined as one of these:
+
+* A function with the signature `hack($code, $path)`.
+* An object containing a `public function hack($code, $path)`.
+
+The `hack` function/method receives a string with the entire contents of the code file in `$code`, and the full path of the code file in `$path`. It must return a string with the hacked file contents (or, if no hacking is required, the unmodified value of `$code`).
+
+There's a `CodeHack` abstract class inside the `Hacks` directory that can be useful to develop new hacks, but that's provided just for convenience and it's not mandatory to use it (any class with a proper `hack` method will work).
+
+## How it works under the hood
+
+The Code Hacker is based on [the Bypass Finals project](https://github.com/dg/bypass-finals) by David Grudl.
+
+The `CodeHacker` class is actually [a streamWrapper class](https://www.php.net/manual/en/class.streamwrapper.php) for the regular filesystem. Most of its methods are just short-circuited to the regular PHP filesystem handling functions, but the `stream_open` method contains some code that allows the magic to happen. What it does (for PHP files only) is to read the file contents and apply all the hacks to it, then if the code has been modified it is stored in a temporary file which is then the one that receives any further filesystem operations instead of the original file. That way, for all practical purposes the content of the file is the "hacked" content.
+
+The `CodeHackerTestHook` then uses reflection to find `before_` methods and `@hack` anotations, putting everything in place right before the tests are executed.
+
+## Note on `copy` in tests
+
+For some reason tests using `copy` to copy files will fail if the code hacker is active. As a workaround, the new `file_copy` method defined in `WC_Unit_Test_Case` should be used instead (it temporarily disables the code hacker and then performs the copy operation). This is something to investigate.
+
+## An important note
+
+The code hacker is intended to be a **last resort** mechanism to test stuff that it's **really** difficult or impossible to test otherwise - the mechanisms already in place to help testing (e.g. the PHPUnit's mocks or the Woo helpers) should still be used whenever possible. And of course, the code hacker should not be an excuse to write code that's difficult to test.
From 884fd084623dbd0800c2bfc33447b5cc0d12653c Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Mon, 20 Apr 2020 15:59:45 +0200
Subject: [PATCH 068/153] Add a workaround for code hacking static methods on
already loaded files.
The unit testing bootstrap loads and initializes WooCommerce, this
loads a bunch of code files that can't then be hacked in the test hooks.
A workaround is provided in this commit for the case of hacking
static methods. A new StaticWrapper class is created that allows
defining mock methods after the code file has been loaded.
This is applied to all classes from a fixed list in the bootstrap,
before WooCommerce is initialized. The list should be kept up to date
with the list of classes that require such workaround.
---
.../CodeHacking/Hacks/StaticMockerHack.php | 18 +-
src/Testing/CodeHacking/README.md | 34 ++++
src/Testing/CodeHacking/StaticWrapper.php | 156 ++++++++++++++++++
tests/legacy/bootstrap.php | 23 ++-
.../classes-that-need-static-wrapper.php | 18 ++
5 files changed, 244 insertions(+), 5 deletions(-)
create mode 100644 src/Testing/CodeHacking/StaticWrapper.php
create mode 100644 tests/legacy/classes-that-need-static-wrapper.php
diff --git a/src/Testing/CodeHacking/Hacks/StaticMockerHack.php b/src/Testing/CodeHacking/Hacks/StaticMockerHack.php
index 3c066808a7c..99fc53915e4 100644
--- a/src/Testing/CodeHacking/Hacks/StaticMockerHack.php
+++ b/src/Testing/CodeHacking/Hacks/StaticMockerHack.php
@@ -25,20 +25,32 @@ use ReflectionClass;
* Invocations of public static members from the original class that exist in the mock class
* will be replaced with invocations of the same members for the mock class.
* Invocations of members not existing in the mock class will be left unmodified.
+ *
+ * If the mock class defines a __callStatic method you can pass the $replace_always constructor argument
+ * as true.
*/
final class StaticMockerHack extends CodeHack {
+ // phpcs:ignore Squiz.Commenting.VariableComment.Missing
+ private $replace_always = false;
+
/**
* StaticMockerHack constructor.
*
* @param string $source_class Name of the original class (the one having the members to be mocked).
* @param string $mock_class Name of the mock class (the one having the replacement mock members).
+ * @param bool $replace_always If true, all method invocations will be always be redirected to the mock class.
* @throws ReflectionException Error when instantiating ReflectionClass.
*/
- public function __construct( $source_class, $mock_class ) {
+ public function __construct( $source_class, $mock_class, $replace_always = false ) {
$this->source_class = $source_class;
$this->target_class = $mock_class;
+ if ( $replace_always ) {
+ $this->replace_always = true;
+ return;
+ }
+
$rc = new ReflectionClass( $mock_class );
$static_methods = $rc->getMethods( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC );
@@ -74,8 +86,8 @@ final class StaticMockerHack extends CodeHack {
$next_token = next( $tokens );
if ( $this->is_token_of_type( $next_token, T_DOUBLE_COLON ) ) {
$called_member = next( $tokens )[1];
- if ( in_array( $called_member, $this->members_implemented_in_mock, true ) ) {
- // Reference to source class member that exists in mock class, replace.
+ if ( $this->replace_always || in_array( $called_member, $this->members_implemented_in_mock, true ) ) {
+ // Reference to source class member that exists in mock class, or replace always requested: replace.
$code .= "{$this->target_class}::{$called_member}";
} else {
// Reference to source class member that does NOT exists in mock class, leave unchanged.
diff --git a/src/Testing/CodeHacking/README.md b/src/Testing/CodeHacking/README.md
index 47a54e735b8..a4f2e17dca6 100644
--- a/src/Testing/CodeHacking/README.md
+++ b/src/Testing/CodeHacking/README.md
@@ -132,6 +132,40 @@ The `CodeHacker` class is actually [a streamWrapper class](https://www.php.net/m
The `CodeHackerTestHook` then uses reflection to find `before_` methods and `@hack` anotations, putting everything in place right before the tests are executed.
+## Workaround for static mocking of already loaded code: the `StaticWrapper`
+
+Before the test hooks that configure the code hacker run there's already a significant amount of code files already loaded, as a result of WooCommerce being loaded and initialized within the unit tests bootstrap files. These code file can't be hacked using the described approach, and therefore require to _hack the hack_.
+
+A workaround for a particular case is provided. If you need to register a `StaticMockerHack` for a class that has been already loaded (you will notice because registering the hack the usual way does nothing), do the following instead:
+
+1. Add the class name (NOT the file name) to the array returned by `tests/classes-that-need-static-wrapper.php`.
+
+2. Configure the mock using `StaticWrapper::set_mock_class_for`.
+
+```
+class WC_Tests_Admin_Hello_Worlder extends WC_Unit_Test_Case {
+
+ public function test_say_hello() {
+ FunctionsMock::$_option_value = 'MSX world';
+
+ StaticWrapper::set_mock_class_for('Logger', 'LoggerMock');
+
+ $sut = new WC_Admin_Hello_Worlder();
+ $actual = $sut->say_hello( 'Nestor' );
+
+ $this->assertEquals( 'Hello Nestor, welcome to MSX world!', $actual );
+ $this->assertEquals( 'site_name', FunctionsMock::$_option_requested );
+ $this->assertEquals( 'Nestor', LoggerMock::$_logged );
+ }
+}
+```
+
+Note that in this case you don't explicitly interact with the code hacker, neither directly nor by using `@hack` annotations.
+
+Under the hood this is hacking all the classes in the list with a "clean" `StaticWrapper`-derived class; here "clean" means that all static methods are redirected to the original class via `__callStatic`. This hacking happens at the beginning of the bootstrapping process, when nothing from WooCommerce has been loaded yet. Later on `StaticWrapper::set_mock_class_for` can be used at any time to selectively mock any static method in the class.
+
+Alternatively, you can configure mock functions instead of a mock class. See the source of `StaticWrapper` for more details.
+
## Note on `copy` in tests
For some reason tests using `copy` to copy files will fail if the code hacker is active. As a workaround, the new `file_copy` method defined in `WC_Unit_Test_Case` should be used instead (it temporarily disables the code hacker and then performs the copy operation). This is something to investigate.
diff --git a/src/Testing/CodeHacking/StaticWrapper.php b/src/Testing/CodeHacking/StaticWrapper.php
new file mode 100644
index 00000000000..fff5e72cf6b
--- /dev/null
+++ b/src/Testing/CodeHacking/StaticWrapper.php
@@ -0,0 +1,156 @@
+ function(...);
+ * );
+ *
+ * StaticWrapper::set_mock_functions_for('MyClass', $functions);
+ *
+ * You can use 2 and 3 at the same time, functions have precedence over mock class methods in case of conflict.
+ *
+ * @package Automattic\WooCommerce\Testing\CodeHacking
+ */
+abstract class StaticWrapper {
+
+ // phpcs:disable Squiz.Commenting.VariableComment.Missing
+ protected static $mock_class_name = null;
+ protected static $methods_in_mock_class = array();
+ protected static $mocking_functions = array();
+
+ private static $wrapper_suffix = '_Wrapper';
+ // phpcs:enable Squiz.Commenting.VariableComment.Missing
+
+ /**
+ * Define a new class that inherits from StaticWrapper, to be used as a static wrapper for a given class.
+ *
+ * @param string $class_name Class for which the static wrapper will be generated.
+ *
+ * @return string Name of the generated class.
+ */
+ public static function define_for( $class_name ) {
+ $wrapper_name = self::wrapper_for( $class_name );
+ // phpcs:ignore Squiz.PHP.Eval.Discouraged
+ eval( 'class ' . $wrapper_name . ' extends ' . __CLASS__ . ' {}' );
+ return $wrapper_name;
+ }
+
+ /**
+ * Returns the name of the wrapper class that define_for will create for a given class.
+ *
+ * @param string $class_name Class to return the wrapper name for.
+ *
+ * @return string Name of the class that define_for defines.
+ */
+ public static function wrapper_for( $class_name ) {
+ return $class_name . self::$wrapper_suffix;
+ }
+
+ /**
+ * Registers the class that will be used to mock the original class.
+ * Static methods called in the wrapper class that also exist in the mock class will be redirected
+ * to the mock class.
+ *
+ * @param string $mock_class_name Mock class whose methods will be invoked.
+ *
+ * @throws \ReflectionException Error when creating ReflectionClass or ReflectionMethod.
+ */
+ public static function set_mock_class( $mock_class_name ) {
+ self::$mock_class_name = $mock_class_name;
+
+ $rc = new ReflectionClass( $mock_class_name );
+
+ $static_methods = $rc->getMethods( ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC );
+ $static_methods = array_map(
+ function( $item ) {
+ return $item->getName();
+ },
+ $static_methods
+ );
+
+ self::$methods_in_mock_class = $static_methods;
+ }
+
+ /**
+ * This method is the same as set_mock_class, but it's intended to be invoked directly on StaticWrapper,
+ * passing the original class name as a parameter.
+ *
+ * @param string $class_name Class name whose set_mock_class method will be invoked.
+ * @param string $mock_class_name Parameter that will be passed to set_mock_class method in $class_name.
+ */
+ public static function set_mock_class_for( $class_name, $mock_class_name ) {
+ ( self::wrapper_for( $class_name ) )::set_mock_class( $mock_class_name );
+ }
+
+ /**
+ * Registers an array of functions that will be used to mock the original class. It must be an associative
+ * array of name => function. Static methods called in the wrapper class having a corresponding key in the array
+ * will be redirected to the corresponding function.
+ *
+ * @param array $mocking_functions Associative array where keys are method names and values are functions.
+ */
+ public static function set_mock_functions( $mocking_functions ) {
+ self::$mocking_functions = $mocking_functions;
+ }
+
+ /**
+ * This method is the same as set_mock_functions, but it's intended to be invoked directly on StaticWrapper,
+ * passing the original class name as a parameter.
+ *
+ * @param string $class_name Class name whose set_mock_functions method will be invoked.
+ * @param array $mocking_functions Parameter that will be passed to set_mock_functions method in $class_name.
+ */
+ public static function set_mock_functions_for( $class_name, $mocking_functions ) {
+ ( self::wrapper_for( $class_name ) )::set_mock_functions( $mocking_functions );
+ }
+
+ /**
+ * Intercepts calls to undefined static methods in the wrapper and redirects them to the mock class/function
+ * if available, or to the original class otherwise.
+ *
+ * @param string $name Method name.
+ * @param array $arguments Method arguments.
+ *
+ * @return mixed Return value from the invoked method.
+ */
+ public static function __callStatic( $name, $arguments ) {
+ if ( array_key_exists( $name, self::$mocking_functions ) ) {
+ return call_user_func( self::$mocking_functions[ $name ], ...$arguments );
+ } elseif ( ! is_null( self::$mock_class_name ) && in_array( $name, self::$methods_in_mock_class, true ) ) {
+ return ( self::$mock_class_name )::$name( ...$arguments );
+ } else {
+ $original_class_name = preg_replace( '/' . self::$wrapper_suffix . '$/', '', get_called_class() );
+ return $original_class_name::$name( ...$arguments );
+ }
+ }
+}
diff --git a/tests/legacy/bootstrap.php b/tests/legacy/bootstrap.php
index c0a91df0ec3..79c31aaca88 100644
--- a/tests/legacy/bootstrap.php
+++ b/tests/legacy/bootstrap.php
@@ -6,6 +6,10 @@
* @package WooCommerce Tests
*/
+use Automattic\WooCommerce\Testing\CodeHacking\CodeHacker;
+use Automattic\WooCommerce\Testing\CodeHacking\StaticWrapper;
+use Automattic\WooCommerce\Testing\CodeHacking\Hacks\StaticMockerHack;
+
/**
* Class WC_Unit_Tests_Bootstrap
*/
@@ -30,6 +34,23 @@ class WC_Unit_Tests_Bootstrap {
*/
public function __construct() {
+ $this->tests_dir = dirname( __FILE__ );
+ $this->plugin_dir = dirname( dirname( $this->tests_dir ) );
+
+ $hacking_base = $this->plugin_dir . '/src/Testing/CodeHacking';
+ require_once $hacking_base . '/StaticWrapper.php';
+ require_once $hacking_base . '/CodeHacker.php';
+ require_once $hacking_base . '/Hacks/CodeHack.php';
+ require_once $hacking_base . '/Hacks/StaticMockerHack.php';
+
+ // Define a static wrapper for all the classes that need it.
+ $classes_that_need_static_wrapper = include_once __DIR__ . '/classes-that-need-static-wrapper.php';
+ foreach ( $classes_that_need_static_wrapper as $class ) {
+ $wrapper_class = StaticWrapper::define_for( $class );
+ CodeHacker::add_hack( new StaticMockerHack( $class, $wrapper_class, true ) );
+ }
+ CodeHacker::enable();
+
ini_set( 'display_errors', 'on' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Blacklisted
error_reporting( E_ALL ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting, WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
@@ -40,8 +61,6 @@ class WC_Unit_Tests_Bootstrap {
}
// phpcs:enable WordPress.VIP.SuperGlobalInputUsage.AccessDetected
- $this->tests_dir = dirname( __FILE__ );
- $this->plugin_dir = dirname( dirname( $this->tests_dir ) );
$this->wp_tests_dir = getenv( 'WP_TESTS_DIR' ) ? getenv( 'WP_TESTS_DIR' ) : sys_get_temp_dir() . '/wordpress-tests-lib';
// load test function so tests_add_filter() is available.
diff --git a/tests/legacy/classes-that-need-static-wrapper.php b/tests/legacy/classes-that-need-static-wrapper.php
new file mode 100644
index 00000000000..be043a20432
--- /dev/null
+++ b/tests/legacy/classes-that-need-static-wrapper.php
@@ -0,0 +1,18 @@
+
Date: Thu, 23 Apr 2020 16:21:46 +0200
Subject: [PATCH 069/153] Add function docs to the CodeHacker class.
---
src/Testing/CodeHacking/CodeHacker.php | 242 ++++++++++++++++++++++---
1 file changed, 215 insertions(+), 27 deletions(-)
diff --git a/src/Testing/CodeHacking/CodeHacker.php b/src/Testing/CodeHacking/CodeHacker.php
index 24f31d04f23..328720e374b 100644
--- a/src/Testing/CodeHacking/CodeHacker.php
+++ b/src/Testing/CodeHacking/CodeHacker.php
@@ -5,7 +5,6 @@
* @package WooCommerce/Testing
*/
-//phpcs:disable Squiz.Commenting.FunctionComment.Missing, Squiz.Commenting.VariableComment.Missing
//phpcs:disable WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors.Discouraged
namespace Automattic\WooCommerce\Testing\CodeHacking;
@@ -36,14 +35,39 @@ class CodeHacker {
const PROTOCOL = 'file';
+ /**
+ * Value of "context" parameter to be passed to the native PHP filesystem related functions.
+ *
+ * @var mixed
+ */
public $context;
+ /**
+ * File handle of the file that is open.
+ *
+ * @var mixed
+ */
private $handle;
+ /**
+ * Optional white list of files to hack, if empty all the files will be hacked.
+ *
+ * @var array
+ */
private static $path_white_list = array();
+ /**
+ * Registered hacks.
+ *
+ * @var array
+ */
private static $hacks = array();
+ /**
+ * Is the code hacker enabled?.
+ *
+ * @var bool
+ */
private static $enabled = false;
/**
@@ -101,10 +125,25 @@ class CodeHacker {
self::$hacks[] = $hack;
}
+ /**
+ * Check if the supplied argument is a valid hack callback (has two mandatory arguments).
+ *
+ * @param mixed $callback Argument to check.
+ *
+ * @return bool true if the argument is a valid hack callback, false otherwise.
+ * @throws ReflectionException Error when instantiating ReflectionFunction.
+ */
private static function is_valid_hack_callback( $callback ) {
return is_callable( $callback ) && 2 === ( new ReflectionFunction( $callback ) )->getNumberOfRequiredParameters();
}
+ /**
+ * Check if the supplied argument is a valid hack object (has a public "hack" method with two mandatory arguments).
+ *
+ * @param mixed $callback Argument to check.
+ *
+ * @return boolt rue if the argument is a valid hack object, false otherwise.
+ */
private static function is_valid_hack_object( $callback ) {
if ( ! is_object( $callback ) ) {
return false;
@@ -128,12 +167,21 @@ class CodeHacker {
self::$path_white_list = $path_white_list;
}
-
+ /**
+ * Close directory handle.
+ */
public function dir_closedir() {
closedir( $this->handle );
}
-
+ /**
+ * Open directory handle.
+ *
+ * @param string $path Specifies the URL that was passed to opendir().
+ * @param int $options Whether or not to enforce safe_mode (0x04).
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function dir_opendir( $path, $options ) {
$this->handle = $this->context
? $this->native( 'opendir', $path, $this->context )
@@ -141,59 +189,120 @@ class CodeHacker {
return (bool) $this->handle;
}
-
+ /**
+ * Read entry from directory handle.
+ *
+ * @return false|string string representing the next filename, or FALSE if there is no next file.
+ */
public function dir_readdir() {
return readdir( $this->handle );
}
-
+ /**
+ * Rewind directory handle.
+ *
+ * @return TRUE on success or FALSE on failure.
+ */
public function dir_rewinddir() {
return rewinddir( $this->handle );
}
-
+ /**
+ * Create a directory.
+ *
+ * @param string $path Directory which should be created.
+ * @param int $mode The value passed to mkdir().
+ * @param int $options A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE.
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function mkdir( $path, $mode, $options ) {
$recursive = (bool) ( $options & STREAM_MKDIR_RECURSIVE );
return $this->native( 'mkdir', $path, $mode, $recursive, $this->context );
}
-
+ /**
+ * Renames a file or directory.
+ *
+ * @param string $path_from The URL to the current file.
+ * @param string $path_to The URL which the path_from should be renamed to.
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function rename( $path_from, $path_to ) {
return $this->native( 'rename', $path_from, $path_to, $this->context );
}
-
+ /**
+ * Removes a directory.
+ *
+ * @param string $path The directory URL which should be removed.
+ * @param int $options A bitwise mask of values, such as STREAM_MKDIR_RECURSIVE.
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function rmdir( $path, $options ) {
return $this->native( 'rmdir', $path, $this->context );
}
-
+ /**
+ * Retrieve the underlying resource.
+ *
+ * @param mixed $cast_as Can be STREAM_CAST_FOR_SELECT when stream_select() is calling stream_cast() or STREAM_CAST_AS_STREAM when stream_cast() is called for other uses.
+ *
+ * @return mixed The underlying stream resource used by the wrapper, or FALSE.
+ */
public function stream_cast( $cast_as ) {
return $this->handle;
}
+ /**
+ * Close a resource.
+ */
public function stream_close() {
fclose( $this->handle );
}
-
+ /**
+ * Tests for end-of-file on a file pointer.
+ *
+ * @return bool TRUE if the read/write position is at the end of the stream and if no more data is available to be read, or FALSE otherwise.
+ */
public function stream_eof() {
return feof( $this->handle );
}
-
+ /**
+ * Flushes the output.
+ *
+ * @return bool TRUE if the cached data was successfully stored (or if there was no data to store), or FALSE if the data could not be stored.
+ */
public function stream_flush() {
return fflush( $this->handle );
}
-
+ /**
+ * Advisory file locking.
+ *
+ * @param int $operation LOCK_SH, LOCK_EX, LOCK_UN, or LOCK_NB.
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function stream_lock( $operation ) {
return $operation
? flock( $this->handle, $operation )
: true;
}
-
+ /**
+ * Change stream metadata.
+ *
+ * @param string $path The file path or URL to set metadata. Note that in the case of a URL, it must be a :// delimited URL. Other URL forms are not supported.
+ * @param int $option STREAM_META_TOUCH, STREAM_META_OWNER_NAME, STREAM_META_OWNER, STREAM_META_GROUP_NAME, STREAM_META_GROUP, or STREAM_META_ACCESS.
+ * @param mixed $value Depends on $option.
+ *
+ * @return bool TRUE on success or FALSE on failure. If option is not implemented, FALSE should be returned.
+ */
public function stream_metadata( $path, $option, $value ) {
switch ( $option ) {
case STREAM_META_TOUCH:
@@ -210,7 +319,16 @@ class CodeHacker {
}
}
-
+ /**
+ * Opens file or URL. Note that this is where the hacking actually happens.
+ *
+ * @param string $path Specifies the URL that was passed to the original function.
+ * @param string $mode The mode used to open the file, as detailed for fopen().
+ * @param int $options Holds additional flags set by the streams API: STREAM_USE_PATH, STREAM_REPORT_ERRORS.
+ * @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options, opened_path should be set to the full path of the file/resource that was actually opened.
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function stream_open( $path, $mode, $options, &$opened_path ) {
$use_path = (bool) ( $options & STREAM_USE_PATH );
if ( 'rb' === $mode && self::path_in_white_list( $path ) && 'php' === pathinfo( $path, PATHINFO_EXTENSION ) ) {
@@ -232,46 +350,98 @@ class CodeHacker {
return (bool) $this->handle;
}
-
+ /**
+ * Read from stream.
+ *
+ * @param int $count How many bytes of data from the current position should be returned.
+ *
+ * @return false|string If there are less than count bytes available, return as many as are available. If no more data is available, return either FALSE or an empty string.
+ */
public function stream_read( $count ) {
return fread( $this->handle, $count );
}
-
+ /**
+ * Seeks to specific location in a stream.
+ *
+ * @param int $offset The stream offset to seek to.
+ * @param int $whence SEEK_SET, SEEK_CUR, or SEEK_END.
+ *
+ * @return bool TRUE if the position was updated, FALSE otherwise.
+ */
public function stream_seek( $offset, $whence = SEEK_SET ) {
return fseek( $this->handle, $offset, $whence ) === 0;
}
-
+ /**
+ * Change stream options.
+ *
+ * @param int $option STREAM_OPTION_BLOCKING, STREAM_OPTION_READ_TIMEOUT, or STREAM_OPTION_WRITE_BUFFER.
+ * @param int $arg1 Depends on $option.
+ * @param int $arg2 Depends on $option.
+ */
public function stream_set_option( $option, $arg1, $arg2 ) {
}
-
+ /**
+ * Retrieve information about a file resource.
+ *
+ * @return array See stat().
+ */
public function stream_stat() {
return fstat( $this->handle );
}
-
+ /**
+ * Retrieve the current position of a stream.
+ *
+ * @return false|int The current position of the stream.
+ */
public function stream_tell() {
return ftell( $this->handle );
}
-
+ /**
+ * Truncate stream.
+ *
+ * @param int $new_size The new size.
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function stream_truncate( $new_size ) {
return ftruncate( $this->handle, $new_size );
}
-
+ /**
+ * Write to stream.
+ *
+ * @param string $data Should be stored into the underlying stream.
+ *
+ * @return false|int The number of bytes that were successfully stored, or 0 if none could be stored.
+ */
public function stream_write( $data ) {
return fwrite( $this->handle, $data );
}
-
+ /**
+ * Delete a file.
+ *
+ * @param string $path The file URL which should be deleted.
+ *
+ * @return bool TRUE on success or FALSE on failure.
+ */
public function unlink( $path ) {
return $this->native( 'unlink', $path );
}
-
+ /**
+ * Retrieve information about a file.
+ *
+ * @param string $path The file path or URL to stat. Note that in the case of a URL, it must be a :// delimited URL. Other URL forms are not supported.
+ * @param int $flags Holds additional flags set by the streams API. It can hold one or more of the following values OR'd together.
+ *
+ * @return mixed Should return as many elements as stat() does. Unknown or unavailable values should be set to a rational value (usually 0). Pay special attention to mode as documented under stat().
+ */
public function url_stat( $path, $flags ) {
$func = $flags & STREAM_URL_STAT_LINK ? 'lstat' : 'stat';
return $flags & STREAM_URL_STAT_QUIET
@@ -279,7 +449,13 @@ class CodeHacker {
: $this->native( $func, $path );
}
-
+ /**
+ * Executes a native PHP function.
+ *
+ * @param string $func Name of the function to execute. Pass the arguments for the PHP function after this one.
+ *
+ * @return mixed Return value from the native PHP function.
+ */
private function native( $func ) {
stream_wrapper_restore( self::PROTOCOL );
$res = call_user_func_array( $func, array_slice( func_get_args(), 1 ) );
@@ -288,7 +464,14 @@ class CodeHacker {
return $res;
}
-
+ /**
+ * Apply the reigstered hacks to the contents of a file.
+ *
+ * @param string $code Code content to hack.
+ * @param string $path Path of the file being hacked.
+ *
+ * @return string The code after applying all the registered hacks.
+ */
private static function hack( $code, $path ) {
foreach ( self::$hacks as $hack ) {
if ( is_callable( $hack ) ) {
@@ -301,7 +484,13 @@ class CodeHacker {
return $code;
}
-
+ /**
+ * Check if a file path is in the white list.
+ *
+ * @param string $path File path to check.
+ *
+ * @return bool TRUE if there's an entry in the white list that ends with $path, FALSE otherwise.
+ */
private static function path_in_white_list( $path ) {
if ( empty( self::$path_white_list ) ) {
return true;
@@ -315,6 +504,5 @@ class CodeHacker {
}
}
-//phpcs:enable Squiz.Commenting.FunctionComment.Missing, Squiz.Commenting.VariableComment.Missing
//phpcs:enable WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors.Discouraged
From 6f2e0bf6949cb0ba7cb452729988f1409f7594e5 Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Fri, 24 Apr 2020 08:57:55 +0200
Subject: [PATCH 070/153] Improve error messaging in
WC_Tests_MaxMind_Database::test_download_database_works
---
.../maxmind-geolocation/class-wc-tests-maxmind-database.php | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php b/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
index 6dddc237565..c37a87c66f5 100644
--- a/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
+++ b/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
@@ -58,6 +58,9 @@ class WC_Tests_MaxMind_Database extends WC_Unit_Test_Case {
$expected_database = sys_get_temp_dir() . '/GeoLite2-Country_20200100/GeoLite2-Country.mmdb';
$result = $database_service->download_database( 'testing_license' );
+ if ( is_wp_error( $result ) ) {
+ $this->fail( $result->get_error_message() );
+ }
$this->assertEquals( $expected_database, $result );
From 8a7d9552537146d3dd48d3377e1b54509e61a74f Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Wed, 6 May 2020 14:40:17 +0200
Subject: [PATCH 071/153] Improvements on the code hacker.
- Add methods to temporarily disable and reenable the code hacker.
The code hacker is causing issues in some tests that perform
write operations to the local filesystem. Since this happens only
in a few cases, the easiest fix is to temporarily disable the
code hacker when that happens. This commit adds two new methods
for that in `WC_Unit_Test_Case`: `disable_code_hacker` and
`reenable_code_hacker`.
These methods use a disabling requests count so that the hacker
isn't enabled before it should. E.g. you call `disable`, then
a helper method that does `disable` and `enable`, then `enable` -
then only the last `enable` will have effect.
- `CodeHacker::add_hack` has now a boolean `persistent` parameter.
Persistent hacks won't be cleared by `clear_hacks`.
- `CodeHackerTestHook::executeAfterTest` will now disable the hacker
only if no persistent hacks are registered.
- The existing `file_copy` method is made static for consistency.
- `CodeHacker::restore` method renamed to `disable` for clarity.
---
src/Testing/CodeHacking/CodeHacker.php | 28 ++++++++++--
.../CodeHacking/CodeHackerTestHook.php | 6 ++-
src/Testing/CodeHacking/README.md | 6 ++-
tests/legacy/bootstrap.php | 2 +-
.../framework/class-wc-unit-test-case.php | 45 +++++++++++++++----
tests/legacy/unit-tests/importer/product.php | 4 +-
.../class-wc-tests-maxmind-database.php | 4 +-
.../legacy/unit-tests/util/api-functions.php | 4 +-
8 files changed, 76 insertions(+), 23 deletions(-)
diff --git a/src/Testing/CodeHacking/CodeHacker.php b/src/Testing/CodeHacking/CodeHacker.php
index 328720e374b..967c5cdd8a9 100644
--- a/src/Testing/CodeHacking/CodeHacker.php
+++ b/src/Testing/CodeHacking/CodeHacker.php
@@ -63,6 +63,13 @@ class CodeHacker {
*/
private static $hacks = array();
+ /**
+ * Registered persistent hacks.
+ *
+ * @var array
+ */
+ private static $persistent_hacks = array();
+
/**
* Is the code hacker enabled?.
*
@@ -84,7 +91,7 @@ class CodeHacker {
/**
* Disable the code hacker.
*/
- public static function restore() {
+ public static function disable() {
if ( self::$enabled ) {
stream_wrapper_restore( self::PROTOCOL );
self::$enabled = false;
@@ -92,10 +99,10 @@ class CodeHacker {
}
/**
- * Unregister all the registered hacks.
+ * Unregister all the non-persistent registered hacks.
*/
public static function clear_hacks() {
- self::$hacks = array();
+ self::$hacks = self::$persistent_hacks;
}
/**
@@ -107,13 +114,23 @@ class CodeHacker {
return self::$enabled;
}
+ /**
+ * Check if persistent hacks have been registered.
+ *
+ * @return bool True if persistent hacks have been registered.
+ */
+ public static function has_persistent_hacks() {
+ return count( self::$persistent_hacks ) > 0;
+ }
+
/**
* Register a new hack.
*
* @param mixed $hack A function with signature "hack($code, $path)" or an object containing a method with that signature.
+ * @param bool $persistent If true, the hack will be registered as persistent (so that clear_hacks will not clear it).
* @throws \Exception Invalid input.
*/
- public static function add_hack( $hack ) {
+ public static function add_hack( $hack, $persistent = false ) {
if ( ! is_callable( $hack ) && ! is_object( $hack ) ) {
throw new \Exception( "Hacks must be either functions, or objects having a 'process(\$text, \$path)' method." );
}
@@ -122,6 +139,9 @@ class CodeHacker {
throw new \Exception( "CodeHacker::addhack: hacks must be either a function with a 'hack(\$code,\$path)' signature, or an object containing a public method 'hack' with that signature. " );
}
+ if ( $persistent ) {
+ self::$persistent_hacks[] = $hack;
+ }
self::$hacks[] = $hack;
}
diff --git a/src/Testing/CodeHacking/CodeHackerTestHook.php b/src/Testing/CodeHacking/CodeHackerTestHook.php
index 6ff74d99a23..0d96a2515ba 100644
--- a/src/Testing/CodeHacking/CodeHackerTestHook.php
+++ b/src/Testing/CodeHacking/CodeHackerTestHook.php
@@ -65,7 +65,9 @@ use Exception;
final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
public function executeAfterTest( string $test, float $time ): void {
- CodeHacker::restore();
+ if ( ! CodeHacker::has_persistent_hacks() ) {
+ CodeHacker::disable();
+ }
}
public function executeBeforeTest( string $test ): void {
@@ -88,7 +90,7 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
$has_class_annotation_hacks = $this->add_hacks_from_annotations( new ReflectionClass( $class_name ) );
$has_method_annotaion_hacks = $this->add_hacks_from_annotations( new ReflectionMethod( $class_name, $method_name ) );
- if ( $has_class_annotation_hacks || $has_method_annotaion_hacks ) {
+ if ( $has_class_annotation_hacks || $has_method_annotaion_hacks || CodeHacker::has_persistent_hacks() ) {
CodeHacker::enable();
}
}
diff --git a/src/Testing/CodeHacking/README.md b/src/Testing/CodeHacking/README.md
index a4f2e17dca6..d6fc95a9c19 100644
--- a/src/Testing/CodeHacking/README.md
+++ b/src/Testing/CodeHacking/README.md
@@ -166,9 +166,11 @@ Under the hood this is hacking all the classes in the list with a "clean" `Stati
Alternatively, you can configure mock functions instead of a mock class. See the source of `StaticWrapper` for more details.
-## Note on `copy` in tests
+## Temporarily disabling the code hacker
-For some reason tests using `copy` to copy files will fail if the code hacker is active. As a workaround, the new `file_copy` method defined in `WC_Unit_Test_Case` should be used instead (it temporarily disables the code hacker and then performs the copy operation). This is something to investigate.
+In a few rare cases the code hacker will cause problems with tests that do write operations on the local filesystem. In these cases it is possible to temporarily disable the code hacker using `self::disable_code_hacker()` and `self::reenable_code_hacker()` in the test (these methods are defined in `WC_Unit_Test_Case`). These methods are carefully written so that they won't enable the code hacker if it wasn't enabled when the test started, and there's a disabling requests count in place to ensure that the code hacker isn't enabled before it should.
+
+One of these cases is the usage of the `copy` command to copy files. Since this function is used in a few tests, a convenience `file_copy` method is defined in `WC_Unit_Test_Case`; it just temporarily disables the hacker, does the copy, and reenables the hacker.
## An important note
diff --git a/tests/legacy/bootstrap.php b/tests/legacy/bootstrap.php
index 79c31aaca88..63b94865b0a 100644
--- a/tests/legacy/bootstrap.php
+++ b/tests/legacy/bootstrap.php
@@ -47,7 +47,7 @@ class WC_Unit_Tests_Bootstrap {
$classes_that_need_static_wrapper = include_once __DIR__ . '/classes-that-need-static-wrapper.php';
foreach ( $classes_that_need_static_wrapper as $class ) {
$wrapper_class = StaticWrapper::define_for( $class );
- CodeHacker::add_hack( new StaticMockerHack( $class, $wrapper_class, true ) );
+ CodeHacker::add_hack( new StaticMockerHack( $class, $wrapper_class, true ), true );
}
CodeHacker::enable();
diff --git a/tests/legacy/framework/class-wc-unit-test-case.php b/tests/legacy/framework/class-wc-unit-test-case.php
index 0af6062d97e..ae8a96c0304 100644
--- a/tests/legacy/framework/class-wc-unit-test-case.php
+++ b/tests/legacy/framework/class-wc-unit-test-case.php
@@ -24,6 +24,37 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
*/
protected $factory;
+ /**
+ * @var int Keeps the count of how many times disable_code_hacker has been invoked.
+ */
+ private static $code_hacker_temporary_disables_requested = 0;
+
+ /**
+ * Increase the count of Code Hacker disable requests, and effectively disable it if the count was zero.
+ * Does nothing if the code hacker wasn't enabled when the test suite started running.
+ */
+ protected static function disable_code_hacker() {
+ if ( CodeHacker::is_enabled() ) {
+ CodeHacker::disable();
+ self::$code_hacker_temporary_disables_requested = 1;
+ } elseif ( self::$code_hacker_temporary_disables_requested > 0 ) {
+ self::$code_hacker_temporary_disables_requested++;
+ }
+ }
+
+ /**
+ * Decrease the count of Code Hacker disable requests, and effectively re-enable it if the count reaches zero.
+ * Does nothing if the count is already zero.
+ */
+ protected static function reenable_code_hacker() {
+ if ( self::$code_hacker_temporary_disables_requested > 0 ) {
+ self::$code_hacker_temporary_disables_requested--;
+ if ( 0 === self::$code_hacker_temporary_disables_requested ) {
+ CodeHacker::enable();
+ }
+ }
+ }
+
/**
* Setup test case.
*
@@ -113,14 +144,10 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase {
* @param string $dest The destination path.
* @return bool true on success or false on failure.
*/
- public function file_copy( $source, $dest ) {
- if ( CodeHacker::is_enabled() ) {
- CodeHacker::restore();
- $result = copy( $source, $dest );
- CodeHacker::enable();
- return $result;
- } else {
- return copy( $source, $dest );
- }
+ public static function file_copy( $source, $dest ) {
+ self::disable_code_hacker();
+ $result = copy( $source, $dest );
+ self::reenable_code_hacker();
+ return $result;
}
}
diff --git a/tests/legacy/unit-tests/importer/product.php b/tests/legacy/unit-tests/importer/product.php
index 89284f3d5ae..e55fdd79351 100644
--- a/tests/legacy/unit-tests/importer/product.php
+++ b/tests/legacy/unit-tests/importer/product.php
@@ -147,7 +147,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
* @return void
*/
public function test_server_file() {
- $this->file_copy( $this->csv_file, ABSPATH . '/sample.csv' );
+ self::file_copy( $this->csv_file, ABSPATH . '/sample.csv' );
$_POST['file_url'] = 'sample.csv';
$import_controller = new WC_Product_CSV_Importer_Controller();
$this->assertEquals( ABSPATH . 'sample.csv', $import_controller->handle_upload() );
@@ -644,7 +644,7 @@ class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
if ( false !== strpos( $url, 'http://demo.woothemes.com' ) ) {
if ( ! empty( $request['filename'] ) ) {
- $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
+ self::file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
}
$mocked_response = array(
diff --git a/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php b/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
index c37a87c66f5..6e424380a33 100644
--- a/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
+++ b/tests/legacy/unit-tests/integrations/maxmind-geolocation/class-wc-tests-maxmind-database.php
@@ -57,7 +57,9 @@ class WC_Tests_MaxMind_Database extends WC_Unit_Test_Case {
$database_service = new WC_Integration_MaxMind_Database_Service( '' );
$expected_database = sys_get_temp_dir() . '/GeoLite2-Country_20200100/GeoLite2-Country.mmdb';
+ self::disable_code_hacker();
$result = $database_service->download_database( 'testing_license' );
+ self::reenable_code_hacker();
if ( is_wp_error( $result ) ) {
$this->fail( $result->get_error_message() );
}
@@ -129,7 +131,7 @@ class WC_Tests_MaxMind_Database extends WC_Unit_Test_Case {
if ( 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=testing_license&suffix=tar.gz' === $url ) {
// We need to copy the file to where the request is supposed to have streamed it.
- $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/GeoLite2-Country.tar.gz', $request['filename'] );
+ self::file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/GeoLite2-Country.tar.gz', $request['filename'] );
$mocked_response = array(
'response' => array( 'code' => 200 ),
diff --git a/tests/legacy/unit-tests/util/api-functions.php b/tests/legacy/unit-tests/util/api-functions.php
index 80d3f00ab96..3fb98c57b12 100644
--- a/tests/legacy/unit-tests/util/api-functions.php
+++ b/tests/legacy/unit-tests/util/api-functions.php
@@ -236,14 +236,14 @@ class WC_Tests_API_Functions extends WC_Unit_Test_Case {
} elseif ( 'http://somedomain.com/invalid-image-2.png' === $url ) {
// image with an unsupported mime type.
// we need to manually copy the file as we are mocking the request. without this an empty file is created.
- $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/file.txt', $request['filename'] );
+ self::file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/file.txt', $request['filename'] );
$mocked_response = array(
'response' => array( 'code' => 200 ),
);
} elseif ( 'http://somedomain.com/' . $this->file_name === $url ) {
// we need to manually copy the file as we are mocking the request. without this an empty file is created.
- $this->file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
+ self::file_copy( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/Dr1Bczxq4q.png', $request['filename'] );
$mocked_response = array(
'response' => array( 'code' => 200 ),
From 0726f71aac82e83b3a2f9b6940379b09192e9173 Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Mon, 18 May 2020 10:07:20 +0200
Subject: [PATCH 072/153] Small improvements in the code hacker
- Convert a hardcoded number to a constant
- Add more information to an exception message
- Remove unnecessary PHPCS disabling
- Throw exception on PHP<7.0
---
src/Testing/CodeHacking/CodeHacker.php | 13 ++++++-----
.../CodeHacking/CodeHackerTestHook.php | 22 +++++++++++++++----
src/Testing/CodeHacking/Hacks/CodeHack.php | 8 +++++--
3 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/src/Testing/CodeHacking/CodeHacker.php b/src/Testing/CodeHacking/CodeHacker.php
index 967c5cdd8a9..9c3696416e6 100644
--- a/src/Testing/CodeHacking/CodeHacker.php
+++ b/src/Testing/CodeHacking/CodeHacker.php
@@ -31,9 +31,10 @@ use \ReflectionException;
*
* For using with PHPUnit, see CodeHackerTestHook.
*/
-class CodeHacker {
+final class CodeHacker {
- const PROTOCOL = 'file';
+ const PROTOCOL = 'file';
+ const HACK_CALLBACK_ARGUMENT_COUNT = 2;
/**
* Value of "context" parameter to be passed to the native PHP filesystem related functions.
@@ -132,11 +133,11 @@ class CodeHacker {
*/
public static function add_hack( $hack, $persistent = false ) {
if ( ! is_callable( $hack ) && ! is_object( $hack ) ) {
- throw new \Exception( "Hacks must be either functions, or objects having a 'process(\$text, \$path)' method." );
+ throw new \Exception( "CodeHacker::addhack: Hacks must be either functions, or objects having a 'process(\$text, \$path)' method." );
}
if ( ! self::is_valid_hack_callback( $hack ) && ! self::is_valid_hack_object( $hack ) ) {
- throw new \Exception( "CodeHacker::addhack: hacks must be either a function with a 'hack(\$code,\$path)' signature, or an object containing a public method 'hack' with that signature. " );
+ throw new \Exception( "CodeHacker::addhack: Hacks must be either a function with a 'hack(\$code,\$path)' signature, or an object containing a public method 'hack' with that signature. " );
}
if ( $persistent ) {
@@ -154,7 +155,7 @@ class CodeHacker {
* @throws ReflectionException Error when instantiating ReflectionFunction.
*/
private static function is_valid_hack_callback( $callback ) {
- return is_callable( $callback ) && 2 === ( new ReflectionFunction( $callback ) )->getNumberOfRequiredParameters();
+ return is_callable( $callback ) && HACK_CALLBACK_ARGUMENT_COUNT === ( new ReflectionFunction( $callback ) )->getNumberOfRequiredParameters();
}
/**
@@ -162,7 +163,7 @@ class CodeHacker {
*
* @param mixed $callback Argument to check.
*
- * @return boolt rue if the argument is a valid hack object, false otherwise.
+ * @return bool rue if the argument is a valid hack object, false otherwise.
*/
private static function is_valid_hack_object( $callback ) {
if ( ! is_object( $callback ) ) {
diff --git a/src/Testing/CodeHacking/CodeHackerTestHook.php b/src/Testing/CodeHacking/CodeHackerTestHook.php
index 0d96a2515ba..f5c84696064 100644
--- a/src/Testing/CodeHacking/CodeHackerTestHook.php
+++ b/src/Testing/CodeHacking/CodeHackerTestHook.php
@@ -5,8 +5,6 @@
* @package WooCommerce/Testing
*/
-// phpcs:disable Squiz.Commenting.FunctionComment.Missing, PHPCompatibility.FunctionDeclarations
-
namespace Automattic\WooCommerce\Testing\CodeHacking;
use PHPUnit\Runner\BeforeTestHook;
@@ -64,12 +62,27 @@ use Exception;
*/
final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
+ // phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound
+
+ /**
+ * Runs after each test.
+ *
+ * @param string $test "TestClass::TestMethod".
+ * @param float $time The time it took the test to run, in seconds.
+ */
public function executeAfterTest( string $test, float $time ): void {
if ( ! CodeHacker::has_persistent_hacks() ) {
CodeHacker::disable();
}
}
+ /**
+ * Runs before each test.
+ *
+ * @param string $test "TestClass::TestMethod".
+ *
+ * @throws \ReflectionException Thrown by execute_before_methods.
+ */
public function executeBeforeTest( string $test ): void {
/**
* Possible formats of $test:
@@ -79,7 +92,7 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
*/
$parts = explode( '::', $test );
if ( count( $parts ) < 2 ) {
- return;
+ return; // "Warning" was supplied as argument
}
$class_name = $parts[0];
$method_name = explode( ' ', $parts[1] )[0];
@@ -95,6 +108,8 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
}
}
+ // phpcs:enable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound
+
/**
* Apply hacks defined in @hack annotations.
*
@@ -164,4 +179,3 @@ final class CodeHackerTestHook implements BeforeTestHook, AfterTestHook {
}
}
-// phpcs:enable Squiz.Commenting.FunctionComment.Missing, PHPCompatibility.FunctionDeclarations
diff --git a/src/Testing/CodeHacking/Hacks/CodeHack.php b/src/Testing/CodeHacking/Hacks/CodeHack.php
index 47e11f2a058..392845ace15 100644
--- a/src/Testing/CodeHacking/Hacks/CodeHack.php
+++ b/src/Testing/CodeHacking/Hacks/CodeHack.php
@@ -29,10 +29,14 @@ abstract class CodeHack {
*
* @param string $code PHP code to tokenize.
* @return array Tokenized code.
+ * @throws \Exception PHP version is less than 7.0.
*/
protected function tokenize( $code ) {
- //phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters, PHPCompatibility.Constants.NewConstants
- return PHP_VERSION_ID >= 70000 ? token_get_all( $code, TOKEN_PARSE ) : token_get_all( $code );
+ if ( PHP_VERSION_ID < 70000 ) {
+ throw new \Exception( 'The code hacker can be used in PHP 7.0+ only.' );
+ }
+
+ return token_get_all( $code, TOKEN_PARSE );
}
/**
From 2a68bb018d59f660ed42e1891cf89f45ac59c4b0 Mon Sep 17 00:00:00 2001
From: Nestor Soriano
Date: Mon, 18 May 2020 11:31:59 +0200
Subject: [PATCH 073/153] Move testing tools to the tests/Tools directory
The testing tools (only the code hacker at this time) have been moved
from 'src' to 'tests/Tools', since many opcode cache plugins
load the whole src folder in production.
Also, an extra autoloader is set in the tests bootstrap so that
the 'tests/Tools' directory corresponds, using PSR4, to the
'Automattic\WooCommerce\Testing\Tools' namespace.
---
phpcs.xml | 1 +
phpunit.xml | 2 +-
.../Tools}/CodeHacking/CodeHacker.php | 2 +-
.../Tools}/CodeHacking/CodeHackerTestHook.php | 6 +++---
.../Tools}/CodeHacking/Hacks/BypassFinalsHack.php | 2 +-
.../Tools}/CodeHacking/Hacks/CodeHack.php | 2 +-
.../CodeHacking/Hacks/FunctionsMockerHack.php | 2 +-
.../Tools}/CodeHacking/Hacks/StaticMockerHack.php | 2 +-
{src/Testing => tests/Tools}/CodeHacking/README.md | 0
.../Tools}/CodeHacking/StaticWrapper.php | 4 ++--
tests/legacy/bootstrap.php | 13 +++++++++----
tests/legacy/framework/class-wc-unit-test-case.php | 2 +-
12 files changed, 22 insertions(+), 16 deletions(-)
rename {src/Testing => tests/Tools}/CodeHacking/CodeHacker.php (99%)
rename {src/Testing => tests/Tools}/CodeHacking/CodeHackerTestHook.php (96%)
rename {src/Testing => tests/Tools}/CodeHacking/Hacks/BypassFinalsHack.php (90%)
rename {src/Testing => tests/Tools}/CodeHacking/Hacks/CodeHack.php (97%)
rename {src/Testing => tests/Tools}/CodeHacking/Hacks/FunctionsMockerHack.php (96%)
rename {src/Testing => tests/Tools}/CodeHacking/Hacks/StaticMockerHack.php (98%)
rename {src/Testing => tests/Tools}/CodeHacking/README.md (100%)
rename {src/Testing => tests/Tools}/CodeHacking/StaticWrapper.php (97%)
diff --git a/phpcs.xml b/phpcs.xml
index b1d14b43537..8075b9d8ccc 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -59,5 +59,6 @@
i18n/
src/
tests/php
+ tests/Tools/
diff --git a/phpunit.xml b/phpunit.xml
index 68ec7b7ca4a..94156de9541 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -50,6 +50,6 @@
-
+
diff --git a/src/Testing/CodeHacking/CodeHacker.php b/tests/Tools/CodeHacking/CodeHacker.php
similarity index 99%
rename from src/Testing/CodeHacking/CodeHacker.php
rename to tests/Tools/CodeHacking/CodeHacker.php
index 9c3696416e6..13582d9f6ea 100644
--- a/src/Testing/CodeHacking/CodeHacker.php
+++ b/tests/Tools/CodeHacking/CodeHacker.php
@@ -7,7 +7,7 @@
//phpcs:disable WordPress.WP.AlternativeFunctions, WordPress.PHP.NoSilencedErrors.Discouraged
-namespace Automattic\WooCommerce\Testing\CodeHacking;
+namespace Automattic\WooCommerce\Testing\Tools\CodeHacking;
use \ReflectionObject;
use \ReflectionFunction;
diff --git a/src/Testing/CodeHacking/CodeHackerTestHook.php b/tests/Tools/CodeHacking/CodeHackerTestHook.php
similarity index 96%
rename from src/Testing/CodeHacking/CodeHackerTestHook.php
rename to tests/Tools/CodeHacking/CodeHackerTestHook.php
index f5c84696064..3be3df0d244 100644
--- a/src/Testing/CodeHacking/CodeHackerTestHook.php
+++ b/tests/Tools/CodeHacking/CodeHackerTestHook.php
@@ -5,7 +5,7 @@
* @package WooCommerce/Testing
*/
-namespace Automattic\WooCommerce\Testing\CodeHacking;
+namespace Automattic\WooCommerce\Testing\Tools\CodeHacking;
use PHPUnit\Runner\BeforeTestHook;
use PHPUnit\Runner\AfterTestHook;
@@ -27,8 +27,8 @@ use Exception;
*
* 2. Add the following to the test classes:
*
- * use Automattic\WooCommerce\Testing\CodeHacking\CodeHacker;
- * use Automattic\WooCommerce\Testing\CodeHacking\Hacks\...
+ * use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker;
+ * use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\...
*
* public static function before_all($method_name) {
* CodeHacker::add_hack(...);
diff --git a/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php b/tests/Tools/CodeHacking/Hacks/BypassFinalsHack.php
similarity index 90%
rename from src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
rename to tests/Tools/CodeHacking/Hacks/BypassFinalsHack.php
index a9d8e97bc6b..18ad71f2b70 100644
--- a/src/Testing/CodeHacking/Hacks/BypassFinalsHack.php
+++ b/tests/Tools/CodeHacking/Hacks/BypassFinalsHack.php
@@ -7,7 +7,7 @@
// phpcs:disable Squiz.Commenting.FunctionComment.Missing
-namespace Automattic\WooCommerce\Testing\CodeHacking\Hacks;
+namespace Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks;
/**
* Code hack to bypass finals.
diff --git a/src/Testing/CodeHacking/Hacks/CodeHack.php b/tests/Tools/CodeHacking/Hacks/CodeHack.php
similarity index 97%
rename from src/Testing/CodeHacking/Hacks/CodeHack.php
rename to tests/Tools/CodeHacking/Hacks/CodeHack.php
index 392845ace15..8049b884be1 100644
--- a/src/Testing/CodeHacking/Hacks/CodeHack.php
+++ b/tests/Tools/CodeHacking/Hacks/CodeHack.php
@@ -5,7 +5,7 @@
* @package WooCommerce/Testing
*/
-namespace Automattic\WooCommerce\Testing\CodeHacking\Hacks;
+namespace Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks;
/**
* Base class to define Hacks for CodeHacker.
diff --git a/src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php b/tests/Tools/CodeHacking/Hacks/FunctionsMockerHack.php
similarity index 96%
rename from src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php
rename to tests/Tools/CodeHacking/Hacks/FunctionsMockerHack.php
index a6703928bbe..494a5e11d0c 100644
--- a/src/Testing/CodeHacking/Hacks/FunctionsMockerHack.php
+++ b/tests/Tools/CodeHacking/Hacks/FunctionsMockerHack.php
@@ -7,7 +7,7 @@
// phpcs:disable Squiz.Commenting.FunctionComment.Missing
-namespace Automattic\WooCommerce\Testing\CodeHacking\Hacks;
+namespace Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks;
use ReflectionMethod;
use ReflectionClass;
diff --git a/src/Testing/CodeHacking/Hacks/StaticMockerHack.php b/tests/Tools/CodeHacking/Hacks/StaticMockerHack.php
similarity index 98%
rename from src/Testing/CodeHacking/Hacks/StaticMockerHack.php
rename to tests/Tools/CodeHacking/Hacks/StaticMockerHack.php
index 99fc53915e4..27c9a39c2ff 100644
--- a/src/Testing/CodeHacking/Hacks/StaticMockerHack.php
+++ b/tests/Tools/CodeHacking/Hacks/StaticMockerHack.php
@@ -7,7 +7,7 @@
// phpcs:disable Squiz.Commenting.FunctionComment.Missing
-namespace Automattic\WooCommerce\Testing\CodeHacking\Hacks;
+namespace Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks;
use ReflectionMethod;
use ReflectionClass;
diff --git a/src/Testing/CodeHacking/README.md b/tests/Tools/CodeHacking/README.md
similarity index 100%
rename from src/Testing/CodeHacking/README.md
rename to tests/Tools/CodeHacking/README.md
diff --git a/src/Testing/CodeHacking/StaticWrapper.php b/tests/Tools/CodeHacking/StaticWrapper.php
similarity index 97%
rename from src/Testing/CodeHacking/StaticWrapper.php
rename to tests/Tools/CodeHacking/StaticWrapper.php
index fff5e72cf6b..812e5429ec6 100644
--- a/src/Testing/CodeHacking/StaticWrapper.php
+++ b/tests/Tools/CodeHacking/StaticWrapper.php
@@ -5,7 +5,7 @@
* @package WooCommerce/Testing
*/
-namespace Automattic\WooCommerce\Testing\CodeHacking;
+namespace Automattic\WooCommerce\Testing\Tools\CodeHacking;
use ReflectionClass;
use ReflectionMethod;
@@ -39,7 +39,7 @@ use ReflectionMethod;
*
* You can use 2 and 3 at the same time, functions have precedence over mock class methods in case of conflict.
*
- * @package Automattic\WooCommerce\Testing\CodeHacking
+ * @package Automattic\WooCommerce\Testing\Tools\CodeHacking
*/
abstract class StaticWrapper {
diff --git a/tests/legacy/bootstrap.php b/tests/legacy/bootstrap.php
index 63b94865b0a..66833aaf190 100644
--- a/tests/legacy/bootstrap.php
+++ b/tests/legacy/bootstrap.php
@@ -6,9 +6,10 @@
* @package WooCommerce Tests
*/
-use Automattic\WooCommerce\Testing\CodeHacking\CodeHacker;
-use Automattic\WooCommerce\Testing\CodeHacking\StaticWrapper;
-use Automattic\WooCommerce\Testing\CodeHacking\Hacks\StaticMockerHack;
+use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker;
+use Automattic\WooCommerce\Testing\Tools\CodeHacking\StaticWrapper;
+use Automattic\WooCommerce\Testing\Tools\CodeHacking\Hacks\StaticMockerHack;
+use Composer\Autoload\ClassLoader;
/**
* Class WC_Unit_Tests_Bootstrap
@@ -33,11 +34,15 @@ class WC_Unit_Tests_Bootstrap {
* @since 2.2
*/
public function __construct() {
+ $classLoader = new ClassLoader();
+ $classLoader->addPsr4("Automattic\\WooCommerce\\Testing\\Tools\\", __DIR__ . '/../Tools', false);
+ $classLoader->register();
+ //Includes needed to initialize the static wrapper
$this->tests_dir = dirname( __FILE__ );
$this->plugin_dir = dirname( dirname( $this->tests_dir ) );
- $hacking_base = $this->plugin_dir . '/src/Testing/CodeHacking';
+ $hacking_base = $this->plugin_dir . '/tests/Tools/CodeHacking';
require_once $hacking_base . '/StaticWrapper.php';
require_once $hacking_base . '/CodeHacker.php';
require_once $hacking_base . '/Hacks/CodeHack.php';
diff --git a/tests/legacy/framework/class-wc-unit-test-case.php b/tests/legacy/framework/class-wc-unit-test-case.php
index ae8a96c0304..8e6e947dec3 100644
--- a/tests/legacy/framework/class-wc-unit-test-case.php
+++ b/tests/legacy/framework/class-wc-unit-test-case.php
@@ -5,7 +5,7 @@
* @package WooCommerce\Tests
*/
-use Automattic\WooCommerce\Testing\CodeHacking\CodeHacker;
+use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker;
/**
* WC Unit Test Case.
From 3acc03c804cb19b7b8f49a6242ad84725611feef Mon Sep 17 00:00:00 2001
From: vedanshujain
Date: Mon, 11 May 2020 23:34:51 +0530
Subject: [PATCH 074/153] Add `verify_base_db` method to check if all base
tables are present.
Optionally, also adds a notice in case all db tables are not present. Returns list of tables.
Note that we only check missing tables and don't care about exact table structure because many time tables are modified by merchants to better suit their needs (indexes, collations etc).
---
.../views/html-notice-base-table-missing.php | 32 +++++++++++++++++
includes/class-wc-install.php | 35 ++++++++++++++++++-
2 files changed, 66 insertions(+), 1 deletion(-)
create mode 100644 includes/admin/views/html-notice-base-table-missing.php
diff --git a/includes/admin/views/html-notice-base-table-missing.php b/includes/admin/views/html-notice-base-table-missing.php
new file mode 100644
index 00000000000..b38cbd4d039
--- /dev/null
+++ b/includes/admin/views/html-notice-base-table-missing.php
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+ WooCommerce status page to see details, or check again. ', 'woocommerce' ),
+ admin_url( 'admin.php?page=wc-status#status-database' ),
+ wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=verify_db_tables' ), 'debug_action' )
+ )
+ );
+ ?>
+
+
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index 4061f1a7c9d..96581e66cc8 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -287,6 +287,7 @@ class WC_Install {
WC()->wpdb_table_fix();
self::remove_admin_notices();
self::create_tables();
+ self::verify_base_tables();
self::create_options();
self::create_roles();
self::setup_environment();
@@ -303,6 +304,37 @@ class WC_Install {
do_action( 'woocommerce_installed' );
}
+ /**
+ * Check if all the base tables are present.
+ *
+ * @param bool $modify_notice Whether to modify notice based on if all tables are present.
+ *
+ * @return array List of querues.
+ */
+ public static function verify_base_tables( $modify_notice = true ) {
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+
+ $queries = dbDelta( self::get_schema(), false );
+ $missing_tables = array();
+ foreach ( $queries as $table_name => $result ) {
+ if ( "Created table $table_name" === $result ) {
+ $missing_tables[] = $table_name;
+ }
+ }
+
+ if ( 0 < count( $missing_tables ) ) {
+ if ( $modify_notice ) {
+ WC_Admin_Notices::add_notice( 'base_tables_missing' );
+ }
+ } else {
+ if ( $modify_notice ) {
+ WC_Admin_Notices::remove_notice( 'base_tables_missing' );
+ }
+ update_option( 'woocommerce-db-verified', WC()->version );
+ }
+ return $missing_tables;
+ }
+
/**
* Reset any notices added to admin.
*
@@ -630,8 +662,9 @@ class WC_Install {
* woocommerce_order_itemmeta - Order line item meta is stored in a table for storing extra data.
* woocommerce_tax_rates - Tax Rates are stored inside 2 tables making tax queries simple and efficient.
* woocommerce_tax_rate_locations - Each rate can be applied to more than one postcode/city hence the second table.
+ * wc_reserved_stock - Hold reserved stock during a checkout.
*/
- private static function create_tables() {
+ public static function create_tables() {
global $wpdb;
$wpdb->hide_errors();
From f5afddd4c97d482fe0a4aabf073960ca3fcdb986 Mon Sep 17 00:00:00 2001
From: vedanshujain
Date: Mon, 11 May 2020 23:39:53 +0530
Subject: [PATCH 075/153] Add support for notices
---
includes/admin/class-wc-admin-notices.php | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/includes/admin/class-wc-admin-notices.php b/includes/admin/class-wc-admin-notices.php
index fd61bf6c388..4e0c09f61d9 100644
--- a/includes/admin/class-wc-admin-notices.php
+++ b/includes/admin/class-wc-admin-notices.php
@@ -40,6 +40,7 @@ class WC_Admin_Notices {
'maxmind_license_key' => 'maxmind_missing_license_key_notice',
'redirect_download_method' => 'redirect_download_method_notice',
'uploads_directory_is_unprotected' => 'uploads_directory_is_unprotected_notice',
+ 'base_tables_missing' => 'base_tables_missing_notice',
);
/**
@@ -506,6 +507,21 @@ class WC_Admin_Notices {
include dirname( __FILE__ ) . '/views/html-notice-uploads-directory-is-unprotected.php';
}
+ /**
+ * Notice about base tables missing.
+ */
+ public static function base_tables_missing_notice() {
+ $notice_dismissed = apply_filters(
+ 'woocommerce_hide_base_tables_missing_nag',
+ get_user_meta( get_current_user_id(), 'dismissed_base_tables_missing_notice', true )
+ );
+ if ( $notice_dismissed ) {
+ self::remove_notice( 'base_tables_missing' );
+ }
+
+ include dirname( __FILE__ ) . '/views/html-notice-base-table-missing.php';
+ }
+
/**
* Determine if the store is running SSL.
*
From fafa44bde01d5787d531abb78a1f15973ddcdb0e Mon Sep 17 00:00:00 2001
From: vedanshujain
Date: Tue, 12 May 2020 18:49:34 +0530
Subject: [PATCH 076/153] Modified notice to also handle when REST API does not
have verify tool.
---
.../views/html-notice-base-table-missing.php | 20 +++++++++++++++----
includes/class-wc-install.php | 2 ++
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/includes/admin/views/html-notice-base-table-missing.php b/includes/admin/views/html-notice-base-table-missing.php
index b38cbd4d039..dc53d0a1f30 100644
--- a/includes/admin/views/html-notice-base-table-missing.php
+++ b/includes/admin/views/html-notice-base-table-missing.php
@@ -19,14 +19,26 @@ defined( 'ABSPATH' ) || exit;
WooCommerce status page to see details, or check again. ', 'woocommerce' ),
- admin_url( 'admin.php?page=wc-status#status-database' ),
+ /* translators: %1%s: Missing tables (seperated by ",") %2$s: Link to check again */
+ __( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s. Check again. ', 'woocommerce' ),
+ esc_html( implode( ', ', $missing_tables ) ),
wp_nonce_url( admin_url( 'admin.php?page=wc-status&tab=tools&action=verify_db_tables' ), 'debug_action' )
)
);
- ?>
+ } else {
+ echo wp_kses_post(
+ sprintf(
+ /* translators: %1%s: Missing tables (seperated by ",") */
+ __( 'One or more tables required for WooCommerce to function are missing, some features may not work as expected. Missing tables: %1$s.', 'woocommerce' ),
+ esc_html( implode( ', ', $missing_tables ) )
+ )
+ );
+ }
+ ?>
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index 96581e66cc8..5fbaeda8697 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -326,11 +326,13 @@ class WC_Install {
if ( $modify_notice ) {
WC_Admin_Notices::add_notice( 'base_tables_missing' );
}
+ update_option( 'woocommerce-db-missing-tables', $missing_tables );
} else {
if ( $modify_notice ) {
WC_Admin_Notices::remove_notice( 'base_tables_missing' );
}
update_option( 'woocommerce-db-verified', WC()->version );
+ delete_option( 'woocommerce-db-missing-tables' );
}
return $missing_tables;
}
From 418741a0b2895c7c8101c199839ef35a0f98dc9f Mon Sep 17 00:00:00 2001
From: vedanshujain
Date: Tue, 12 May 2020 23:02:43 +0530
Subject: [PATCH 077/153] Add unit test for verify_base_tables function
---
includes/class-wc-install.php | 2 +-
tests/php/includes/WCInstallTest.php | 51 ++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 1 deletion(-)
create mode 100644 tests/php/includes/WCInstallTest.php
diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php
index 5fbaeda8697..0aa9fb65df1 100644
--- a/includes/class-wc-install.php
+++ b/includes/class-wc-install.php
@@ -762,7 +762,7 @@ class WC_Install {
*
* @return string
*/
- private static function get_schema() {
+ public static function get_schema() {
global $wpdb;
$collate = '';
diff --git a/tests/php/includes/WCInstallTest.php b/tests/php/includes/WCInstallTest.php
new file mode 100644
index 00000000000..6dccb8e7186
--- /dev/null
+++ b/tests/php/includes/WCInstallTest.php
@@ -0,0 +1,51 @@
+prefix}wc_tax_rate_classes";
+ $changed_table_name = "{$wpdb->prefix}wc_tax_rate_classes_2";
+
+ $clear_query = 'DROP TABLE IF EXISTS %s;';
+ $rename_table_query = 'RENAME TABLE %s to %s;';
+
+ // Rename a base table to simulate it as non-existing.
+ dbDelta( \WC_Install::get_schema() ); // Restore correct state.
+ $wpdb->query( sprintf( $clear_query, $changed_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ $wpdb->query( sprintf( $rename_table_query, $original_table_name, $changed_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+
+ $missing_tables = \WC_Install::verify_base_tables();
+
+ $wpdb->query( sprintf( $rename_table_query, $changed_table_name, $original_table_name ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+ add_filter( 'query', array( $this, '_drop_temporary_tables' ) );
+
+ $this->assertContains( $original_table_name, $missing_tables );
+ $this->assertContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
+
+ // Ideally, no missing table anymore because we have switched back table name.
+ $missing_tables = \WC_Install::verify_base_tables();
+
+ $this->assertNotContains( $original_table_name, $missing_tables );
+ $this->assertNotContains( 'base_tables_missing', \WC_Admin_Notices::get_notices() );
+ }
+
+}
From 57d336433a71a97111ad61e04c7224f0decf46bc Mon Sep 17 00:00:00 2001
From: vedanshujain
Date: Fri, 22 May 2020 22:08:42 +0530
Subject: [PATCH 078/153] Fix regression caused by merging #25092 conflicting
with #24828
---
includes/abstracts/abstract-wc-order.php | 9 ++-------
tests/legacy/unit-tests/order/coupons.php | 4 +---
2 files changed, 3 insertions(+), 10 deletions(-)
diff --git a/includes/abstracts/abstract-wc-order.php b/includes/abstracts/abstract-wc-order.php
index 5e5f38f6d9f..ca6989bcff0 100644
--- a/includes/abstracts/abstract-wc-order.php
+++ b/includes/abstracts/abstract-wc-order.php
@@ -1672,18 +1672,13 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
public function calculate_totals( $and_taxes = true ) {
do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
- $cart_subtotal = 0;
- $cart_total = 0;
$fees_total = 0;
$shipping_total = 0;
$cart_subtotal_tax = 0;
$cart_total_tax = 0;
- // Sum line item costs without rounding.
- foreach ( $this->get_items() as $item ) {
- $cart_subtotal += $item->get_subtotal();
- $cart_total += $item->get_total();
- }
+ $cart_subtotal = $this->get_cart_subtotal_for_order();
+ $cart_total = $this->get_cart_total_for_order();
// Sum shipping costs.
foreach ( $this->get_shipping_methods() as $shipping ) {
diff --git a/tests/legacy/unit-tests/order/coupons.php b/tests/legacy/unit-tests/order/coupons.php
index 0fdb4fce3e9..dbfbea78180 100644
--- a/tests/legacy/unit-tests/order/coupons.php
+++ b/tests/legacy/unit-tests/order/coupons.php
@@ -371,6 +371,7 @@ class WC_Tests_Order_Coupons extends WC_Unit_Test_Case {
public function test_inclusive_tax_rounding_on_totals() {
update_option( 'woocommerce_prices_include_tax', 'yes' );
update_option( 'woocommerce_calc_taxes', 'yes' );
+ update_option( 'woocommerce_tax_round_at_subtotal', 'yes' );
WC_Tax::_insert_tax_rate(
array(
@@ -440,12 +441,9 @@ class WC_Tests_Order_Coupons extends WC_Unit_Test_Case {
$order->apply_coupon( $coupon->get_code() );
$applied_coupons = $order->get_items( 'coupon' );
- $applied_coupon = current( $applied_coupons );
$this->assertEquals( '16.95', $order->get_total() );
$this->assertEquals( '1.73', $order->get_total_tax() );
$this->assertEquals( '1.69', $order->get_discount_total() );
-
- $this->assertEquals( '1.69', $applied_coupon->get_discount() );
}
}
From 4d4e327df63a80952873e3a6342511b8e8867bf9 Mon Sep 17 00:00:00 2001
From: Renovate Bot
Date: Fri, 22 May 2020 19:24:14 +0000
Subject: [PATCH 079/153] Update dependency woocommerce/woocommerce-admin to
v1.2.3
---
composer.json | 2 +-
composer.lock | 26 ++++++++++++++++++++------
2 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/composer.json b/composer.json
index 4b712371355..95cfb18426d 100644
--- a/composer.json
+++ b/composer.json
@@ -14,7 +14,7 @@
"maxmind-db/reader": "1.6.0",
"pelago/emogrifier": "^3.1",
"woocommerce/action-scheduler": "3.1.6",
- "woocommerce/woocommerce-admin": "1.2.0",
+ "woocommerce/woocommerce-admin": "1.2.3",
"woocommerce/woocommerce-blocks": "2.5.16",
"woocommerce/woocommerce-rest-api": "1.0.8"
},
diff --git a/composer.lock b/composer.lock
index bd5ad7397c3..d45718add1d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "94c4cc4a9a0fc2ad6758cc6c7cb069bb",
+ "content-hash": "674dae676cb4905418f7930391e8dcf2",
"packages": [
{
"name": "automattic/jetpack-autoloader",
@@ -380,6 +380,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
"time": "2020-03-16T08:31:04+00:00"
},
{
@@ -419,16 +433,16 @@
},
{
"name": "woocommerce/woocommerce-admin",
- "version": "v1.2.0",
+ "version": "v1.2.3",
"source": {
"type": "git",
"url": "https://github.com/woocommerce/woocommerce-admin.git",
- "reference": "aa5062218a399843e68160e1b4b884320a7530ae"
+ "reference": "5560c9b6e31e1d794a55a0eb4ccf040fd2cee4c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/aa5062218a399843e68160e1b4b884320a7530ae",
- "reference": "aa5062218a399843e68160e1b4b884320a7530ae",
+ "url": "https://api.github.com/repos/woocommerce/woocommerce-admin/zipball/5560c9b6e31e1d794a55a0eb4ccf040fd2cee4c7",
+ "reference": "5560c9b6e31e1d794a55a0eb4ccf040fd2cee4c7",
"shasum": ""
},
"require": {
@@ -462,7 +476,7 @@
],
"description": "A modern, javascript-driven WooCommerce Admin experience.",
"homepage": "https://github.com/woocommerce/woocommerce-admin",
- "time": "2020-05-08T22:39:16+00:00"
+ "time": "2020-05-22T18:45:16+00:00"
},
{
"name": "woocommerce/woocommerce-blocks",
From 4756d4c9118a06995f8bbbc1b0aa13db0dc916a4 Mon Sep 17 00:00:00 2001
From: Julia Amosova
Date: Sun, 24 May 2020 18:27:08 -0400
Subject: [PATCH 080/153] Add asterisk to star ratings if required checkbox is
enabled
---
templates/single-product-reviews.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/single-product-reviews.php b/templates/single-product-reviews.php
index ecde5dbc72a..5fa9279b8ee 100644
--- a/templates/single-product-reviews.php
+++ b/templates/single-product-reviews.php
@@ -121,7 +121,7 @@ if ( ! comments_open() ) {
}
if ( wc_review_ratings_enabled() ) {
- $comment_form['comment_field'] = '