From 6eb7dcabf21566be53c81ad2c8f8f20f7fb46463 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Wed, 26 Apr 2017 10:18:41 -0400 Subject: [PATCH 001/224] Added WC_Meta_Data object It wraps an array, and tells if there were any changes before saving it --- includes/abstracts/abstract-wc-data.php | 18 ++++++---- includes/class-wc-meta-data.php | 48 +++++++++++++++++++++++++ woocommerce.php | 1 + 3 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 includes/class-wc-meta-data.php diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 28ddf87af65..3ff6096a1f4 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -320,11 +320,11 @@ abstract class WC_Data { foreach ( $data as $meta ) { $meta = (array) $meta; if ( isset( $meta['key'], $meta['value'], $meta['id'] ) ) { - $this->meta_data[] = (object) array( + $this->meta_data[] = new WC_Meta_Data( array( 'id' => $meta['id'], 'key' => $meta['key'], 'value' => $meta['value'], - ); + ) ); } } } @@ -343,10 +343,10 @@ abstract class WC_Data { if ( $unique ) { $this->delete_meta_data( $key ); } - $this->meta_data[] = (object) array( + $this->meta_data[] = new WC_Meta_Data( array( 'key' => $key, 'value' => $value, - ); + ) ); } /** @@ -448,11 +448,11 @@ abstract class WC_Data { $raw_meta_data = $this->data_store->read_meta( $this ); if ( $raw_meta_data ) { foreach ( $raw_meta_data as $meta ) { - $this->meta_data[] = (object) array( + $this->meta_data[] = new WC_Meta_Data( array( 'id' => (int) $meta->meta_id, 'key' => $meta->meta_key, 'value' => maybe_unserialize( $meta->meta_value ), - ); + ) ); } if ( ! empty( $this->cache_group ) ) { @@ -480,8 +480,12 @@ abstract class WC_Data { } elseif ( empty( $meta->id ) ) { $new_meta_id = $this->data_store->add_meta( $this, $meta ); $this->meta_data[ $array_key ]->id = $new_meta_id; + $meta->apply_changes(); } else { - $this->data_store->update_meta( $this, $meta ); + if ( $meta->get_changes() ) { + $this->data_store->update_meta( $this, $meta ); + $meta->apply_changes(); + } } } diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php new file mode 100644 index 00000000000..0f97e91cc23 --- /dev/null +++ b/includes/class-wc-meta-data.php @@ -0,0 +1,48 @@ + $value ) { + if ( in_array( $key, $this->properties ) ) { + $this->$key = $value; + } + } + $this->apply_changes(); + } + + /** + * Merge changes with data and clear. + * + */ + public function apply_changes() { + foreach ( $this->properties as $property ) { + $this->previous_value[ $property ] = $this->$property; + } + } + + /** + * Return data changes only. + * + * @return array + */ + public function get_changes() { + $changes = array(); + foreach ( array( 'id', 'key', 'value' ) as $property ) { + if ( array_key_exists( $property, $this->previous_value ) && + $this->previous_value[ $property ] !== $this->$property ) { + $changes[ $property ] = $this->$property; + } + } + + return $changes; + } + +} diff --git a/woocommerce.php b/woocommerce.php index f74c55a931a..9f526512683 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -306,6 +306,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-emails.php' ); include_once( WC_ABSPATH . 'includes/class-wc-data-exception.php' ); include_once( WC_ABSPATH . 'includes/class-wc-query.php' ); + include_once( WC_ABSPATH . 'incldues/class-wc-meta-data.php' ); // Meta data internal object include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller From af65da6494be1f08499ee35a7c51742ae9df0b73 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Wed, 26 Apr 2017 19:39:19 -0400 Subject: [PATCH 002/224] Added doc-comments --- includes/class-wc-meta-data.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php index 0f97e91cc23..bc1c9d63789 100644 --- a/includes/class-wc-meta-data.php +++ b/includes/class-wc-meta-data.php @@ -1,5 +1,16 @@ $value ) { if ( in_array( $key, $this->properties ) ) { @@ -20,7 +36,6 @@ class WC_Meta_Data { /** * Merge changes with data and clear. - * */ public function apply_changes() { foreach ( $this->properties as $property ) { From 2e6dc17bd506091df5f75ba0773811d2c71091f2 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Thu, 27 Apr 2017 09:28:19 -0400 Subject: [PATCH 003/224] Improved update_meta_data function Also updated unit tests that was expecting the meta data as stdclass objects --- includes/abstracts/abstract-wc-data.php | 8 +++----- tests/unit-tests/crud/data.php | 5 ++++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 3ff6096a1f4..c55da870337 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -360,11 +360,9 @@ abstract class WC_Data { public function update_meta_data( $key, $value, $meta_id = '' ) { $this->maybe_read_meta_data(); if ( $array_key = $meta_id ? array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id ) : '' ) { - $this->meta_data[ current( $array_key ) ] = (object) array( - 'id' => $meta_id, - 'key' => $key, - 'value' => $value, - ); + $meta = $this->meta_data[ current( $array_key ) ]; + $meta->key = $key; + $meta->value = $value; } else { $this->add_meta_data( $key, $value, true ); } diff --git a/tests/unit-tests/crud/data.php b/tests/unit-tests/crud/data.php index 3c5349db1ee..9b2e73d968c 100644 --- a/tests/unit-tests/crud/data.php +++ b/tests/unit-tests/crud/data.php @@ -202,7 +202,10 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { $object = new WC_Mock_WC_Data(); $object->set_meta_data( $metadata ); - $this->assertEquals( $metadata, $object->get_meta_data() ); + foreach ( $object->get_meta_data() as $id => $meta ) { + $this->assertEquals( get_object_vars( $metadata[ $id ] ), get_object_vars( $meta ) ); + } + } /** From de8b899d65416fd48cedaa390f44a735dcf54da9 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Thu, 27 Apr 2017 11:24:03 -0400 Subject: [PATCH 004/224] Fixed typo --- woocommerce.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/woocommerce.php b/woocommerce.php index 9f526512683..4426bcc1055 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -306,7 +306,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-emails.php' ); include_once( WC_ABSPATH . 'includes/class-wc-data-exception.php' ); include_once( WC_ABSPATH . 'includes/class-wc-query.php' ); - include_once( WC_ABSPATH . 'incldues/class-wc-meta-data.php' ); // Meta data internal object + include_once( WC_ABSPATH . 'includes/class-wc-meta-data.php' ); // Meta data internal object include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller From dafc795d7c2404675b32e72e1a280c546b5e0124 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Tue, 23 May 2017 19:15:13 -0400 Subject: [PATCH 005/224] Abstracting the WC_Meta_Data Properties with __set/__get --- includes/class-wc-meta-data.php | 55 +++++++++++++++++++++++---------- tests/unit-tests/crud/data.php | 6 ++-- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php index bc1c9d63789..94473bc45ec 100644 --- a/includes/class-wc-meta-data.php +++ b/includes/class-wc-meta-data.php @@ -1,5 +1,9 @@ $value ) { - if ( in_array( $key, $this->properties ) ) { - $this->$key = $value; - } - } + $this->current_data = $meta; $this->apply_changes(); } @@ -38,9 +46,22 @@ class WC_Meta_Data { * Merge changes with data and clear. */ public function apply_changes() { - foreach ( $this->properties as $property ) { - $this->previous_value[ $property ] = $this->$property; + $this->data = $this->current_data; + } + + public function __set( $key, $value ) { + $this->current_data[ $key ] = $value; + } + + public function __isset( $key ) { + return array_key_exists( $key, $this->current_data ); + } + + public function __get( $key ) { + if ( array_key_exists( $key, $this->current_data )) { + return $this->current_data[ $key ]; } + return null; } /** @@ -49,11 +70,11 @@ class WC_Meta_Data { * @return array */ public function get_changes() { + $changes = array(); - foreach ( array( 'id', 'key', 'value' ) as $property ) { - if ( array_key_exists( $property, $this->previous_value ) && - $this->previous_value[ $property ] !== $this->$property ) { - $changes[ $property ] = $this->$property; + foreach ( $this->current_data as $id => $value) { + if ( ! array_key_exists( $id, $this->data ) || $value !== $this->data[ $id ] ) { + $changes[ $id ] = $value; } } diff --git a/tests/unit-tests/crud/data.php b/tests/unit-tests/crud/data.php index 034f316921f..a44e5198924 100644 --- a/tests/unit-tests/crud/data.php +++ b/tests/unit-tests/crud/data.php @@ -139,7 +139,7 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { */ function test_get_meta_data_cache_invalidation() { $object = new WC_Order; - $object->add_meta_data( 'test_meta_key', 'val1', true ); + $object->add_meta_data( 'test_meta_key', array( 'val1' ), true ); $object->add_meta_data( 'test_multi_meta_key', 'val2' ); $object->add_meta_data( 'test_multi_meta_key', 'val3' ); $object->save(); @@ -242,7 +242,9 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { $object->set_meta_data( $metadata ); foreach ( $object->get_meta_data() as $id => $meta ) { - $this->assertEquals( get_object_vars( $metadata[ $id ] ), get_object_vars( $meta ) ); + $this->assertEquals( $metadata[ $id ]->id, $meta->id); + $this->assertEquals( $metadata[ $id ]->key, $meta->key); + $this->assertEquals( $metadata[ $id ]->value, $meta->value); } } From addc80de152709901e0e7d9854b42b8e9898c721 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Wed, 31 May 2017 09:39:13 -0400 Subject: [PATCH 006/224] Fixed coding styles --- includes/class-wc-meta-data.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php index 94473bc45ec..cae977849ea 100644 --- a/includes/class-wc-meta-data.php +++ b/includes/class-wc-meta-data.php @@ -49,14 +49,34 @@ class WC_Meta_Data { $this->data = $this->current_data; } + /** + * Creates or updates a property in the metadata object. + * + * @param string $key + * @param mixed $value + * + */ public function __set( $key, $value ) { $this->current_data[ $key ] = $value; } + /** + * Checks if a given key exists in our data. This is called internally + * by `empty` and `isset`. + * + * @param string $key + */ public function __isset( $key ) { return array_key_exists( $key, $this->current_data ); } + /** + * Returns the value of any property. + * + * @param string $key + * + * @return mixed Property value or NULL if it does not exists + */ public function __get( $key ) { if ( array_key_exists( $key, $this->current_data )) { return $this->current_data[ $key ]; @@ -70,7 +90,6 @@ class WC_Meta_Data { * @return array */ public function get_changes() { - $changes = array(); foreach ( $this->current_data as $id => $value) { if ( ! array_key_exists( $id, $this->data ) || $value !== $this->data[ $id ] ) { From 0d1451b719f7207c70c8dd386f82243c332320f6 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Wed, 31 May 2017 09:40:29 -0400 Subject: [PATCH 007/224] Fixed coding styles --- tests/unit-tests/crud/data.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit-tests/crud/data.php b/tests/unit-tests/crud/data.php index a44e5198924..2004641fe6b 100644 --- a/tests/unit-tests/crud/data.php +++ b/tests/unit-tests/crud/data.php @@ -242,9 +242,9 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { $object->set_meta_data( $metadata ); foreach ( $object->get_meta_data() as $id => $meta ) { - $this->assertEquals( $metadata[ $id ]->id, $meta->id); - $this->assertEquals( $metadata[ $id ]->key, $meta->key); - $this->assertEquals( $metadata[ $id ]->value, $meta->value); + $this->assertEquals( $metadata[ $id ]->id, $meta->id ); + $this->assertEquals( $metadata[ $id ]->key, $meta->key ); + $this->assertEquals( $metadata[ $id ]->value, $meta->value ); } } From 5997ffd4b1c9b7ab1c54149ad8275c402af8a9ce Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Thu, 1 Jun 2017 11:46:05 -0400 Subject: [PATCH 008/224] Fixed coding styles issues --- includes/abstracts/abstract-wc-data.php | 3 +-- includes/class-wc-meta-data.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index c89c179e170..22e645ff7d3 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -475,8 +475,7 @@ abstract class WC_Data { unset( $this->meta_data[ $array_key ] ); } } elseif ( empty( $meta->id ) ) { - $new_meta_id = $this->data_store->add_meta( $this, $meta ); - $this->meta_data[ $array_key ]->id = $new_meta_id; + $meta->id = $this->data_store->add_meta( $this, $meta ); $meta->apply_changes(); } else { if ( $meta->get_changes() ) { diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php index cae977849ea..2c45a8d2290 100644 --- a/includes/class-wc-meta-data.php +++ b/includes/class-wc-meta-data.php @@ -78,7 +78,7 @@ class WC_Meta_Data { * @return mixed Property value or NULL if it does not exists */ public function __get( $key ) { - if ( array_key_exists( $key, $this->current_data )) { + if ( array_key_exists( $key, $this->current_data ) ) { return $this->current_data[ $key ]; } return null; From 03a3ce9901998378165a25ab3979363dbb9e0bc5 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Thu, 1 Jun 2017 11:47:36 -0400 Subject: [PATCH 009/224] Fixed coding styles issues --- includes/class-wc-meta-data.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php index 2c45a8d2290..41595e1055a 100644 --- a/includes/class-wc-meta-data.php +++ b/includes/class-wc-meta-data.php @@ -91,7 +91,7 @@ class WC_Meta_Data { */ public function get_changes() { $changes = array(); - foreach ( $this->current_data as $id => $value) { + foreach ( $this->current_data as $id => $value ) { if ( ! array_key_exists( $id, $this->data ) || $value !== $this->data[ $id ] ) { $changes[ $id ] = $value; } From b0f3b5a19da4a5a221d1f2dd99d74c0b2de71a39 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Thu, 1 Jun 2017 12:03:13 -0400 Subject: [PATCH 010/224] Added test_get_meta_data_cache_invalidation_array_to_scalar() unit tests --- tests/unit-tests/crud/data.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unit-tests/crud/data.php b/tests/unit-tests/crud/data.php index 2004641fe6b..63a28b0db8f 100644 --- a/tests/unit-tests/crud/data.php +++ b/tests/unit-tests/crud/data.php @@ -138,6 +138,23 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { * Tests the cache invalidation after an order is saved */ function test_get_meta_data_cache_invalidation() { + $object = new WC_Order; + $object->add_meta_data( 'test_meta_key', 'val1', true ); + $object->add_meta_data( 'test_multi_meta_key', 'val2' ); + $object->add_meta_data( 'test_multi_meta_key', 'val3' ); + $object->save(); + + $order = new WC_Order( $object->get_id() ); + $metas = $order->get_meta_data(); + $metas[0]->value = 'updated value'; + $order->save(); + + $order = new WC_Order( $object->get_id() ); + $metas = $order->get_meta_data(); + $this->assertEquals( 'updated value', $metas[0]->value ); + } + + function test_get_meta_data_cache_invalidation_array_to_scalar() { $object = new WC_Order; $object->add_meta_data( 'test_meta_key', array( 'val1' ), true ); $object->add_meta_data( 'test_multi_meta_key', 'val2' ); From efd42e6bc43ae321e8b883801faa419a3ad26ab7 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Tue, 6 Jun 2017 19:10:24 +0200 Subject: [PATCH 011/224] Proof of concept to error when internal meta props are accessed directly --- includes/abstracts/abstract-wc-data.php | 17 +++++++++++++++++ includes/data-stores/class-wc-data-store-wp.php | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 976028f1333..4321c15f391 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -268,6 +268,18 @@ abstract class WC_Data { return array_filter( $this->meta_data, array( $this, 'filter_null_meta' ) ); } + /** + * Validate meta key. + * + * @since 3.x + * @param string $key + */ + private function validate_meta_key( $key ) { + if ( $this->data_store && ! empty( $key ) && in_array( $key, $this->data_store->get_internal_meta_keys() ) ) { + throw new Exception( __( 'Meta properties should not be accessed directly. Use getters and setters.', 'woocommerce' ) ); + } + } + /** * Get Meta Data by Key. * @@ -278,6 +290,7 @@ abstract class WC_Data { * @return mixed */ public function get_meta( $key = '', $single = true, $context = 'view' ) { + $this->validate_meta_key( $key ); $this->maybe_read_meta_data(); $meta_data = $this->get_meta_data(); $array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key ); @@ -307,6 +320,7 @@ abstract class WC_Data { * @return boolean */ public function meta_exists( $key = '' ) { + $this->validate_meta_key( $key ); $this->maybe_read_meta_data(); $array_keys = wp_list_pluck( $this->get_meta_data(), 'key' ); return in_array( $key, $array_keys ); @@ -343,6 +357,7 @@ abstract class WC_Data { * @param bool $unique Should this be a unique key? */ public function add_meta_data( $key, $value, $unique = false ) { + $this->validate_meta_key( $key ); $this->maybe_read_meta_data(); if ( $unique ) { $this->delete_meta_data( $key ); @@ -362,6 +377,7 @@ abstract class WC_Data { * @param int $meta_id */ public function update_meta_data( $key, $value, $meta_id = '' ) { + $this->validate_meta_key( $key ); $this->maybe_read_meta_data(); if ( $array_key = $meta_id ? array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id ) : '' ) { $this->meta_data[ current( $array_key ) ] = (object) array( @@ -381,6 +397,7 @@ abstract class WC_Data { * @param string $key Meta key */ public function delete_meta_data( $key ) { + $this->validate_meta_key( $key ); $this->maybe_read_meta_data(); if ( $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key ) ) { foreach ( $array_keys as $array_key ) { diff --git a/includes/data-stores/class-wc-data-store-wp.php b/includes/data-stores/class-wc-data-store-wp.php index cc0d53aa832..466df136dc3 100644 --- a/includes/data-stores/class-wc-data-store-wp.php +++ b/includes/data-stores/class-wc-data-store-wp.php @@ -417,4 +417,14 @@ class WC_Data_Store_WP { return $wp_query_args; } + + /** + * Return list of internal meta keys. + * + * @since 3.x + * @return array + */ + public function get_internal_meta_keys() { + return $this->internal_meta_keys; + } } From b5fd62274f93c326bb1632c51104dc2d1a1de99f Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Wed, 7 Jun 2017 12:30:03 +0200 Subject: [PATCH 012/224] Map the correct function and use notice instead of exception --- includes/abstracts/abstract-wc-data.php | 35 ++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 4321c15f391..93e051df30e 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -273,11 +273,15 @@ abstract class WC_Data { * * @since 3.x * @param string $key + * @return bool True if validation is successful, false otherwise */ private function validate_meta_key( $key ) { if ( $this->data_store && ! empty( $key ) && in_array( $key, $this->data_store->get_internal_meta_keys() ) ) { - throw new Exception( __( 'Meta properties should not be accessed directly. Use getters and setters.', 'woocommerce' ) ); + wc_doing_it_wrong( __FUNCTION__, __( 'Meta properties should not be accessed directly. Use getters and setters.', 'woocommerce' ), '3.x' ); + return false; } + + return true; } /** @@ -290,7 +294,14 @@ abstract class WC_Data { * @return mixed */ public function get_meta( $key = '', $single = true, $context = 'view' ) { - $this->validate_meta_key( $key ); + if ( ! $this->validate_meta_key() ) { + $function = 'get_' . $key; + + if ( is_callable( array( $this, $function ) ) ) { + return $this->{$function}(); + } + } + $this->maybe_read_meta_data(); $meta_data = $this->get_meta_data(); $array_keys = array_keys( wp_list_pluck( $meta_data, 'key' ), $key ); @@ -320,7 +331,6 @@ abstract class WC_Data { * @return boolean */ public function meta_exists( $key = '' ) { - $this->validate_meta_key( $key ); $this->maybe_read_meta_data(); $array_keys = wp_list_pluck( $this->get_meta_data(), 'key' ); return in_array( $key, $array_keys ); @@ -357,7 +367,14 @@ abstract class WC_Data { * @param bool $unique Should this be a unique key? */ public function add_meta_data( $key, $value, $unique = false ) { - $this->validate_meta_key( $key ); + if ( ! $this->validate_meta_key() ) { + $function = 'set_' . $key; + + if ( is_callable( array( $this, $function ) ) ) { + return $this->{$function}( $value ); + } + } + $this->maybe_read_meta_data(); if ( $unique ) { $this->delete_meta_data( $key ); @@ -377,7 +394,14 @@ abstract class WC_Data { * @param int $meta_id */ public function update_meta_data( $key, $value, $meta_id = '' ) { - $this->validate_meta_key( $key ); + if ( ! $this->validate_meta_key() ) { + $function = 'set_' . $key; + + if ( is_callable( array( $this, $function ) ) ) { + return $this->{$function}( $value ); + } + } + $this->maybe_read_meta_data(); if ( $array_key = $meta_id ? array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id ) : '' ) { $this->meta_data[ current( $array_key ) ] = (object) array( @@ -397,7 +421,6 @@ abstract class WC_Data { * @param string $key Meta key */ public function delete_meta_data( $key ) { - $this->validate_meta_key( $key ); $this->maybe_read_meta_data(); if ( $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key ) ) { foreach ( $array_keys as $array_key ) { From 1584836fa9ea7c8f27254087353591abeeda39d2 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Wed, 7 Jun 2017 12:31:42 +0200 Subject: [PATCH 013/224] Code style fixes --- includes/abstracts/abstract-wc-data.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 93e051df30e..8c9c6b8f924 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -403,7 +403,9 @@ abstract class WC_Data { } $this->maybe_read_meta_data(); - if ( $array_key = $meta_id ? array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id ) : '' ) { + $array_key = $meta_id ? array_keys( wp_list_pluck( $this->meta_data, 'id' ), $meta_id ) : ''; + + if ( $array_key ) { $this->meta_data[ current( $array_key ) ] = (object) array( 'id' => $meta_id, 'key' => $key, @@ -422,7 +424,9 @@ abstract class WC_Data { */ public function delete_meta_data( $key ) { $this->maybe_read_meta_data(); - if ( $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key ) ) { + $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'key' ), $key ); + + if ( $array_keys ) { foreach ( $array_keys as $array_key ) { $this->meta_data[ $array_key ]->value = null; } @@ -437,7 +441,9 @@ abstract class WC_Data { */ public function delete_meta_data_by_mid( $mid ) { $this->maybe_read_meta_data(); - if ( $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $mid ) ) { + $array_keys = array_keys( wp_list_pluck( $this->meta_data, 'id' ), $mid ); + + if ( $array_keys ) { foreach ( $array_keys as $array_key ) { $this->meta_data[ $array_key ]->value = null; } @@ -549,7 +555,7 @@ abstract class WC_Data { $this->data = $this->default_data; $this->changes = array(); $this->set_object_read( false ); - } + } /** * Set object read property. From 00847da73016e77f178acf72f3a2ab322148bc5d Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Wed, 7 Jun 2017 12:33:05 +0200 Subject: [PATCH 014/224] Fix --- includes/abstracts/abstract-wc-data.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 8c9c6b8f924..819e2d66c32 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -271,9 +271,9 @@ abstract class WC_Data { /** * Validate meta key. * - * @since 3.x - * @param string $key - * @return bool True if validation is successful, false otherwise + * @since 3.x + * @param string $key + * @return bool true if validation is successful, false otherwise */ private function validate_meta_key( $key ) { if ( $this->data_store && ! empty( $key ) && in_array( $key, $this->data_store->get_internal_meta_keys() ) ) { @@ -294,7 +294,7 @@ abstract class WC_Data { * @return mixed */ public function get_meta( $key = '', $single = true, $context = 'view' ) { - if ( ! $this->validate_meta_key() ) { + if ( ! $this->validate_meta_key( $key ) ) { $function = 'get_' . $key; if ( is_callable( array( $this, $function ) ) ) { @@ -367,7 +367,7 @@ abstract class WC_Data { * @param bool $unique Should this be a unique key? */ public function add_meta_data( $key, $value, $unique = false ) { - if ( ! $this->validate_meta_key() ) { + if ( ! $this->validate_meta_key( $key ) ) { $function = 'set_' . $key; if ( is_callable( array( $this, $function ) ) ) { @@ -394,7 +394,7 @@ abstract class WC_Data { * @param int $meta_id */ public function update_meta_data( $key, $value, $meta_id = '' ) { - if ( ! $this->validate_meta_key() ) { + if ( ! $this->validate_meta_key( $key ) ) { $function = 'set_' . $key; if ( is_callable( array( $this, $function ) ) ) { From 0271a9e2fe26f0bb6cc72f0ee1889184923e0578 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Wed, 7 Jun 2017 16:20:09 +0200 Subject: [PATCH 015/224] Update version --- includes/abstracts/abstract-wc-data.php | 4 ++-- includes/data-stores/class-wc-data-store-wp.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 819e2d66c32..efb8d346f82 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -271,13 +271,13 @@ abstract class WC_Data { /** * Validate meta key. * - * @since 3.x + * @since 3.2.0 * @param string $key * @return bool true if validation is successful, false otherwise */ private function validate_meta_key( $key ) { if ( $this->data_store && ! empty( $key ) && in_array( $key, $this->data_store->get_internal_meta_keys() ) ) { - wc_doing_it_wrong( __FUNCTION__, __( 'Meta properties should not be accessed directly. Use getters and setters.', 'woocommerce' ), '3.x' ); + wc_doing_it_wrong( __FUNCTION__, __( 'Meta properties should not be accessed directly. Use getters and setters.', 'woocommerce' ), '3.2.0' ); return false; } diff --git a/includes/data-stores/class-wc-data-store-wp.php b/includes/data-stores/class-wc-data-store-wp.php index 466df136dc3..f58003eb80e 100644 --- a/includes/data-stores/class-wc-data-store-wp.php +++ b/includes/data-stores/class-wc-data-store-wp.php @@ -421,7 +421,7 @@ class WC_Data_Store_WP { /** * Return list of internal meta keys. * - * @since 3.x + * @since 3.2.0 * @return array */ public function get_internal_meta_keys() { From 68d512ae0103216cdaca968b80a6af2084b191d3 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Thu, 8 Jun 2017 13:00:28 +0200 Subject: [PATCH 016/224] Address PR comments --- includes/abstracts/abstract-wc-data.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index efb8d346f82..db586bd4104 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -269,19 +269,20 @@ abstract class WC_Data { } /** - * Validate meta key. + * Check if the key is an internal one. * * @since 3.2.0 * @param string $key - * @return bool true if validation is successful, false otherwise + * @return bool true if it's an internal key, false otherwise */ - private function validate_meta_key( $key ) { + protected function is_internal_meta_key( $key ) { if ( $this->data_store && ! empty( $key ) && in_array( $key, $this->data_store->get_internal_meta_keys() ) ) { wc_doing_it_wrong( __FUNCTION__, __( 'Meta properties should not be accessed directly. Use getters and setters.', 'woocommerce' ), '3.2.0' ); - return false; + + return true; } - return true; + return false; } /** @@ -294,7 +295,7 @@ abstract class WC_Data { * @return mixed */ public function get_meta( $key = '', $single = true, $context = 'view' ) { - if ( ! $this->validate_meta_key( $key ) ) { + if ( $this->is_internal_meta_key( $key ) ) { $function = 'get_' . $key; if ( is_callable( array( $this, $function ) ) ) { @@ -367,7 +368,7 @@ abstract class WC_Data { * @param bool $unique Should this be a unique key? */ public function add_meta_data( $key, $value, $unique = false ) { - if ( ! $this->validate_meta_key( $key ) ) { + if ( $this->is_internal_meta_key( $key ) ) { $function = 'set_' . $key; if ( is_callable( array( $this, $function ) ) ) { @@ -394,7 +395,7 @@ abstract class WC_Data { * @param int $meta_id */ public function update_meta_data( $key, $value, $meta_id = '' ) { - if ( ! $this->validate_meta_key( $key ) ) { + if ( $this->is_internal_meta_key( $key ) ) { $function = 'set_' . $key; if ( is_callable( array( $this, $function ) ) ) { From 0f134d2b6f846e36b37112f3d75e806ed6d8eda4 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Wed, 12 Jul 2017 07:44:53 -0700 Subject: [PATCH 017/224] Begin product query --- includes/class-wc-product-query.php | 82 +++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 includes/class-wc-product-query.php diff --git a/includes/class-wc-product-query.php b/includes/class-wc-product-query.php new file mode 100644 index 00000000000..25db7087051 --- /dev/null +++ b/includes/class-wc-product-query.php @@ -0,0 +1,82 @@ + 'publish', + 'type' => array( 'product', 'product_variation' ), + + + ) + ); + } + + /** + * Get products matching the current query vars. + * @return array of WC_Product objects + */ + public function get_products() { + $args = apply_filters( 'woocommerce_product_query_args', $this->get_query_vars() ); + $results = array();//WC_Data_Store::load( 'product' )->query( $args ); + return apply_filters( 'woocommerce_product_query', $results, $args ); + } +} From 753979ec49e2348b3926558028519cdf4c5f5a22 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Wed, 12 Jul 2017 13:58:39 -0700 Subject: [PATCH 018/224] WC_Product_Query --- includes/class-wc-product-query.php | 93 ++++++++++--------- .../class-wc-product-data-store-cpt.php | 93 +++++++++++++++++++ tests/unit-tests/product/query.php | 66 +++++++++++++ woocommerce.php | 1 + 4 files changed, 207 insertions(+), 46 deletions(-) create mode 100644 tests/unit-tests/product/query.php diff --git a/includes/class-wc-product-query.php b/includes/class-wc-product-query.php index 25db7087051..b06c5fe6d09 100644 --- a/includes/class-wc-product-query.php +++ b/includes/class-wc-product-query.php @@ -13,47 +13,7 @@ if ( ! defined( 'ABSPATH' ) ) { * @category Class */ class WC_Product_Query extends WC_Object_Query { -/* - '_visibility', - '_sku', - '_price', - '_regular_price', - '_sale_price', - '_sale_price_dates_from', - '_sale_price_dates_to', - 'total_sales', - '_tax_status', - '_tax_class', - '_manage_stock', - '_stock', - '_stock_status', - '_backorders', - '_sold_individually', - '_weight', - '_length', - '_width', - '_height', - '_upsell_ids', - '_crosssell_ids', - '_purchase_note', - '_default_attributes', - '_product_attributes', - '_virtual', - '_downloadable', - '_featured', - '_downloadable_files', - '_wc_rating_count', - '_wc_average_rating', - '_wc_review_count', - '_variation_description', - '_thumbnail_id', - '_file_paths', - '_product_image_gallery', - '_product_version', - '_wp_old_slug', - '_edit_last', - '_edit_lock', -*/ + /** * Valid query vars for products. * @return array @@ -62,10 +22,51 @@ class WC_Product_Query extends WC_Object_Query { return array_merge( parent::get_default_query_vars(), array( - 'status' => 'publish', - 'type' => array( 'product', 'product_variation' ), - - + 'status' => 'publish', + 'type' => array( 'product', 'product_variation' ), + 'slug' => '', + 'date_created' => '', + 'date_modified' => '', + 'featured' => '', + 'catalog_visibility' => '', + 'description' => '', + 'short_description' => '', + 'sku' => '', + 'price' => '', + 'regular_price' => '', + 'sale_price' => '', + 'date_on_sale_from' => '', + 'date_on_sale_to' => '', + 'total_sales' => '', + 'tax_status' => '', + 'tax_class' => '', + 'manage_stock' => '', + 'stock_quantity' => '', + 'stock_status' => '', + 'backorders' => '', + 'sold_individually' => '', + 'weight' => '', + 'length' => '', + 'width' => '', + 'height' => '', + 'upsell_ids' => array(), + 'cross_sell_ids' => array(), + 'reviews_allowed' => '', + 'purchase_note' => '', + 'attributes' => array(), + 'default_attributes' => array(), + 'menu_order' => '', + 'virtual' => '', + 'downloadable' => '', + 'category_ids' => array(), + 'tag_ids' => array(), + 'shipping_class_id' => '', + 'image_id' => '', + 'download_limit' => '', + 'download_expiry' => '', + 'rating_counts' => array(), + 'average_rating' => '', + 'review_count' => '', ) ); } @@ -76,7 +77,7 @@ class WC_Product_Query extends WC_Object_Query { */ public function get_products() { $args = apply_filters( 'woocommerce_product_query_args', $this->get_query_vars() ); - $results = array();//WC_Data_Store::load( 'product' )->query( $args ); + $results = WC_Data_Store::load( 'product' )->query( $args ); return apply_filters( 'woocommerce_product_query', $results, $args ); } } diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 5fa2b9e0d4d..fab4b883a31 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1372,4 +1372,97 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da return false; } } + + + /** + * Get valid WP_Query args from a WC_Product_Query's query variables. + * + * @since 3.2.0 + * @param array $query_vars query vars from a WC_Product_Query + * @return array + */ + protected function get_wp_query_args( $query_vars ) { + + // Map query vars to ones that get_wp_query_args or WP_Query recognize. + $key_mapping = array( + 'status' => 'post_status', + 'page' => 'paged', + ); + + foreach ( $key_mapping as $query_key => $db_key ) { + if ( isset( $query_vars[ $query_key ] ) ) { + $query_vars[ $db_key ] = $query_vars[ $query_key ]; + unset( $query_vars[ $query_key ] ); + } + } + + $wp_query_args = parent::get_wp_query_args( $query_vars ); + + if ( ! isset( $wp_query_args['date_query'] ) ) { + $wp_query_args['date_query'] = array(); + } + if ( ! isset( $wp_query_args['meta_query'] ) ) { + $wp_query_args['meta_query'] = array(); + } + + $date_queries = array( + 'date_created' => 'post_date', + 'date_modified' => 'post_modified', + 'date_on_sale_from' => '_sale_price_dates_from', + 'date_on_sale_to' => '_sale_price_dates_to', + ); + foreach ( $date_queries as $query_var_key => $db_key ) { + if ( isset( $query_vars[ $query_var_key ] ) && '' !== $query_vars[ $query_var_key ] ) { + + // Remove any existing meta queries for the same keys to prevent conflicts. + $existing_queries = wp_list_pluck( $wp_query_args['meta_query'], 'key', true ); + foreach ( $existing_queries as $query_index => $query_contents ) { + unset( $wp_query_args['meta_query'][ $query_index ] ); + } + + $wp_query_args = $this->parse_date_for_wp_query( $query_vars[ $query_var_key ], $db_key, $wp_query_args ); + } + } + + if ( ! isset( $query_vars['paginate'] ) || ! $query_vars['paginate'] ) { + $wp_query_args['no_found_rows'] = true; + } + + return apply_filters( 'woocommerce_product_data_store_cpt_get_products_query', $wp_query_args, $query_vars, $this ); + } + + /** + * Query for Products matching specific criteria. + * + * @since 3.2.0 + * + * @param array $query_vars query vars from a WC_Product_Query + * + * @return array|object + */ + public function query( $query_vars ) { + $args = $this->get_wp_query_args( $query_vars ); + + if ( ! empty( $args['errors'] ) ) { + $query = (object) array( + 'posts' => array(), + 'found_posts' => 0, + 'max_num_pages' => 0, + ); + } else { + $query = new WP_Query( $args ); + } + + $products = ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) ? $query->posts : array_filter( array_map( 'wc_get_product', $query->posts ) ); + + if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) { + return (object) array( + 'products' => $products, + 'total' => $query->found_posts, + 'max_num_pages' => $query->max_num_pages, + ); + } + + return $products; + } } diff --git a/tests/unit-tests/product/query.php b/tests/unit-tests/product/query.php new file mode 100644 index 00000000000..f1d497bb43a --- /dev/null +++ b/tests/unit-tests/product/query.php @@ -0,0 +1,66 @@ +assertEquals( '', $query->get( 'weight' ) ); + $this->assertEquals( array( 'product', 'product_variation' ), $query->get( 'type' ) ); + } + + /** + * Test querying by product properties. + * + * @since 3.2 + */ + public function test_order_query_standard() { + $product1 = new WC_Product_Simple(); + $product1->set_sku( 'sku1' ); + $product1->set_regular_price( '10.00' ); + $product1->set_sale_price( '5.00' ); + $product1->save(); + + $product2 = new WC_Product_Variation(); + $product2->set_sku( 'sku2' ); + $product2->set_regular_price( '12.50' ); + $product2->set_sale_price( '5.00' ); + $product2->save(); + + // Just get some products. + $query = new WC_Product_Query(); + $results = $query->get_products(); + $this->assertEquals( 2, count( $results ) ); + + // Get products with a specific property.. + $query->set( 'sku', 'sku2' ); + $results = $query->get_products(); + $this->assertEquals( 1, count( $results ) ); + $this->assertEquals( $results[0]->get_id(), $product2->get_id() ); + + // Get products with two specific properties. + $query->set( 'regular_price', '12.50' ); + $results = $query->get_products(); + $this->assertEquals( 1, count( $results ) ); + $this->assertEquals( $results[0]->get_id(), $product2->get_id() ); + + // Get multiple products that have the same specific property. + $query = new WC_Product_Query(); + $query->set( 'sale_price', '5.00' ); + $results = $query->get_products(); + $this->assertEquals( 2, count( $results ) ); + + // Limit to one result. + $query->set( 'limit', 1 ); + $results = $query->get_products(); + $this->assertEquals( 1, count( $results ) ); + } +} diff --git a/woocommerce.php b/woocommerce.php index f46d0e3fce7..3dbafb11820 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -327,6 +327,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory include_once( WC_ABSPATH . 'includes/class-wc-order-query.php' ); // Order query include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory + include_once( WC_ABSPATH . 'includes/class-wc-product-query.php' ); // Product query include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller include_once( WC_ABSPATH . 'includes/class-wc-shipping-zone.php' ); include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway From 7be2c7dc55ff07849ed7078d8a8dfab6487e5beb Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Wed, 12 Jul 2017 14:18:51 -0700 Subject: [PATCH 019/224] More tests --- tests/unit-tests/product/query.php | 86 +++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/tests/unit-tests/product/query.php b/tests/unit-tests/product/query.php index f1d497bb43a..ec70a930006 100644 --- a/tests/unit-tests/product/query.php +++ b/tests/unit-tests/product/query.php @@ -11,7 +11,7 @@ class WC_Tests_WC_Product_Query extends WC_Unit_Test_Case { * * @since 3.2 */ - public function test_order_query_new() { + public function test_product_query_new() { $query = new WC_Product_Query(); $this->assertEquals( '', $query->get( 'weight' ) ); $this->assertEquals( array( 'product', 'product_variation' ), $query->get( 'type' ) ); @@ -22,7 +22,7 @@ class WC_Tests_WC_Product_Query extends WC_Unit_Test_Case { * * @since 3.2 */ - public function test_order_query_standard() { + public function test_product_query_standard() { $product1 = new WC_Product_Simple(); $product1->set_sku( 'sku1' ); $product1->set_regular_price( '10.00' ); @@ -63,4 +63,86 @@ class WC_Tests_WC_Product_Query extends WC_Unit_Test_Case { $results = $query->get_products(); $this->assertEquals( 1, count( $results ) ); } + + + /** + * Test querying by product date properties for dates stored in metadata. + * + * @since 3.2 + */ + public function test_product_query_meta_date_queries() { + $now = current_time( 'mysql', true ); + $now_stamp = strtotime( $now ); + $now_date = date( 'Y-m-d', $now_stamp ); + $past_stamp = $now_stamp - DAY_IN_SECONDS; + $past = date( 'Y-m-d', $past_stamp ); + $future_stamp = $now_stamp + DAY_IN_SECONDS; + $future = date( 'Y-m-d', $future_stamp ); + + $product = new WC_Product_Simple(); + $product->set_date_on_sale_from( $now_stamp ); + $product->save(); + + // Check WC_DateTime support. + $query = new WC_Product_Query( array( + 'date_on_sale_from' => $product->get_date_on_sale_from(), + ) ); + $products = $query->get_products(); + $this->assertEquals( 1, count( $products ) ); + + // Check date support. + $query = new WC_Product_Query( array( + 'date_on_sale_from' => $now_date, + ) ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', $past ); + $this->assertEquals( 0, count( $query->get_products() ) ); + + // Check timestamp support. + $query = new WC_Product_Query( array( + 'date_on_sale_from' => $product->get_date_on_sale_from()->getTimestamp(), + ) ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', $future_stamp ); + $this->assertEquals( 0, count( $query->get_products() ) ); + + // Check comparison support. + $query = new WC_Product_Query( array( + 'date_on_sale_from' => '>' . $past, + ) ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', '<' . $past ); + $this->assertEquals( 0, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', '>=' . $now_date ); + $this->assertEquals( 1, count( $query->get_products() ) ); + + // Check timestamp comparison support. + $query = new WC_Product_Query( array( + 'date_on_sale_from' => '<' . $future_stamp, + ) ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', '<' . $past_stamp ); + $this->assertEquals( 0, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', '>=' . $now_stamp ); + + // Check date range support. + $query = new WC_Product_Query( array( + 'date_on_sale_from' => $past . '...' . $future, + ) ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', $now_date . '...' . $future ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', $future . '...' . $now_date ); + $this->assertEquals( 0, count( $query->get_products() ) ); + + // Check timestamp range support. + $query = new WC_Product_Query( array( + 'date_on_sale_from' => $past_stamp . '...' . $future_stamp, + ) ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', $now_stamp . '...' . $future_stamp ); + $this->assertEquals( 1, count( $query->get_products() ) ); + $query->set( 'date_on_sale_from', $future_stamp . '...' . $now_stamp ); + $this->assertEquals( 0, count( $query->get_products() ) ); + } } From bf9a7381dc8099042726a195680f7a3587196102 Mon Sep 17 00:00:00 2001 From: Ninos Ego Date: Thu, 13 Jul 2017 13:15:58 +0200 Subject: [PATCH 020/224] Coding improvements for datepicker range --- assets/js/admin/meta-boxes-product-variation.js | 4 ++-- assets/js/admin/meta-boxes-product.js | 9 ++++----- assets/js/admin/reports.js | 5 ++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/assets/js/admin/meta-boxes-product-variation.js b/assets/js/admin/meta-boxes-product-variation.js index bc3e644583e..17819a83276 100644 --- a/assets/js/admin/meta-boxes-product-variation.js +++ b/assets/js/admin/meta-boxes-product-variation.js @@ -123,10 +123,10 @@ jQuery( function( $ ) { dateFormat: 'yy-mm-dd', numberOfMonths: 1, showButtonPanel: true, - onSelect: function( selectedDate, instance ) { + onSelect: function() { var option = $( this ).is( '.sale_price_dates_from' ) ? 'minDate' : 'maxDate', dates = $( this ).closest( '.sale_price_dates_fields' ).find( 'input' ), - date = $.datepicker.parseDate( instance.settings.dateFormat || $.datepicker._defaults.dateFormat, selectedDate, instance.settings ); + date = $( this ).datepicker( 'getDate' ); dates.not( this ).datepicker( 'option', option, date ); $( this ).change(); diff --git a/assets/js/admin/meta-boxes-product.js b/assets/js/admin/meta-boxes-product.js index f236cbb6dfc..31a6882a76a 100644 --- a/assets/js/admin/meta-boxes-product.js +++ b/assets/js/admin/meta-boxes-product.js @@ -244,11 +244,10 @@ jQuery( function( $ ) { dateFormat: 'yy-mm-dd', numberOfMonths: 1, showButtonPanel: true, - onSelect: function( selectedDate ) { - var option = $( this ).next().is('.hasDatepicker') ? 'minDate' : 'maxDate', - instance = $( this ).data( 'datepicker' ), - dates = $( this ).closest( '.sale_price_dates_fields' ).find( 'input' ), - date = $.datepicker.parseDate( instance.settings.dateFormat || $.datepicker._defaults.dateFormat, selectedDate, instance.settings ); + onSelect: function() { + var option = $( this ).next().is('.hasDatepicker') ? 'minDate' : 'maxDate', + dates = $( this ).closest( '.sale_price_dates_fields' ).find( 'input' ), + date = $( this ).datepicker( 'getDate' ); dates.not( this ).datepicker( 'option', option, date ); $( this ).change(); diff --git a/assets/js/admin/reports.js b/assets/js/admin/reports.js index 0a3d1e8e0b5..6005d15fb3f 100644 --- a/assets/js/admin/reports.js +++ b/assets/js/admin/reports.js @@ -115,10 +115,9 @@ jQuery(function( $ ) { showButtonPanel: true, showOn: 'focus', buttonImageOnly: true, - onSelect: function( selectedDate ) { + onSelect: function() { var option = $( this ).is( '.from' ) ? 'minDate' : 'maxDate', - instance = $( this ).data( 'datepicker' ), - date = $.datepicker.parseDate( instance.settings.dateFormat || $.datepicker._defaults.dateFormat, selectedDate, instance.settings ); + date = $( this ).datepicker( 'getDate' ); dates.not( this ).datepicker( 'option', option, date ); } From ac4d2db43aaee4362367f1a91e4de2d71eda3c67 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 13 Jul 2017 14:50:30 +0100 Subject: [PATCH 021/224] Improve tracking page validation Ensures error messages are vague but reflect the posted data. Closes #15986 --- .../class-wc-shortcode-order-tracking.php | 28 ++++++++----------- templates/order/form-tracking.php | 4 +-- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/includes/shortcodes/class-wc-shortcode-order-tracking.php b/includes/shortcodes/class-wc-shortcode-order-tracking.php index b12d84743e6..145807f49c9 100644 --- a/includes/shortcodes/class-wc-shortcode-order-tracking.php +++ b/includes/shortcodes/class-wc-shortcode-order-tracking.php @@ -32,7 +32,6 @@ class WC_Shortcode_Order_Tracking { * @param array $atts */ public static function output( $atts ) { - // Check cart class is loaded or abort if ( is_null( WC()->cart ) ) { return; @@ -40,31 +39,26 @@ class WC_Shortcode_Order_Tracking { extract( shortcode_atts( array(), $atts, 'woocommerce_order_tracking' ) ); - global $post; + if ( isset( $_REQUEST['orderid'], $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-order_tracking' ) ) { - if ( ! empty( $_REQUEST['orderid'] ) && isset( $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-order_tracking' ) ) { - - $order_id = empty( $_REQUEST['orderid'] ) ? 0 : esc_attr( $_REQUEST['orderid'] ); - $order_email = empty( $_REQUEST['order_email'] ) ? '' : esc_attr( $_REQUEST['order_email'] ); + $order_id = empty( $_REQUEST['orderid'] ) ? 0 : wc_clean( ltrim( $_REQUEST['orderid'], '#' ) ); + $order_email = empty( $_REQUEST['order_email'] ) ? '' : sanitize_email( $_REQUEST['order_email'] ); if ( ! $order_id ) { wc_add_notice( __( 'Please enter a valid order ID', 'woocommerce' ), 'error' ); } elseif ( ! $order_email ) { - wc_add_notice( __( 'Please enter a valid order email', 'woocommerce' ), 'error' ); + wc_add_notice( __( 'Please enter a valid email address', 'woocommerce' ), 'error' ); } else { $order = wc_get_order( apply_filters( 'woocommerce_shortcode_order_tracking_order_id', $order_id ) ); - if ( $order && $order->get_id() && $order_email ) { - if ( strtolower( $order->get_billing_email() ) == strtolower( $order_email ) ) { - do_action( 'woocommerce_track_order', $order->get_id() ); - wc_get_template( 'order/tracking.php', array( - 'order' => $order, - ) ); - - return; - } + if ( $order && $order->get_id() && strtolower( $order->get_billing_email() ) === strtolower( $order_email ) ) { + do_action( 'woocommerce_track_order', $order->get_id() ); + wc_get_template( 'order/tracking.php', array( + 'order' => $order, + ) ); + return; } else { - wc_add_notice( __( 'Sorry, we could not find that order ID in our database.', 'woocommerce' ), 'error' ); + wc_add_notice( __( 'Sorry, the order could not be found. Please contact us if you are having difficulty finding your order details.', 'woocommerce' ), 'error' ); } } } diff --git a/templates/order/form-tracking.php b/templates/order/form-tracking.php index a390c6e537b..72c6e3b28fe 100644 --- a/templates/order/form-tracking.php +++ b/templates/order/form-tracking.php @@ -28,8 +28,8 @@ global $post;

-

-

+

+

From f37e066d97f6fe32587f63549729daced7ba5900 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 13 Jul 2017 15:19:17 -0300 Subject: [PATCH 022/224] [REST API] Allow OPTIONS requests again --- includes/api/class-wc-rest-authentication.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/api/class-wc-rest-authentication.php b/includes/api/class-wc-rest-authentication.php index 138fbd68bb8..ae4e008474b 100644 --- a/includes/api/class-wc-rest-authentication.php +++ b/includes/api/class-wc-rest-authentication.php @@ -533,6 +533,8 @@ class WC_REST_Authentication { return new WP_Error( 'woocommerce_rest_authentication_error', __( 'The API key provided does not have write permissions.', 'woocommerce' ), array( 'status' => 401 ) ); } break; + case 'OPTIONS' : + return true; default : return new WP_Error( 'woocommerce_rest_authentication_error', __( 'Unknown request method.', 'woocommerce' ), array( 'status' => 401 ) ); From e7ffb9f910bee7413c22c02db2f2f4ae1732a3c5 Mon Sep 17 00:00:00 2001 From: Aristeides Stathopoulos Date: Tue, 11 Jul 2017 02:51:27 +0300 Subject: [PATCH 023/224] Move WooCommerce class to separate file. --- includes/class-woocommerce.php | 642 +++++++++++++++++++++++++++++++++ woocommerce.php | 638 +------------------------------- 2 files changed, 645 insertions(+), 635 deletions(-) create mode 100644 includes/class-woocommerce.php diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php new file mode 100644 index 00000000000..c6721568d95 --- /dev/null +++ b/includes/class-woocommerce.php @@ -0,0 +1,642 @@ +$key(); + } + } + + /** + * WooCommerce Constructor. + */ + public function __construct() { + $this->define_constants(); + $this->includes(); + $this->init_hooks(); + + do_action( 'woocommerce_loaded' ); + } + + /** + * Hook into actions and filters. + * @since 2.3 + */ + private function init_hooks() { + register_activation_hook( __FILE__, array( 'WC_Install', 'install' ) ); + register_shutdown_function( array( $this, 'log_errors' ) ); + add_action( 'after_setup_theme', array( $this, 'setup_environment' ) ); + add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 ); + add_action( 'init', array( $this, 'init' ), 0 ); + add_action( 'init', array( 'WC_Shortcodes', 'init' ) ); + add_action( 'init', array( 'WC_Emails', 'init_transactional_emails' ) ); + add_action( 'init', array( $this, 'wpdb_table_fix' ), 0 ); + add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 ); + } + + /** + * Ensures fatal errors are logged so they can be picked up in the status report. + * + * @since 3.2.0 + */ + public function log_errors() { + $error = error_get_last(); + if ( E_ERROR === $error['type'] ) { + $logger = wc_get_logger(); + $logger->critical( + $error['message'] . PHP_EOL, + array( 'source' => 'fatal-errors' ) + ); + } + } + + /** + * Define WC Constants. + */ + private function define_constants() { + $upload_dir = wp_upload_dir(); + + $this->define( 'WC_PLUGIN_FILE', __FILE__ ); + $this->define( 'WC_ABSPATH', dirname( __FILE__ ) . '/' ); + $this->define( 'WC_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); + $this->define( 'WC_VERSION', $this->version ); + $this->define( 'WOOCOMMERCE_VERSION', $this->version ); + $this->define( 'WC_ROUNDING_PRECISION', 4 ); + $this->define( 'WC_DISCOUNT_ROUNDING_MODE', 2 ); + $this->define( 'WC_TAX_ROUNDING_MODE', 'yes' === get_option( 'woocommerce_prices_include_tax', 'no' ) ? 2 : 1 ); + $this->define( 'WC_DELIMITER', '|' ); + $this->define( 'WC_LOG_DIR', $upload_dir['basedir'] . '/wc-logs/' ); + $this->define( 'WC_SESSION_CACHE_GROUP', 'wc_session_id' ); + $this->define( 'WC_TEMPLATE_DEBUG_MODE', false ); + } + + /** + * Define constant if not already set. + * + * @param string $name + * @param string|bool $value + */ + private function define( $name, $value ) { + if ( ! defined( $name ) ) { + define( $name, $value ); + } + } + + /** + * What type of request is this? + * + * @param string $type admin, ajax, cron or frontend. + * @return bool + */ + private function is_request( $type ) { + switch ( $type ) { + case 'admin' : + return is_admin(); + case 'ajax' : + return defined( 'DOING_AJAX' ); + case 'cron' : + return defined( 'DOING_CRON' ); + case 'frontend' : + return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' ); + } + } + + /** + * Check the active theme. + * + * @since 2.6.9 + * @param string $theme Theme slug to check + * @return bool + */ + private function is_active_theme( $theme ) { + return get_template() === $theme; + } + + /** + * Include required core files used in admin and on the frontend. + */ + public function includes() { + /** + * Class autoloader. + */ + include_once( WC_ABSPATH . 'includes/class-wc-autoloader.php' ); + + /** + * Interfaces. + */ + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-abstract-order-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-coupon-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-download-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-object-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-product-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-type-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-refund-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-payment-token-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-product-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-product-variable-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-shipping-zone-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-logger-interface.php' ); + include_once( WC_ABSPATH . 'includes/interfaces/class-wc-log-handler-interface.php' ); + + /** + * Abstract classes. + */ + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-data.php' ); // WC_Data for CRUD + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-object-query.php' ); // WC_Object_Query for CRUD + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-token.php' ); // Payment Tokens + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-product.php' ); // Products + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-order.php' ); // Orders + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-settings-api.php' ); // Settings API (for gateways, shipping, and integrations) + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-shipping-method.php' ); // A Shipping method + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-gateway.php' ); // A Payment gateway + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-log-handler.php' ); + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-deprecated-hooks.php' ); + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-session.php' ); + + /** + * Core classes. + */ + include_once( WC_ABSPATH . 'includes/wc-core-functions.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-datetime.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-post-types.php' ); // Registers post types + include_once( WC_ABSPATH . 'includes/class-wc-install.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-geolocation.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-download-handler.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-comments.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-post-data.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-ajax.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-emails.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-data-exception.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-query.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory + include_once( WC_ABSPATH . 'includes/class-wc-order-query.php' ); // Order query + include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory + include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller + include_once( WC_ABSPATH . 'includes/class-wc-shipping-zone.php' ); + include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway + include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-echeck.php' ); // eCheck Payment Gateway + include_once( WC_ABSPATH . 'includes/class-wc-countries.php' ); // Defines countries and states + include_once( WC_ABSPATH . 'includes/class-wc-integrations.php' ); // Loads integrations + include_once( WC_ABSPATH . 'includes/class-wc-cache-helper.php' ); // Cache Helper + include_once( WC_ABSPATH . 'includes/class-wc-https.php' ); // https Helper + include_once( WC_ABSPATH . 'includes/class-wc-deprecated-action-hooks.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-deprecated-filter-hooks.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-background-emailer.php' ); + + /** + * Data stores - used to store and retrieve CRUD object data from the database. + */ + include_once( WC_ABSPATH . 'includes/class-wc-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-data-store-wp.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-coupon-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-grouped-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variable-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variation-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-item-type-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-coupon-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-fee-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-product-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-shipping-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-tax-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-payment-token-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store-session.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-download-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-shipping-zone-data-store.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-refund-data-store-cpt.php' ); + + /** + * REST API. + */ + include_once( WC_ABSPATH . 'includes/class-wc-legacy-api.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-api.php' ); // API Class + include_once( WC_ABSPATH . 'includes/class-wc-auth.php' ); // Auth Class + include_once( WC_ABSPATH . 'includes/class-wc-register-wp-admin-settings.php' ); + + if ( defined( 'WP_CLI' ) && WP_CLI ) { + include_once( WC_ABSPATH . 'includes/class-wc-cli.php' ); + } + + if ( $this->is_request( 'admin' ) ) { + include_once( WC_ABSPATH . 'includes/admin/class-wc-admin.php' ); + } + + if ( $this->is_request( 'frontend' ) ) { + $this->frontend_includes(); + } + + if ( $this->is_request( 'frontend' ) || $this->is_request( 'cron' ) ) { + include_once( WC_ABSPATH . 'includes/class-wc-session-handler.php' ); + } + + if ( $this->is_request( 'cron' ) && 'yes' === get_option( 'woocommerce_allow_tracking', 'no' ) ) { + include_once( WC_ABSPATH . 'includes/class-wc-tracker.php' ); + } + + $this->query = new WC_Query(); + $this->api = new WC_API(); + } + + /** + * Include required frontend files. + */ + public function frontend_includes() { + include_once( WC_ABSPATH . 'includes/wc-cart-functions.php' ); + include_once( WC_ABSPATH . 'includes/wc-notice-functions.php' ); + include_once( WC_ABSPATH . 'includes/wc-template-hooks.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-template-loader.php' ); // Template Loader + include_once( WC_ABSPATH . 'includes/class-wc-frontend-scripts.php' ); // Frontend Scripts + include_once( WC_ABSPATH . 'includes/class-wc-form-handler.php' ); // Form Handlers + include_once( WC_ABSPATH . 'includes/class-wc-cart.php' ); // The main cart class + include_once( WC_ABSPATH . 'includes/class-wc-tax.php' ); // Tax class + include_once( WC_ABSPATH . 'includes/class-wc-shipping-zones.php' ); // Shipping Zones class + include_once( WC_ABSPATH . 'includes/class-wc-customer.php' ); // Customer class + include_once( WC_ABSPATH . 'includes/class-wc-shortcodes.php' ); // Shortcodes class + include_once( WC_ABSPATH . 'includes/class-wc-embed.php' ); // Embeds + include_once( WC_ABSPATH . 'includes/class-wc-structured-data.php' ); // Structured Data class + + if ( $this->is_active_theme( 'twentyseventeen' ) ) { + include_once( WC_ABSPATH . 'includes/theme-support/class-wc-twenty-seventeen.php' ); + } + } + + /** + * Function used to Init WooCommerce Template Functions - This makes them pluggable by plugins and themes. + */ + public function include_template_functions() { + include_once( WC_ABSPATH . 'includes/wc-template-functions.php' ); + } + + /** + * Init WooCommerce when WordPress Initialises. + */ + public function init() { + // Before init action. + do_action( 'before_woocommerce_init' ); + + // Set up localisation. + $this->load_plugin_textdomain(); + + // Load class instances. + $this->product_factory = new WC_Product_Factory(); // Product Factory to create new product instances + $this->order_factory = new WC_Order_Factory(); // Order Factory to create new order instances + $this->countries = new WC_Countries(); // Countries class + $this->integrations = new WC_Integrations(); // Integrations class + $this->structured_data = new WC_Structured_Data(); // Structured Data class, generates and handles structured data + $this->deprecated_hook_handlers['actions'] = new WC_Deprecated_Action_Hooks(); + $this->deprecated_hook_handlers['filters'] = new WC_Deprecated_Filter_Hooks(); + + // Session class, handles session data for users - can be overwritten if custom handler is needed. + if ( $this->is_request( 'frontend' ) || $this->is_request( 'cron' ) ) { + $session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' ); + $this->session = new $session_class(); + } + + // Classes/actions loaded for the frontend and for ajax requests. + if ( $this->is_request( 'frontend' ) ) { + $this->cart = new WC_Cart(); // Cart class, stores the cart contents + $this->customer = new WC_Customer( get_current_user_id(), true ); // Customer class, handles data such as customer location + add_action( 'shutdown', array( $this->customer, 'save' ), 10 ); // Customer should be saved during shutdown. + } + + $this->load_webhooks(); + + // Init action. + do_action( 'woocommerce_init' ); + } + + /** + * Load Localisation files. + * + * Note: the first-loaded translation file overrides any following ones if the same translation is present. + * + * Locales found in: + * - WP_LANG_DIR/woocommerce/woocommerce-LOCALE.mo + * - WP_LANG_DIR/plugins/woocommerce-LOCALE.mo + */ + public function load_plugin_textdomain() { + $locale = is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(); + $locale = apply_filters( 'plugin_locale', $locale, 'woocommerce' ); + + unload_textdomain( 'woocommerce' ); + load_textdomain( 'woocommerce', WP_LANG_DIR . '/woocommerce/woocommerce-' . $locale . '.mo' ); + load_plugin_textdomain( 'woocommerce', false, plugin_basename( dirname( __FILE__ ) ) . '/i18n/languages' ); + } + + /** + * Ensure theme and server variable compatibility and setup image sizes. + */ + public function setup_environment() { + /** + * @deprecated 2.2 Use WC()->template_path() + */ + $this->define( 'WC_TEMPLATE_PATH', $this->template_path() ); + + $this->add_thumbnail_support(); + $this->add_image_sizes(); + } + + /** + * Ensure post thumbnail support is turned on. + */ + private function add_thumbnail_support() { + if ( ! current_theme_supports( 'post-thumbnails' ) ) { + add_theme_support( 'post-thumbnails' ); + } + add_post_type_support( 'product', 'thumbnail' ); + } + + /** + * Add WC Image sizes to WP. + * + * @since 2.3 + */ + private function add_image_sizes() { + $shop_thumbnail = wc_get_image_size( 'shop_thumbnail' ); + $shop_catalog = wc_get_image_size( 'shop_catalog' ); + $shop_single = wc_get_image_size( 'shop_single' ); + + add_image_size( 'shop_thumbnail', $shop_thumbnail['width'], $shop_thumbnail['height'], $shop_thumbnail['crop'] ); + add_image_size( 'shop_catalog', $shop_catalog['width'], $shop_catalog['height'], $shop_catalog['crop'] ); + add_image_size( 'shop_single', $shop_single['width'], $shop_single['height'], $shop_single['crop'] ); + } + + /** + * Get the plugin url. + * @return string + */ + public function plugin_url() { + return untrailingslashit( plugins_url( '/', __FILE__ ) ); + } + + /** + * Get the plugin path. + * @return string + */ + public function plugin_path() { + return untrailingslashit( plugin_dir_path( __FILE__ ) ); + } + + /** + * Get the template path. + * @return string + */ + public function template_path() { + return apply_filters( 'woocommerce_template_path', 'woocommerce/' ); + } + + /** + * Get Ajax URL. + * @return string + */ + public function ajax_url() { + return admin_url( 'admin-ajax.php', 'relative' ); + } + + /** + * Return the WC API URL for a given request. + * + * @param string $request + * @param mixed $ssl (default: null) + * @return string + */ + public function api_request_url( $request, $ssl = null ) { + if ( is_null( $ssl ) ) { + $scheme = parse_url( home_url(), PHP_URL_SCHEME ); + } elseif ( $ssl ) { + $scheme = 'https'; + } else { + $scheme = 'http'; + } + + if ( strstr( get_option( 'permalink_structure' ), '/index.php/' ) ) { + $api_request_url = trailingslashit( home_url( '/index.php/wc-api/' . $request, $scheme ) ); + } elseif ( get_option( 'permalink_structure' ) ) { + $api_request_url = trailingslashit( home_url( '/wc-api/' . $request, $scheme ) ); + } else { + $api_request_url = add_query_arg( 'wc-api', $request, trailingslashit( home_url( '', $scheme ) ) ); + } + + return esc_url_raw( apply_filters( 'woocommerce_api_request_url', $api_request_url, $request, $ssl ) ); + } + + /** + * Load & enqueue active webhooks. + * + * @since 2.2 + */ + private function load_webhooks() { + + if ( ! is_blog_installed() ) { + return; + } + + if ( false === ( $webhooks = get_transient( 'woocommerce_webhook_ids' ) ) ) { + $webhooks = get_posts( array( + 'fields' => 'ids', + 'post_type' => 'shop_webhook', + 'post_status' => 'publish', + 'posts_per_page' => -1, + ) ); + set_transient( 'woocommerce_webhook_ids', $webhooks ); + } + foreach ( $webhooks as $webhook_id ) { + $webhook = new WC_Webhook( $webhook_id ); + $webhook->enqueue(); + } + } + + /** + * WooCommerce Payment Token Meta API and Term/Order item Meta - set table names. + */ + public function wpdb_table_fix() { + global $wpdb; + $wpdb->payment_tokenmeta = $wpdb->prefix . 'woocommerce_payment_tokenmeta'; + $wpdb->order_itemmeta = $wpdb->prefix . 'woocommerce_order_itemmeta'; + $wpdb->tables[] = 'woocommerce_payment_tokenmeta'; + $wpdb->tables[] = 'woocommerce_order_itemmeta'; + + if ( get_option( 'db_version' ) < 34370 ) { + $wpdb->woocommerce_termmeta = $wpdb->prefix . 'woocommerce_termmeta'; + $wpdb->tables[] = 'woocommerce_termmeta'; + } + } + + /** + * Get Checkout Class. + * @return WC_Checkout + */ + public function checkout() { + return WC_Checkout::instance(); + } + + /** + * Get gateways class. + * @return WC_Payment_Gateways + */ + public function payment_gateways() { + return WC_Payment_Gateways::instance(); + } + + /** + * Get shipping class. + * @return WC_Shipping + */ + public function shipping() { + return WC_Shipping::instance(); + } + + /** + * Email Class. + * @return WC_Emails + */ + public function mailer() { + return WC_Emails::instance(); + } +} diff --git a/woocommerce.php b/woocommerce.php index f46d0e3fce7..68987862ec5 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -20,643 +20,11 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } -if ( ! class_exists( 'WooCommerce' ) ) : - -/** - * Main WooCommerce Class. - * - * @class WooCommerce - * @version 3.2.0 - */ -final class WooCommerce { - - /** - * WooCommerce version. - * - * @var string - */ - public $version = '3.2.0'; - - /** - * The single instance of the class. - * - * @var WooCommerce - * @since 2.1 - */ - protected static $_instance = null; - - /** - * Session instance. - * - * @var WC_Session|WC_Session_Handler - */ - public $session = null; - - /** - * Query instance. - * - * @var WC_Query - */ - public $query = null; - - /** - * Product factory instance. - * - * @var WC_Product_Factory - */ - public $product_factory = null; - - /** - * Countries instance. - * - * @var WC_Countries - */ - public $countries = null; - - /** - * Integrations instance. - * - * @var WC_Integrations - */ - public $integrations = null; - - /** - * Cart instance. - * - * @var WC_Cart - */ - public $cart = null; - - /** - * Customer instance. - * - * @var WC_Customer - */ - public $customer = null; - - /** - * Order factory instance. - * - * @var WC_Order_Factory - */ - public $order_factory = null; - - /** - * Structured data instance. - * - * @var WC_Structured_Data - */ - public $structured_data = null; - - /** - * Array of deprecated hook handlers. - * - * @var array of WC_Deprecated_Hooks - */ - public $deprecated_hook_handlers = array(); - - /** - * Main WooCommerce Instance. - * - * Ensures only one instance of WooCommerce is loaded or can be loaded. - * - * @since 2.1 - * @static - * @see WC() - * @return WooCommerce - Main instance. - */ - public static function instance() { - if ( is_null( self::$_instance ) ) { - self::$_instance = new self(); - } - return self::$_instance; - } - - /** - * Cloning is forbidden. - * @since 2.1 - */ - public function __clone() { - wc_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'woocommerce' ), '2.1' ); - } - - /** - * Unserializing instances of this class is forbidden. - * @since 2.1 - */ - public function __wakeup() { - wc_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'woocommerce' ), '2.1' ); - } - - /** - * Auto-load in-accessible properties on demand. - * @param mixed $key - * @return mixed - */ - public function __get( $key ) { - if ( in_array( $key, array( 'payment_gateways', 'shipping', 'mailer', 'checkout' ) ) ) { - return $this->$key(); - } - } - - /** - * WooCommerce Constructor. - */ - public function __construct() { - $this->define_constants(); - $this->includes(); - $this->init_hooks(); - - do_action( 'woocommerce_loaded' ); - } - - /** - * Hook into actions and filters. - * @since 2.3 - */ - private function init_hooks() { - register_activation_hook( __FILE__, array( 'WC_Install', 'install' ) ); - register_shutdown_function( array( $this, 'log_errors' ) ); - add_action( 'after_setup_theme', array( $this, 'setup_environment' ) ); - add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 ); - add_action( 'init', array( $this, 'init' ), 0 ); - add_action( 'init', array( 'WC_Shortcodes', 'init' ) ); - add_action( 'init', array( 'WC_Emails', 'init_transactional_emails' ) ); - add_action( 'init', array( $this, 'wpdb_table_fix' ), 0 ); - add_action( 'switch_blog', array( $this, 'wpdb_table_fix' ), 0 ); - } - - /** - * Ensures fatal errors are logged so they can be picked up in the status report. - * - * @since 3.2.0 - */ - public function log_errors() { - $error = error_get_last(); - if ( E_ERROR === $error['type'] ) { - $logger = wc_get_logger(); - $logger->critical( - $error['message'] . PHP_EOL, - array( 'source' => 'fatal-errors' ) - ); - } - } - - /** - * Define WC Constants. - */ - private function define_constants() { - $upload_dir = wp_upload_dir(); - - $this->define( 'WC_PLUGIN_FILE', __FILE__ ); - $this->define( 'WC_ABSPATH', dirname( __FILE__ ) . '/' ); - $this->define( 'WC_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); - $this->define( 'WC_VERSION', $this->version ); - $this->define( 'WOOCOMMERCE_VERSION', $this->version ); - $this->define( 'WC_ROUNDING_PRECISION', 4 ); - $this->define( 'WC_DISCOUNT_ROUNDING_MODE', 2 ); - $this->define( 'WC_TAX_ROUNDING_MODE', 'yes' === get_option( 'woocommerce_prices_include_tax', 'no' ) ? 2 : 1 ); - $this->define( 'WC_DELIMITER', '|' ); - $this->define( 'WC_LOG_DIR', $upload_dir['basedir'] . '/wc-logs/' ); - $this->define( 'WC_SESSION_CACHE_GROUP', 'wc_session_id' ); - $this->define( 'WC_TEMPLATE_DEBUG_MODE', false ); - } - - /** - * Define constant if not already set. - * - * @param string $name - * @param string|bool $value - */ - private function define( $name, $value ) { - if ( ! defined( $name ) ) { - define( $name, $value ); - } - } - - /** - * What type of request is this? - * - * @param string $type admin, ajax, cron or frontend. - * @return bool - */ - private function is_request( $type ) { - switch ( $type ) { - case 'admin' : - return is_admin(); - case 'ajax' : - return defined( 'DOING_AJAX' ); - case 'cron' : - return defined( 'DOING_CRON' ); - case 'frontend' : - return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' ); - } - } - - /** - * Check the active theme. - * - * @since 2.6.9 - * @param string $theme Theme slug to check - * @return bool - */ - private function is_active_theme( $theme ) { - return get_template() === $theme; - } - - /** - * Include required core files used in admin and on the frontend. - */ - public function includes() { - /** - * Class autoloader. - */ - include_once( WC_ABSPATH . 'includes/class-wc-autoloader.php' ); - - /** - * Interfaces. - */ - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-abstract-order-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-coupon-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-customer-download-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-object-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-product-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-item-type-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-order-refund-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-payment-token-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-product-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-product-variable-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-shipping-zone-data-store-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-logger-interface.php' ); - include_once( WC_ABSPATH . 'includes/interfaces/class-wc-log-handler-interface.php' ); - - /** - * Abstract classes. - */ - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-data.php' ); // WC_Data for CRUD - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-object-query.php' ); // WC_Object_Query for CRUD - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-token.php' ); // Payment Tokens - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-product.php' ); // Products - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-order.php' ); // Orders - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-settings-api.php' ); // Settings API (for gateways, shipping, and integrations) - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-shipping-method.php' ); // A Shipping method - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-gateway.php' ); // A Payment gateway - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-log-handler.php' ); - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-deprecated-hooks.php' ); - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-session.php' ); - - /** - * Core classes. - */ - include_once( WC_ABSPATH . 'includes/wc-core-functions.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-datetime.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-post-types.php' ); // Registers post types - include_once( WC_ABSPATH . 'includes/class-wc-install.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-geolocation.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-download-handler.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-comments.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-post-data.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-ajax.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-emails.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-data-exception.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-query.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory - include_once( WC_ABSPATH . 'includes/class-wc-order-query.php' ); // Order query - include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory - include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller - include_once( WC_ABSPATH . 'includes/class-wc-shipping-zone.php' ); - include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway - include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-echeck.php' ); // eCheck Payment Gateway - include_once( WC_ABSPATH . 'includes/class-wc-countries.php' ); // Defines countries and states - include_once( WC_ABSPATH . 'includes/class-wc-integrations.php' ); // Loads integrations - include_once( WC_ABSPATH . 'includes/class-wc-cache-helper.php' ); // Cache Helper - include_once( WC_ABSPATH . 'includes/class-wc-https.php' ); // https Helper - include_once( WC_ABSPATH . 'includes/class-wc-deprecated-action-hooks.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-deprecated-filter-hooks.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-background-emailer.php' ); - - /** - * Data stores - used to store and retrieve CRUD object data from the database. - */ - include_once( WC_ABSPATH . 'includes/class-wc-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-data-store-wp.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-coupon-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-grouped-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variable-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variation-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-item-type-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-coupon-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-fee-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-product-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-shipping-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-item-tax-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-payment-token-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store-session.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-download-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-shipping-zone-data-store.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-refund-data-store-cpt.php' ); - - /** - * REST API. - */ - include_once( WC_ABSPATH . 'includes/class-wc-legacy-api.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-api.php' ); // API Class - include_once( WC_ABSPATH . 'includes/class-wc-auth.php' ); // Auth Class - include_once( WC_ABSPATH . 'includes/class-wc-register-wp-admin-settings.php' ); - - if ( defined( 'WP_CLI' ) && WP_CLI ) { - include_once( WC_ABSPATH . 'includes/class-wc-cli.php' ); - } - - if ( $this->is_request( 'admin' ) ) { - include_once( WC_ABSPATH . 'includes/admin/class-wc-admin.php' ); - } - - if ( $this->is_request( 'frontend' ) ) { - $this->frontend_includes(); - } - - if ( $this->is_request( 'frontend' ) || $this->is_request( 'cron' ) ) { - include_once( WC_ABSPATH . 'includes/class-wc-session-handler.php' ); - } - - if ( $this->is_request( 'cron' ) && 'yes' === get_option( 'woocommerce_allow_tracking', 'no' ) ) { - include_once( WC_ABSPATH . 'includes/class-wc-tracker.php' ); - } - - $this->query = new WC_Query(); - $this->api = new WC_API(); - } - - /** - * Include required frontend files. - */ - public function frontend_includes() { - include_once( WC_ABSPATH . 'includes/wc-cart-functions.php' ); - include_once( WC_ABSPATH . 'includes/wc-notice-functions.php' ); - include_once( WC_ABSPATH . 'includes/wc-template-hooks.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-template-loader.php' ); // Template Loader - include_once( WC_ABSPATH . 'includes/class-wc-frontend-scripts.php' ); // Frontend Scripts - include_once( WC_ABSPATH . 'includes/class-wc-form-handler.php' ); // Form Handlers - include_once( WC_ABSPATH . 'includes/class-wc-cart.php' ); // The main cart class - include_once( WC_ABSPATH . 'includes/class-wc-tax.php' ); // Tax class - include_once( WC_ABSPATH . 'includes/class-wc-shipping-zones.php' ); // Shipping Zones class - include_once( WC_ABSPATH . 'includes/class-wc-customer.php' ); // Customer class - include_once( WC_ABSPATH . 'includes/class-wc-shortcodes.php' ); // Shortcodes class - include_once( WC_ABSPATH . 'includes/class-wc-embed.php' ); // Embeds - include_once( WC_ABSPATH . 'includes/class-wc-structured-data.php' ); // Structured Data class - - if ( $this->is_active_theme( 'twentyseventeen' ) ) { - include_once( WC_ABSPATH . 'includes/theme-support/class-wc-twenty-seventeen.php' ); - } - } - - /** - * Function used to Init WooCommerce Template Functions - This makes them pluggable by plugins and themes. - */ - public function include_template_functions() { - include_once( WC_ABSPATH . 'includes/wc-template-functions.php' ); - } - - /** - * Init WooCommerce when WordPress Initialises. - */ - public function init() { - // Before init action. - do_action( 'before_woocommerce_init' ); - - // Set up localisation. - $this->load_plugin_textdomain(); - - // Load class instances. - $this->product_factory = new WC_Product_Factory(); // Product Factory to create new product instances - $this->order_factory = new WC_Order_Factory(); // Order Factory to create new order instances - $this->countries = new WC_Countries(); // Countries class - $this->integrations = new WC_Integrations(); // Integrations class - $this->structured_data = new WC_Structured_Data(); // Structured Data class, generates and handles structured data - $this->deprecated_hook_handlers['actions'] = new WC_Deprecated_Action_Hooks(); - $this->deprecated_hook_handlers['filters'] = new WC_Deprecated_Filter_Hooks(); - - // Session class, handles session data for users - can be overwritten if custom handler is needed. - if ( $this->is_request( 'frontend' ) || $this->is_request( 'cron' ) ) { - $session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' ); - $this->session = new $session_class(); - } - - // Classes/actions loaded for the frontend and for ajax requests. - if ( $this->is_request( 'frontend' ) ) { - $this->cart = new WC_Cart(); // Cart class, stores the cart contents - $this->customer = new WC_Customer( get_current_user_id(), true ); // Customer class, handles data such as customer location - add_action( 'shutdown', array( $this->customer, 'save' ), 10 ); // Customer should be saved during shutdown. - } - - $this->load_webhooks(); - - // Init action. - do_action( 'woocommerce_init' ); - } - - /** - * Load Localisation files. - * - * Note: the first-loaded translation file overrides any following ones if the same translation is present. - * - * Locales found in: - * - WP_LANG_DIR/woocommerce/woocommerce-LOCALE.mo - * - WP_LANG_DIR/plugins/woocommerce-LOCALE.mo - */ - public function load_plugin_textdomain() { - $locale = is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(); - $locale = apply_filters( 'plugin_locale', $locale, 'woocommerce' ); - - unload_textdomain( 'woocommerce' ); - load_textdomain( 'woocommerce', WP_LANG_DIR . '/woocommerce/woocommerce-' . $locale . '.mo' ); - load_plugin_textdomain( 'woocommerce', false, plugin_basename( dirname( __FILE__ ) ) . '/i18n/languages' ); - } - - /** - * Ensure theme and server variable compatibility and setup image sizes. - */ - public function setup_environment() { - /** - * @deprecated 2.2 Use WC()->template_path() - */ - $this->define( 'WC_TEMPLATE_PATH', $this->template_path() ); - - $this->add_thumbnail_support(); - $this->add_image_sizes(); - } - - /** - * Ensure post thumbnail support is turned on. - */ - private function add_thumbnail_support() { - if ( ! current_theme_supports( 'post-thumbnails' ) ) { - add_theme_support( 'post-thumbnails' ); - } - add_post_type_support( 'product', 'thumbnail' ); - } - - /** - * Add WC Image sizes to WP. - * - * @since 2.3 - */ - private function add_image_sizes() { - $shop_thumbnail = wc_get_image_size( 'shop_thumbnail' ); - $shop_catalog = wc_get_image_size( 'shop_catalog' ); - $shop_single = wc_get_image_size( 'shop_single' ); - - add_image_size( 'shop_thumbnail', $shop_thumbnail['width'], $shop_thumbnail['height'], $shop_thumbnail['crop'] ); - add_image_size( 'shop_catalog', $shop_catalog['width'], $shop_catalog['height'], $shop_catalog['crop'] ); - add_image_size( 'shop_single', $shop_single['width'], $shop_single['height'], $shop_single['crop'] ); - } - - /** - * Get the plugin url. - * @return string - */ - public function plugin_url() { - return untrailingslashit( plugins_url( '/', __FILE__ ) ); - } - - /** - * Get the plugin path. - * @return string - */ - public function plugin_path() { - return untrailingslashit( plugin_dir_path( __FILE__ ) ); - } - - /** - * Get the template path. - * @return string - */ - public function template_path() { - return apply_filters( 'woocommerce_template_path', 'woocommerce/' ); - } - - /** - * Get Ajax URL. - * @return string - */ - public function ajax_url() { - return admin_url( 'admin-ajax.php', 'relative' ); - } - - /** - * Return the WC API URL for a given request. - * - * @param string $request - * @param mixed $ssl (default: null) - * @return string - */ - public function api_request_url( $request, $ssl = null ) { - if ( is_null( $ssl ) ) { - $scheme = parse_url( home_url(), PHP_URL_SCHEME ); - } elseif ( $ssl ) { - $scheme = 'https'; - } else { - $scheme = 'http'; - } - - if ( strstr( get_option( 'permalink_structure' ), '/index.php/' ) ) { - $api_request_url = trailingslashit( home_url( '/index.php/wc-api/' . $request, $scheme ) ); - } elseif ( get_option( 'permalink_structure' ) ) { - $api_request_url = trailingslashit( home_url( '/wc-api/' . $request, $scheme ) ); - } else { - $api_request_url = add_query_arg( 'wc-api', $request, trailingslashit( home_url( '', $scheme ) ) ); - } - - return esc_url_raw( apply_filters( 'woocommerce_api_request_url', $api_request_url, $request, $ssl ) ); - } - - /** - * Load & enqueue active webhooks. - * - * @since 2.2 - */ - private function load_webhooks() { - - if ( ! is_blog_installed() ) { - return; - } - - if ( false === ( $webhooks = get_transient( 'woocommerce_webhook_ids' ) ) ) { - $webhooks = get_posts( array( - 'fields' => 'ids', - 'post_type' => 'shop_webhook', - 'post_status' => 'publish', - 'posts_per_page' => -1, - ) ); - set_transient( 'woocommerce_webhook_ids', $webhooks ); - } - foreach ( $webhooks as $webhook_id ) { - $webhook = new WC_Webhook( $webhook_id ); - $webhook->enqueue(); - } - } - - /** - * WooCommerce Payment Token Meta API and Term/Order item Meta - set table names. - */ - public function wpdb_table_fix() { - global $wpdb; - $wpdb->payment_tokenmeta = $wpdb->prefix . 'woocommerce_payment_tokenmeta'; - $wpdb->order_itemmeta = $wpdb->prefix . 'woocommerce_order_itemmeta'; - $wpdb->tables[] = 'woocommerce_payment_tokenmeta'; - $wpdb->tables[] = 'woocommerce_order_itemmeta'; - - if ( get_option( 'db_version' ) < 34370 ) { - $wpdb->woocommerce_termmeta = $wpdb->prefix . 'woocommerce_termmeta'; - $wpdb->tables[] = 'woocommerce_termmeta'; - } - } - - /** - * Get Checkout Class. - * @return WC_Checkout - */ - public function checkout() { - return WC_Checkout::instance(); - } - - /** - * Get gateways class. - * @return WC_Payment_Gateways - */ - public function payment_gateways() { - return WC_Payment_Gateways::instance(); - } - - /** - * Get shipping class. - * @return WC_Shipping - */ - public function shipping() { - return WC_Shipping::instance(); - } - - /** - * Email Class. - * @return WC_Emails - */ - public function mailer() { - return WC_Emails::instance(); - } +// Include the main WooCommerce class. +if ( ! class_exists( 'WooCommerce' ) ) { + include_once dirname( __FILE__ ) . '/includes/class-woocommerce.php'; } -endif; - /** * Main instance of WooCommerce. * From e4bf9b3d78716589f59cc4f1d3210b12d6a832fd Mon Sep 17 00:00:00 2001 From: Aristeides Stathopoulos Date: Tue, 11 Jul 2017 02:53:54 +0300 Subject: [PATCH 024/224] Properly define constants after moving class outside main plugin file. --- includes/class-woocommerce.php | 11 ++++------- woocommerce.php | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index c6721568d95..5b1949aa7c9 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -161,7 +161,7 @@ final class WooCommerce { * @since 2.3 */ private function init_hooks() { - register_activation_hook( __FILE__, array( 'WC_Install', 'install' ) ); + register_activation_hook( WC_PLUGIN_FILE, array( 'WC_Install', 'install' ) ); register_shutdown_function( array( $this, 'log_errors' ) ); add_action( 'after_setup_theme', array( $this, 'setup_environment' ) ); add_action( 'after_setup_theme', array( $this, 'include_template_functions' ), 11 ); @@ -194,9 +194,6 @@ final class WooCommerce { private function define_constants() { $upload_dir = wp_upload_dir(); - $this->define( 'WC_PLUGIN_FILE', __FILE__ ); - $this->define( 'WC_ABSPATH', dirname( __FILE__ ) . '/' ); - $this->define( 'WC_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); $this->define( 'WC_VERSION', $this->version ); $this->define( 'WOOCOMMERCE_VERSION', $this->version ); $this->define( 'WC_ROUNDING_PRECISION', 4 ); @@ -466,7 +463,7 @@ final class WooCommerce { unload_textdomain( 'woocommerce' ); load_textdomain( 'woocommerce', WP_LANG_DIR . '/woocommerce/woocommerce-' . $locale . '.mo' ); - load_plugin_textdomain( 'woocommerce', false, plugin_basename( dirname( __FILE__ ) ) . '/i18n/languages' ); + load_plugin_textdomain( 'woocommerce', false, plugin_basename( dirname( WC_PLUGIN_FILE ) ) . '/i18n/languages' ); } /** @@ -512,7 +509,7 @@ final class WooCommerce { * @return string */ public function plugin_url() { - return untrailingslashit( plugins_url( '/', __FILE__ ) ); + return untrailingslashit( plugins_url( '/', WC_PLUGIN_FILE ) ); } /** @@ -520,7 +517,7 @@ final class WooCommerce { * @return string */ public function plugin_path() { - return untrailingslashit( plugin_dir_path( __FILE__ ) ); + return untrailingslashit( plugin_dir_path( WC_PLUGIN_FILE ) ); } /** diff --git a/woocommerce.php b/woocommerce.php index 68987862ec5..f700af07e3e 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -20,6 +20,21 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } +// Define WC_PLUGIN_FILE. +if ( ! defined( 'WC_PLUGIN_FILE' ) ) { + define( 'WC_PLUGIN_FILE', __FILE__ ); +} + +// Define WC_ABSPATH. +if ( ! defined( 'WC_ABSPATH' ) ) { + define( 'WC_ABSPATH', dirname( __FILE__ ) . '/' ); +} + +// Define WC_PLUGIN_BASENAME. +if ( ! defined( 'WC_PLUGIN_BASENAME' ) ) { + define( 'WC_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); +} + // Include the main WooCommerce class. if ( ! class_exists( 'WooCommerce' ) ) { include_once dirname( __FILE__ ) . '/includes/class-woocommerce.php'; From 89bbd4acfa7be3d4114a806694661065ce0d5306 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 13 Jul 2017 15:46:34 -0300 Subject: [PATCH 025/224] Prevent direct access --- includes/class-woocommerce.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 5b1949aa7c9..5c88335e875 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -2,12 +2,16 @@ /** * Main WooCommerce Class * - * @author WooThemes + * @author Automattic * @category API * @package WooCommerce * @since 3.2.0 */ +if ( ! defined( 'ABSPATH' ) ) { + exit; // Exit if accessed directly. +} + /** * Main WooCommerce Class. * From fe550d53221d30fe21881303d6931994c0de6d1a Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 13 Jul 2017 15:57:46 -0300 Subject: [PATCH 026/224] Fixed coding standards --- includes/class-woocommerce.php | 120 ++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 54 deletions(-) diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 5c88335e875..08dd8e650cc 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -124,6 +124,7 @@ final class WooCommerce { /** * Cloning is forbidden. + * * @since 2.1 */ public function __clone() { @@ -132,6 +133,7 @@ final class WooCommerce { /** * Unserializing instances of this class is forbidden. + * * @since 2.1 */ public function __wakeup() { @@ -140,11 +142,12 @@ final class WooCommerce { /** * Auto-load in-accessible properties on demand. - * @param mixed $key + * + * @param mixed $key Key name. * @return mixed */ public function __get( $key ) { - if ( in_array( $key, array( 'payment_gateways', 'shipping', 'mailer', 'checkout' ) ) ) { + if ( in_array( $key, array( 'payment_gateways', 'shipping', 'mailer', 'checkout' ), true ) ) { return $this->$key(); } } @@ -162,7 +165,8 @@ final class WooCommerce { /** * Hook into actions and filters. - * @since 2.3 + * + * @since 2.3 */ private function init_hooks() { register_activation_hook( WC_PLUGIN_FILE, array( 'WC_Install', 'install' ) ); @@ -187,7 +191,9 @@ final class WooCommerce { $logger = wc_get_logger(); $logger->critical( $error['message'] . PHP_EOL, - array( 'source' => 'fatal-errors' ) + array( + 'source' => 'fatal-errors', + ) ); } } @@ -212,8 +218,8 @@ final class WooCommerce { /** * Define constant if not already set. * - * @param string $name - * @param string|bool $value + * @param string $name Constant name. + * @param string|bool $value Constant value. */ private function define( $name, $value ) { if ( ! defined( $name ) ) { @@ -244,7 +250,7 @@ final class WooCommerce { * Check the active theme. * * @since 2.6.9 - * @param string $theme Theme slug to check + * @param string $theme Theme slug to check. * @return bool */ private function is_active_theme( $theme ) { @@ -283,15 +289,15 @@ final class WooCommerce { /** * Abstract classes. */ - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-data.php' ); // WC_Data for CRUD - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-object-query.php' ); // WC_Object_Query for CRUD - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-token.php' ); // Payment Tokens - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-product.php' ); // Products - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-order.php' ); // Orders - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-settings-api.php' ); // Settings API (for gateways, shipping, and integrations) - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-shipping-method.php' ); // A Shipping method - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-gateway.php' ); // A Payment gateway - include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-data.php' ); // WC_Data for CRUD. + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-object-query.php' ); // WC_Object_Query for CRUD. + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-token.php' ); // Payment Tokens. + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-product.php' ); // Products. + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-order.php' ); // Orders. + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-settings-api.php' ); // Settings API (for gateways, shipping, and integrations). + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-shipping-method.php' ); // A Shipping method. + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-gateway.php' ); // A Payment gateway. + include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service. include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-log-handler.php' ); include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-deprecated-hooks.php' ); include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-session.php' ); @@ -301,7 +307,7 @@ final class WooCommerce { */ include_once( WC_ABSPATH . 'includes/wc-core-functions.php' ); include_once( WC_ABSPATH . 'includes/class-wc-datetime.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-post-types.php' ); // Registers post types + include_once( WC_ABSPATH . 'includes/class-wc-post-types.php' ); // Registers post types. include_once( WC_ABSPATH . 'includes/class-wc-install.php' ); include_once( WC_ABSPATH . 'includes/class-wc-geolocation.php' ); include_once( WC_ABSPATH . 'includes/class-wc-download-handler.php' ); @@ -311,17 +317,17 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-emails.php' ); include_once( WC_ABSPATH . 'includes/class-wc-data-exception.php' ); include_once( WC_ABSPATH . 'includes/class-wc-query.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory - include_once( WC_ABSPATH . 'includes/class-wc-order-query.php' ); // Order query - include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory - include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller + include_once( WC_ABSPATH . 'includes/class-wc-order-factory.php' ); // Order factory. + include_once( WC_ABSPATH . 'includes/class-wc-order-query.php' ); // Order query. + include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory. + include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller. include_once( WC_ABSPATH . 'includes/class-wc-shipping-zone.php' ); - include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway - include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-echeck.php' ); // eCheck Payment Gateway - include_once( WC_ABSPATH . 'includes/class-wc-countries.php' ); // Defines countries and states - include_once( WC_ABSPATH . 'includes/class-wc-integrations.php' ); // Loads integrations - include_once( WC_ABSPATH . 'includes/class-wc-cache-helper.php' ); // Cache Helper - include_once( WC_ABSPATH . 'includes/class-wc-https.php' ); // https Helper + include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway. + include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-echeck.php' ); // eCheck Payment Gateway. + include_once( WC_ABSPATH . 'includes/class-wc-countries.php' ); // Defines countries and states. + include_once( WC_ABSPATH . 'includes/class-wc-integrations.php' ); // Loads integrations. + include_once( WC_ABSPATH . 'includes/class-wc-cache-helper.php' ); // Cache Helper. + include_once( WC_ABSPATH . 'includes/class-wc-https.php' ); // https Helper. include_once( WC_ABSPATH . 'includes/class-wc-deprecated-action-hooks.php' ); include_once( WC_ABSPATH . 'includes/class-wc-deprecated-filter-hooks.php' ); include_once( WC_ABSPATH . 'includes/class-wc-background-emailer.php' ); @@ -356,13 +362,13 @@ final class WooCommerce { * REST API. */ include_once( WC_ABSPATH . 'includes/class-wc-legacy-api.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-api.php' ); // API Class - include_once( WC_ABSPATH . 'includes/class-wc-auth.php' ); // Auth Class - include_once( WC_ABSPATH . 'includes/class-wc-register-wp-admin-settings.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-api.php' ); // API Class. + include_once( WC_ABSPATH . 'includes/class-wc-auth.php' ); // Auth Class. + include_once( WC_ABSPATH . 'includes/class-wc-register-wp-admin-settings.php' ); if ( defined( 'WP_CLI' ) && WP_CLI ) { - include_once( WC_ABSPATH . 'includes/class-wc-cli.php' ); - } + include_once( WC_ABSPATH . 'includes/class-wc-cli.php' ); + } if ( $this->is_request( 'admin' ) ) { include_once( WC_ABSPATH . 'includes/admin/class-wc-admin.php' ); @@ -391,16 +397,16 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/wc-cart-functions.php' ); include_once( WC_ABSPATH . 'includes/wc-notice-functions.php' ); include_once( WC_ABSPATH . 'includes/wc-template-hooks.php' ); - include_once( WC_ABSPATH . 'includes/class-wc-template-loader.php' ); // Template Loader - include_once( WC_ABSPATH . 'includes/class-wc-frontend-scripts.php' ); // Frontend Scripts - include_once( WC_ABSPATH . 'includes/class-wc-form-handler.php' ); // Form Handlers - include_once( WC_ABSPATH . 'includes/class-wc-cart.php' ); // The main cart class - include_once( WC_ABSPATH . 'includes/class-wc-tax.php' ); // Tax class - include_once( WC_ABSPATH . 'includes/class-wc-shipping-zones.php' ); // Shipping Zones class - include_once( WC_ABSPATH . 'includes/class-wc-customer.php' ); // Customer class - include_once( WC_ABSPATH . 'includes/class-wc-shortcodes.php' ); // Shortcodes class - include_once( WC_ABSPATH . 'includes/class-wc-embed.php' ); // Embeds - include_once( WC_ABSPATH . 'includes/class-wc-structured-data.php' ); // Structured Data class + include_once( WC_ABSPATH . 'includes/class-wc-template-loader.php' ); // Template Loader. + include_once( WC_ABSPATH . 'includes/class-wc-frontend-scripts.php' ); // Frontend Scripts. + include_once( WC_ABSPATH . 'includes/class-wc-form-handler.php' ); // Form Handlers. + include_once( WC_ABSPATH . 'includes/class-wc-cart.php' ); // The main cart class. + include_once( WC_ABSPATH . 'includes/class-wc-tax.php' ); // Tax class. + include_once( WC_ABSPATH . 'includes/class-wc-shipping-zones.php' ); // Shipping Zones class. + include_once( WC_ABSPATH . 'includes/class-wc-customer.php' ); // Customer class. + include_once( WC_ABSPATH . 'includes/class-wc-shortcodes.php' ); // Shortcodes class. + include_once( WC_ABSPATH . 'includes/class-wc-embed.php' ); // Embeds. + include_once( WC_ABSPATH . 'includes/class-wc-structured-data.php' ); // Structured Data class. if ( $this->is_active_theme( 'twentyseventeen' ) ) { include_once( WC_ABSPATH . 'includes/theme-support/class-wc-twenty-seventeen.php' ); @@ -425,11 +431,11 @@ final class WooCommerce { $this->load_plugin_textdomain(); // Load class instances. - $this->product_factory = new WC_Product_Factory(); // Product Factory to create new product instances - $this->order_factory = new WC_Order_Factory(); // Order Factory to create new order instances - $this->countries = new WC_Countries(); // Countries class - $this->integrations = new WC_Integrations(); // Integrations class - $this->structured_data = new WC_Structured_Data(); // Structured Data class, generates and handles structured data + $this->product_factory = new WC_Product_Factory(); // Product Factory to create new product instances. + $this->order_factory = new WC_Order_Factory(); // Order Factory to create new order instances. + $this->countries = new WC_Countries(); // Countries class. + $this->integrations = new WC_Integrations(); // Integrations class. + $this->structured_data = new WC_Structured_Data(); // Structured Data class, generates and handles structured data. $this->deprecated_hook_handlers['actions'] = new WC_Deprecated_Action_Hooks(); $this->deprecated_hook_handlers['filters'] = new WC_Deprecated_Filter_Hooks(); @@ -441,8 +447,8 @@ final class WooCommerce { // Classes/actions loaded for the frontend and for ajax requests. if ( $this->is_request( 'frontend' ) ) { - $this->cart = new WC_Cart(); // Cart class, stores the cart contents - $this->customer = new WC_Customer( get_current_user_id(), true ); // Customer class, handles data such as customer location + $this->cart = new WC_Cart(); // Cart class, stores the cart contents. + $this->customer = new WC_Customer( get_current_user_id(), true ); // Customer class, handles data such as customer location. add_action( 'shutdown', array( $this->customer, 'save' ), 10 ); // Customer should be saved during shutdown. } @@ -474,9 +480,7 @@ final class WooCommerce { * Ensure theme and server variable compatibility and setup image sizes. */ public function setup_environment() { - /** - * @deprecated 2.2 Use WC()->template_path() - */ + /* @deprecated 2.2 Use WC()->template_path() instead. */ $this->define( 'WC_TEMPLATE_PATH', $this->template_path() ); $this->add_thumbnail_support(); @@ -510,6 +514,7 @@ final class WooCommerce { /** * Get the plugin url. + * * @return string */ public function plugin_url() { @@ -518,6 +523,7 @@ final class WooCommerce { /** * Get the plugin path. + * * @return string */ public function plugin_path() { @@ -526,6 +532,7 @@ final class WooCommerce { /** * Get the template path. + * * @return string */ public function template_path() { @@ -534,6 +541,7 @@ final class WooCommerce { /** * Get Ajax URL. + * * @return string */ public function ajax_url() { @@ -543,8 +551,8 @@ final class WooCommerce { /** * Return the WC API URL for a given request. * - * @param string $request - * @param mixed $ssl (default: null) + * @param string $request Requested endpoint. + * @param bool|null $ssl If should use SSL, null if should auto detect. Default: null. * @return string */ public function api_request_url( $request, $ssl = null ) { @@ -611,6 +619,7 @@ final class WooCommerce { /** * Get Checkout Class. + * * @return WC_Checkout */ public function checkout() { @@ -619,6 +628,7 @@ final class WooCommerce { /** * Get gateways class. + * * @return WC_Payment_Gateways */ public function payment_gateways() { @@ -627,6 +637,7 @@ final class WooCommerce { /** * Get shipping class. + * * @return WC_Shipping */ public function shipping() { @@ -635,6 +646,7 @@ final class WooCommerce { /** * Email Class. + * * @return WC_Emails */ public function mailer() { From 8b9f974231d54c369fba16dcd22dd45e5dab5f41 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 13 Jul 2017 15:59:50 -0300 Subject: [PATCH 027/224] Fixed coding standards in main file PHP functions name are case-insensitive. --- woocommerce.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/woocommerce.php b/woocommerce.php index f700af07e3e..9f990ef8ebc 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -16,6 +16,7 @@ * @category Core * @author Automattic */ + if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } @@ -48,9 +49,9 @@ if ( ! class_exists( 'WooCommerce' ) ) { * @since 2.1 * @return WooCommerce */ -function WC() { +function wc() { return WooCommerce::instance(); } // Global for backwards compatibility. -$GLOBALS['woocommerce'] = WC(); +$GLOBALS['woocommerce'] = wc(); From 14ff34ef81fae3f93339649e078a361e2263be82 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Fri, 14 Jul 2017 12:49:16 -0300 Subject: [PATCH 028/224] Moved constants to main class and applied new description --- includes/class-woocommerce.php | 4 +++- woocommerce.php | 10 ---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 08dd8e650cc..fea2e8a57d7 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -1,6 +1,6 @@ define( 'WC_ABSPATH', dirname( WC_PLUGIN_FILE ) . '/' ); + $this->define( 'WC_PLUGIN_BASENAME', plugin_basename( WC_PLUGIN_FILE ) ); $this->define( 'WC_VERSION', $this->version ); $this->define( 'WOOCOMMERCE_VERSION', $this->version ); $this->define( 'WC_ROUNDING_PRECISION', 4 ); diff --git a/woocommerce.php b/woocommerce.php index 9f990ef8ebc..99c48b11d63 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -26,16 +26,6 @@ if ( ! defined( 'WC_PLUGIN_FILE' ) ) { define( 'WC_PLUGIN_FILE', __FILE__ ); } -// Define WC_ABSPATH. -if ( ! defined( 'WC_ABSPATH' ) ) { - define( 'WC_ABSPATH', dirname( __FILE__ ) . '/' ); -} - -// Define WC_PLUGIN_BASENAME. -if ( ! defined( 'WC_PLUGIN_BASENAME' ) ) { - define( 'WC_PLUGIN_BASENAME', plugin_basename( __FILE__ ) ); -} - // Include the main WooCommerce class. if ( ! class_exists( 'WooCommerce' ) ) { include_once dirname( __FILE__ ) . '/includes/class-woocommerce.php'; From f66198c37dcdaa9414fd507866506e1777beb00d Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Fri, 14 Jul 2017 09:50:20 -0700 Subject: [PATCH 029/224] Initial working wc_get_products --- includes/class-wc-product-query.php | 11 ++- .../class-wc-product-data-store-cpt.php | 69 +++++++++++++++++++ includes/wc-product-functions.php | 5 +- tests/unit-tests/product/query.php | 7 +- 4 files changed, 85 insertions(+), 7 deletions(-) diff --git a/includes/class-wc-product-query.php b/includes/class-wc-product-query.php index b06c5fe6d09..c8ed55f5355 100644 --- a/includes/class-wc-product-query.php +++ b/includes/class-wc-product-query.php @@ -22,8 +22,10 @@ class WC_Product_Query extends WC_Object_Query { return array_merge( parent::get_default_query_vars(), array( - 'status' => 'publish', - 'type' => array( 'product', 'product_variation' ), + 'status' => array( 'draft', 'pending', 'private', 'publish' ), + 'type' => array_merge( array_keys( wc_get_product_types() ) ), + 'limit' => get_option( 'posts_per_page' ), + 'include' => array(), 'slug' => '', 'date_created' => '', 'date_modified' => '', @@ -58,9 +60,12 @@ class WC_Product_Query extends WC_Object_Query { 'menu_order' => '', 'virtual' => '', 'downloadable' => '', + 'category' => array(), 'category_ids' => array(), 'tag_ids' => array(), - 'shipping_class_id' => '', + 'tag' => array(), + 'shipping_class_id' => array(), + 'shipping_class' => array(), 'image_id' => '', 'download_limit' => '', 'download_expiry' => '', diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index fab4b883a31..164f5684a4d 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1396,8 +1396,25 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da } } + // SKU needs special handling because it works with partial matches. + // Don't auto-generate meta query args for it. + $sku_query = false; + if ( isset( $query_vars['sku'] ) ) { + $sku_query = $query_vars['sku']; + unset( $query_vars['sku'] ); + } + $wp_query_args = parent::get_wp_query_args( $query_vars ); + // Add the special SKU query if needed. + if ( $sku_query ) { + $wp_query_args['meta_query'][] = array( + 'key' => '_sku', + 'value' => $sku_query, + 'compare' => 'LIKE', + ); + } + if ( ! isset( $wp_query_args['date_query'] ) ) { $wp_query_args['date_query'] = array(); } @@ -1405,6 +1422,58 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $wp_query_args['meta_query'] = array(); } + // Handle product types. + if ( 'variation' === $query_vars['type'] ) { + $wp_query_args['post_type'] = 'product_variation'; + } elseif ( is_array( $query_vars['type'] ) && in_array( 'variation', $query_vars['type'] ) ) { + $wp_query_args['post_type'] = array( 'product_variation', 'product' ); + $wp_query_args['tax_query'][] = array( + 'relation' => 'OR', + array( + 'taxonomy' => 'product_type', + 'field' => 'slug', + 'terms' => $query_vars['type'], + ), + array( + 'taxonomy' => 'product_type', + 'field' => 'id', + 'operator' => 'NOT EXISTS', + ), + ); + } else { + $wp_query_args['post_type'] = 'product'; + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'product_type', + 'field' => 'slug', + 'terms' => $query_vars['type'], + ); + } + + if ( ! empty( $query_vars['category'] ) ) { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'product_cat', + 'field' => 'slug', + 'terms' => $query_vars['category'], + ); + } + + if ( ! empty( $query_vars['tag'] ) ) { + unset( $wp_query_args['tag'] ); + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'product_tag', + 'field' => 'slug', + 'terms' => $query_vars['tag'], + ); + } + + if ( ! empty( $query_vars['shipping_class'] ) ) { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'product_shipping_class', + 'field' => 'slug', + 'terms' => $query_vars['shipping_class'], + ); + } + $date_queries = array( 'date_created' => 'post_date', 'date_modified' => 'post_modified', diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php index fd9b973c7d6..e9b9d74b0dc 100644 --- a/includes/wc-product-functions.php +++ b/includes/wc-product-functions.php @@ -85,7 +85,10 @@ function wc_get_products( $args ) { } } - return WC_Data_Store::load( 'product' )->get_products( $args ); + $query = new WC_Product_Query( $args ); + return $query->get_products(); + + //return WC_Data_Store::load( 'product' )->get_products( $args ); } /** diff --git a/tests/unit-tests/product/query.php b/tests/unit-tests/product/query.php index ec70a930006..8d9da11f1a8 100644 --- a/tests/unit-tests/product/query.php +++ b/tests/unit-tests/product/query.php @@ -14,7 +14,9 @@ class WC_Tests_WC_Product_Query extends WC_Unit_Test_Case { public function test_product_query_new() { $query = new WC_Product_Query(); $this->assertEquals( '', $query->get( 'weight' ) ); - $this->assertEquals( array( 'product', 'product_variation' ), $query->get( 'type' ) ); + $types = $query->get( 'type' ); + sort( $types ); + $this->assertEquals( array( 'external', 'grouped', 'simple', 'variable' ), $types ); } /** @@ -29,7 +31,7 @@ class WC_Tests_WC_Product_Query extends WC_Unit_Test_Case { $product1->set_sale_price( '5.00' ); $product1->save(); - $product2 = new WC_Product_Variation(); + $product2 = new WC_Product_Grouped(); $product2->set_sku( 'sku2' ); $product2->set_regular_price( '12.50' ); $product2->set_sale_price( '5.00' ); @@ -64,7 +66,6 @@ class WC_Tests_WC_Product_Query extends WC_Unit_Test_Case { $this->assertEquals( 1, count( $results ) ); } - /** * Test querying by product date properties for dates stored in metadata. * From b04534bb853f447f71fd55cf7df2f856d0fb416f Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Fri, 14 Jul 2017 10:01:25 -0700 Subject: [PATCH 030/224] Hook everything up to wc_product_query --- includes/class-wc-product-query.php | 3 - .../class-wc-product-data-store-cpt.php | 130 ++---------------- 2 files changed, 10 insertions(+), 123 deletions(-) diff --git a/includes/class-wc-product-query.php b/includes/class-wc-product-query.php index c8ed55f5355..935e962bb4d 100644 --- a/includes/class-wc-product-query.php +++ b/includes/class-wc-product-query.php @@ -61,10 +61,7 @@ class WC_Product_Query extends WC_Object_Query { 'virtual' => '', 'downloadable' => '', 'category' => array(), - 'category_ids' => array(), - 'tag_ids' => array(), 'tag' => array(), - 'shipping_class_id' => array(), 'shipping_class' => array(), 'image_id' => '', 'download_limit' => '', diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 164f5684a4d..0513566a35b 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1168,124 +1168,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da * @return array|object */ public function get_products( $args = array() ) { - /** - * Generate WP_Query args. - */ - $wp_query_args = array( - 'post_status' => $args['status'], - 'posts_per_page' => $args['limit'], - 'meta_query' => array(), - 'orderby' => $args['orderby'], - 'order' => $args['order'], - 'tax_query' => array(), - ); - - if ( 'variation' === $args['type'] ) { - $wp_query_args['post_type'] = 'product_variation'; - } elseif ( is_array( $args['type'] ) && in_array( 'variation', $args['type'] ) ) { - $wp_query_args['post_type'] = array( 'product_variation', 'product' ); - $wp_query_args['tax_query'][] = array( - 'relation' => 'OR', - array( - 'taxonomy' => 'product_type', - 'field' => 'slug', - 'terms' => $args['type'], - ), - array( - 'taxonomy' => 'product_type', - 'field' => 'id', - 'operator' => 'NOT EXISTS', - ), - ); - } else { - $wp_query_args['post_type'] = 'product'; - $wp_query_args['tax_query'][] = array( - 'taxonomy' => 'product_type', - 'field' => 'slug', - 'terms' => $args['type'], - ); - } - - // Do not load unnecessary post data if the user only wants IDs. - if ( 'ids' === $args['return'] ) { - $wp_query_args['fields'] = 'ids'; - } - - if ( ! empty( $args['sku'] ) ) { - $wp_query_args['meta_query'][] = array( - 'key' => '_sku', - 'value' => $args['sku'], - 'compare' => 'LIKE', - ); - } - - if ( ! empty( $args['category'] ) ) { - $wp_query_args['tax_query'][] = array( - 'taxonomy' => 'product_cat', - 'field' => 'slug', - 'terms' => $args['category'], - ); - } - - if ( ! empty( $args['tag'] ) ) { - $wp_query_args['tax_query'][] = array( - 'taxonomy' => 'product_tag', - 'field' => 'slug', - 'terms' => $args['tag'], - ); - } - - if ( ! empty( $args['shipping_class'] ) ) { - $wp_query_args['tax_query'][] = array( - 'taxonomy' => 'product_shipping_class', - 'field' => 'slug', - 'terms' => $args['shipping_class'], - ); - } - - if ( ! is_null( $args['parent'] ) ) { - $wp_query_args['post_parent'] = absint( $args['parent'] ); - } - - if ( ! is_null( $args['offset'] ) ) { - $wp_query_args['offset'] = absint( $args['offset'] ); - } else { - $wp_query_args['paged'] = absint( $args['page'] ); - } - - if ( ! empty( $args['include'] ) ) { - $wp_query_args['post__in'] = array_map( 'absint', $args['include'] ); - } - - if ( ! empty( $args['exclude'] ) ) { - $wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] ); - } - - if ( ! $args['paginate'] ) { - $wp_query_args['no_found_rows'] = true; - } - - // Get results. - $products = new WP_Query( $wp_query_args ); - - if ( 'objects' === $args['return'] ) { - // Prime caches before grabbing objects. - update_post_caches( $products->posts, array( 'product', 'product_variation' ) ); - - $return = array_filter( array_map( 'wc_get_product', $products->posts ) ); - } else { - $return = $products->posts; - } - - if ( $args['paginate'] ) { - return (object) array( - 'products' => $return, - 'total' => $products->found_posts, - 'max_num_pages' => $products->max_num_pages, - ); - } else { - return $return; - } + $query = new WC_Product_Query( $args ); + return $query->get_products(); } /** @@ -1385,8 +1269,9 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da // Map query vars to ones that get_wp_query_args or WP_Query recognize. $key_mapping = array( - 'status' => 'post_status', - 'page' => 'paged', + 'status' => 'post_status', + 'page' => 'paged', + 'include' => 'post__in', ); foreach ( $key_mapping as $query_key => $db_key ) { @@ -1522,6 +1407,11 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $query = new WP_Query( $args ); } + if ( isset( $query_vars['return'] ) && 'objects' === $query_vars['return'] && ! empty( $query->posts ) ) { + // Prime caches before grabbing objects. + update_post_caches( $products->posts, array( 'product', 'product_variation' ) ); + } + $products = ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) ? $query->posts : array_filter( array_map( 'wc_get_product', $query->posts ) ); if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) { From 96bc5e6f9556d6e0377d8ae0a9f44ec1701a8e7c Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Fri, 14 Jul 2017 10:07:18 -0700 Subject: [PATCH 031/224] cleanup --- includes/wc-product-functions.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php index e9b9d74b0dc..8c8dd5013c2 100644 --- a/includes/wc-product-functions.php +++ b/includes/wc-product-functions.php @@ -87,8 +87,6 @@ function wc_get_products( $args ) { $query = new WC_Product_Query( $args ); return $query->get_products(); - - //return WC_Data_Store::load( 'product' )->get_products( $args ); } /** From 8c8684b8aa4b8e48f20d42ecd667c837252295f4 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Fri, 14 Jul 2017 10:10:37 -0700 Subject: [PATCH 032/224] Move meta query init above sku query --- .../class-wc-product-data-store-cpt.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 0513566a35b..872e7c2614f 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1291,6 +1291,13 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $wp_query_args = parent::get_wp_query_args( $query_vars ); + if ( ! isset( $wp_query_args['date_query'] ) ) { + $wp_query_args['date_query'] = array(); + } + if ( ! isset( $wp_query_args['meta_query'] ) ) { + $wp_query_args['meta_query'] = array(); + } + // Add the special SKU query if needed. if ( $sku_query ) { $wp_query_args['meta_query'][] = array( @@ -1300,13 +1307,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da ); } - if ( ! isset( $wp_query_args['date_query'] ) ) { - $wp_query_args['date_query'] = array(); - } - if ( ! isset( $wp_query_args['meta_query'] ) ) { - $wp_query_args['meta_query'] = array(); - } - // Handle product types. if ( 'variation' === $query_vars['type'] ) { $wp_query_args['post_type'] = 'product_variation'; From a2a34cf0be52984bebca304145c24878ba074f75 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 17 Jul 2017 10:34:44 +0100 Subject: [PATCH 033/224] Added `woocommerce_data_store_wp_{$this->meta_type}_read_meta` filter #16136 --- includes/data-stores/class-wc-data-store-wp.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/data-stores/class-wc-data-store-wp.php b/includes/data-stores/class-wc-data-store-wp.php index 821b16a91ab..241275bc7c6 100644 --- a/includes/data-stores/class-wc-data-store-wp.php +++ b/includes/data-stores/class-wc-data-store-wp.php @@ -76,7 +76,8 @@ class WC_Data_Store_WP { ", $object->get_id() ) ); $this->internal_meta_keys = array_merge( array_map( array( $this, 'prefix_key' ), $object->get_data_keys() ), $this->internal_meta_keys ); - return array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) ); + $meta_data = array_filter( $raw_meta_data, array( $this, 'exclude_internal_meta_keys' ) ); + return apply_filters( "woocommerce_data_store_wp_{$this->meta_type}_read_meta", $meta_data, $object, $this ); } /** From 3522857d23051844bea742a37a1b0b242f7850af Mon Sep 17 00:00:00 2001 From: Jaydeep Rami Date: Mon, 17 Jul 2017 15:40:52 +0530 Subject: [PATCH 034/224] Fix typo in WooCommerce plugin (#16135) * Fixed typo * Fixed more typo * Fixed more typo * Fix tyop * Fix more typo * Fix more typo * Fix typo * Fix typo * Fix typo * Fix typo datatime object * Fix short name of compat * Fix typo: update short name * Fix typo "deactive" to "deactivate" and short BW - Compat * Fix typo "Backwards compat" * Fix typo 'parameters' * Fix more typo 'pararmeters' * Fix typo 'compund' * FIx typo order * Fix typo * Fix typo 'incorrecly' * Fix typo 'genarate' * Fix typo 'reletive' * Fix typo 'Handly' * Fix typo 'rotatated' * Fix typo * Fix typo 'additonal' * Fix typos --- includes/abstracts/abstract-wc-data.php | 2 +- includes/abstracts/abstract-wc-order.php | 2 +- includes/abstracts/abstract-wc-product.php | 6 +++--- includes/abstracts/abstract-wc-rest-terms-controller.php | 2 +- includes/admin/class-wc-admin-reports.php | 2 +- includes/admin/class-wc-admin-setup-wizard.php | 2 +- includes/admin/helper/class-wc-helper.php | 6 +++--- .../admin/meta-boxes/class-wc-meta-box-product-images.php | 2 +- includes/api/class-wc-rest-authentication.php | 4 ++-- includes/api/class-wc-rest-coupons-controller.php | 2 +- includes/api/class-wc-rest-orders-controller.php | 2 +- .../api/legacy/class-wc-rest-legacy-orders-controller.php | 2 +- includes/api/legacy/v1/class-wc-api-authentication.php | 2 +- includes/api/legacy/v2/class-wc-api-authentication.php | 2 +- includes/api/legacy/v2/class-wc-api-customers.php | 2 +- includes/api/legacy/v3/class-wc-api-authentication.php | 2 +- includes/api/legacy/v3/class-wc-api-customers.php | 2 +- includes/api/legacy/v3/class-wc-api-taxes.php | 2 +- includes/api/v1/class-wc-rest-coupons-controller.php | 2 +- includes/api/v1/class-wc-rest-customers-controller.php | 2 +- includes/api/v1/class-wc-rest-orders-controller.php | 4 ++-- includes/api/v1/class-wc-rest-taxes-controller.php | 4 ++-- includes/class-wc-ajax.php | 6 +++--- includes/class-wc-auth.php | 2 +- includes/class-wc-cart.php | 2 +- includes/class-wc-coupon.php | 6 +++--- includes/class-wc-form-handler.php | 2 +- includes/class-wc-order-item-coupon.php | 2 +- includes/class-wc-order-item-fee.php | 2 +- includes/class-wc-order-item-meta.php | 2 +- includes/class-wc-order-item-product.php | 2 +- includes/class-wc-order-item-shipping.php | 2 +- includes/class-wc-order-item-tax.php | 2 +- includes/class-wc-order-item.php | 2 +- includes/class-wc-product-external.php | 2 +- includes/class-wc-product-grouped.php | 2 +- includes/class-wc-product-variable.php | 2 +- includes/class-wc-query.php | 2 +- includes/class-wc-shipping-zones.php | 2 +- includes/class-wc-tax.php | 2 +- includes/cli/class-wc-cli-rest-command.php | 2 +- .../data-stores/abstract-wc-order-item-type-data-store.php | 2 +- .../class-wc-product-variable-data-store-cpt.php | 2 +- includes/emails/class-wc-email-customer-completed-order.php | 2 +- includes/emails/class-wc-email-customer-refunded-order.php | 2 +- includes/emails/class-wc-email.php | 4 ++-- includes/export/class-wc-product-csv-exporter.php | 2 +- .../simplify-commerce/includes/Simplify/Constants.php | 2 +- .../gateways/simplify-commerce/includes/Simplify/Event.php | 4 ++-- .../gateways/simplify-commerce/includes/Simplify/Http.php | 2 +- includes/import/abstract-wc-product-importer.php | 2 +- includes/import/class-wc-product-csv-importer.php | 2 +- includes/legacy/abstract-wc-legacy-order.php | 2 +- includes/legacy/abstract-wc-legacy-product.php | 2 +- includes/libraries/class-wc-eval-math.php | 2 +- includes/log-handlers/class-wc-log-handler-file.php | 2 +- .../legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php | 2 +- .../class-wc-shipping-legacy-free-shipping.php | 2 +- .../class-wc-shipping-legacy-international-delivery.php | 2 +- .../class-wc-shipping-legacy-local-delivery.php | 2 +- .../class-wc-shipping-legacy-local-pickup.php | 2 +- includes/shortcodes/class-wc-shortcode-checkout.php | 2 +- includes/vendor/abstract-wp-rest-controller.php | 2 +- includes/vendor/wp-rest-functions.php | 2 +- includes/wc-order-functions.php | 2 +- includes/wc-template-functions.php | 4 ++-- includes/wc-user-functions.php | 4 ++-- includes/widgets/class-wc-widget-layered-nav-filters.php | 2 +- includes/widgets/class-wc-widget-layered-nav.php | 2 +- includes/widgets/class-wc-widget-rating-filter.php | 2 +- 70 files changed, 85 insertions(+), 85 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index 976028f1333..fd4605b04fc 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -644,7 +644,7 @@ abstract class WC_Data { } /** - * Sets a date prop whilst handling formatting and datatime objects. + * Sets a date prop whilst handling formatting and datetime objects. * * @since 3.0.0 * @param string $prop Name of prop to set. diff --git a/includes/abstracts/abstract-wc-order.php b/includes/abstracts/abstract-wc-order.php index b8a9acc7e80..a9e77d0c16e 100644 --- a/includes/abstracts/abstract-wc-order.php +++ b/includes/abstracts/abstract-wc-order.php @@ -616,7 +616,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { } /** - * Sets order tax (sum of cart and shipping tax). Used internaly only. + * Sets order tax (sum of cart and shipping tax). Used internally only. * * @param string $value * @throws WC_Data_Exception diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index d91eec6ebc0..eefd9527897 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -130,7 +130,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { /** * Get internal type. Should return string and *should be overridden* by child classes. * - * The product_type property is deprecated but is used here for BW compat with child classes which may be defining product_type and not have a get_type method. + * The product_type property is deprecated but is used here for BW compatibility with child classes which may be defining product_type and not have a get_type method. * * @since 3.0.0 * @return string @@ -452,7 +452,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { } /** - * Get upsel IDs. + * Get upsell IDs. * * @since 3.0.0 * @param string $context @@ -1331,7 +1331,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { /** * Checks the product type. * - * Backwards compat with downloadable/virtual. + * Backwards compatibility with downloadable/virtual. * * @param string|array $type Array or string of types * @return bool diff --git a/includes/abstracts/abstract-wc-rest-terms-controller.php b/includes/abstracts/abstract-wc-rest-terms-controller.php index 49b53a14e85..f04eb469f06 100644 --- a/includes/abstracts/abstract-wc-rest-terms-controller.php +++ b/includes/abstracts/abstract-wc-rest-terms-controller.php @@ -326,7 +326,7 @@ abstract class WC_REST_Terms_Controller extends WC_REST_Controller { $response = rest_ensure_response( $response ); - // Store pagation values for headers then unset for count query. + // Store pagination values for headers then unset for count query. $per_page = (int) $prepared_args['number']; $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); diff --git a/includes/admin/class-wc-admin-reports.php b/includes/admin/class-wc-admin-reports.php index f7a7b3926b0..5efd469fe57 100644 --- a/includes/admin/class-wc-admin-reports.php +++ b/includes/admin/class-wc-admin-reports.php @@ -133,7 +133,7 @@ class WC_Admin_Reports { } $reports = apply_filters( 'woocommerce_admin_reports', $reports ); - $reports = apply_filters( 'woocommerce_reports_charts', $reports ); // Backwards compat + $reports = apply_filters( 'woocommerce_reports_charts', $reports ); // Backwards compatibility. foreach ( $reports as $key => $report_group ) { if ( isset( $reports[ $key ]['charts'] ) ) { diff --git a/includes/admin/class-wc-admin-setup-wizard.php b/includes/admin/class-wc-admin-setup-wizard.php index f1316bf5feb..1346bcda4ba 100644 --- a/includes/admin/class-wc-admin-setup-wizard.php +++ b/includes/admin/class-wc-admin-setup-wizard.php @@ -18,7 +18,7 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Admin_Setup_Wizard { - /** @var string Currenct Step */ + /** @var string Current Step */ private $step = ''; /** @var array Steps for the setup wizard */ diff --git a/includes/admin/helper/class-wc-helper.php b/includes/admin/helper/class-wc-helper.php index 44f1f267f61..12af2af13ce 100644 --- a/includes/admin/helper/class-wc-helper.php +++ b/includes/admin/helper/class-wc-helper.php @@ -374,7 +374,7 @@ class WC_Helper { ), admin_url( 'admin.php' ) ); /* translators: %1$s: product name, %2$s: deactivate url */ - $message = sprintf( __( 'Subscription for %1$s deactivated successfully. You will no longer receive updates for this product. Click here if you wish to deactive the plugin as well.', 'woocommerce' ), + $message = sprintf( __( 'Subscription for %1$s deactivated successfully. You will no longer receive updates for this product. Click here if you wish to deactivate the plugin as well.', 'woocommerce' ), '' . esc_html( $subscription['product_name'] ) . '', esc_url( $deactivate_plugin_url ) ); } @@ -835,7 +835,7 @@ class WC_Helper { $plugins = get_plugins(); $woo_plugins = array(); - // Back-compat for woothemes_queue_update(). + // Backwards compatibility for woothemes_queue_update(). $_compat = array(); if ( ! empty( $GLOBALS['woothemes_queued_updates'] ) ) { foreach ( $GLOBALS['woothemes_queued_updates'] as $_compat_plugin ) { @@ -880,7 +880,7 @@ class WC_Helper { foreach ( $themes as $theme ) { $header = $theme->get( 'Woo' ); - // Back-compat for theme_info.txt + // Backwards compatibility for theme_info.txt if ( ! $header ) { $txt = $theme->get_stylesheet_directory() . '/theme_info.txt'; if ( is_readable( $txt ) ) { diff --git a/includes/admin/meta-boxes/class-wc-meta-box-product-images.php b/includes/admin/meta-boxes/class-wc-meta-box-product-images.php index 5ac7cca74bd..7f63034ca47 100644 --- a/includes/admin/meta-boxes/class-wc-meta-box-product-images.php +++ b/includes/admin/meta-boxes/class-wc-meta-box-product-images.php @@ -32,7 +32,7 @@ class WC_Meta_Box_Product_Images { if ( metadata_exists( 'post', $post->ID, '_product_image_gallery' ) ) { $product_image_gallery = get_post_meta( $post->ID, '_product_image_gallery', true ); } else { - // Backwards compat + // Backwards compatibility. $attachment_ids = get_posts( 'post_parent=' . $post->ID . '&numberposts=-1&post_type=attachment&orderby=menu_order&order=ASC&post_mime_type=image&fields=ids&meta_key=_woocommerce_exclude_image&meta_value=0' ); $attachment_ids = array_diff( $attachment_ids, array( get_post_thumbnail_id() ) ); $product_image_gallery = implode( ',', $attachment_ids ); diff --git a/includes/api/class-wc-rest-authentication.php b/includes/api/class-wc-rest-authentication.php index ae4e008474b..0fbcbc5bf5e 100644 --- a/includes/api/class-wc-rest-authentication.php +++ b/includes/api/class-wc-rest-authentication.php @@ -92,7 +92,7 @@ class WC_REST_Authentication { * @return WP_Error|null|bool */ public function check_authentication_error( $error ) { - // Passthrough other errors. + // Pass through other errors. if ( ! empty( $error ) ) { return $error; } @@ -103,7 +103,7 @@ class WC_REST_Authentication { /** * Set authentication error. * - * @param WP_Error $error Authication error data. + * @param WP_Error $error Authentication error data. */ protected function set_error( $error ) { // Reset user. diff --git a/includes/api/class-wc-rest-coupons-controller.php b/includes/api/class-wc-rest-coupons-controller.php index 796d0c02536..f76ce441cfa 100644 --- a/includes/api/class-wc-rest-coupons-controller.php +++ b/includes/api/class-wc-rest-coupons-controller.php @@ -240,7 +240,7 @@ class WC_REST_Coupons_Controller extends WC_REST_Legacy_Coupons_Controller { } /** - * Only reutrn writeable props from schema. + * Only return writable props from schema. * * @param array $schema * @return bool diff --git a/includes/api/class-wc-rest-orders-controller.php b/includes/api/class-wc-rest-orders-controller.php index 15f6cdae9b5..32f2e8bd70c 100644 --- a/includes/api/class-wc-rest-orders-controller.php +++ b/includes/api/class-wc-rest-orders-controller.php @@ -394,7 +394,7 @@ class WC_REST_Orders_Controller extends WC_REST_Legacy_Orders_Controller { } /** - * Only reutrn writeable props from schema. + * Only return writable props from schema. * * @param array $schema * @return bool diff --git a/includes/api/legacy/class-wc-rest-legacy-orders-controller.php b/includes/api/legacy/class-wc-rest-legacy-orders-controller.php index 5b4e501c66d..ea454bef983 100644 --- a/includes/api/legacy/class-wc-rest-legacy-orders-controller.php +++ b/includes/api/legacy/class-wc-rest-legacy-orders-controller.php @@ -215,7 +215,7 @@ class WC_REST_Legacy_Orders_Controller extends WC_REST_CRUD_Controller { * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being * prepared for the response. * - * @param WC_Order $order The prder object. + * @param WC_Order $order The Order object. * @param WP_REST_Request $request Request object. */ return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $order, $request ); diff --git a/includes/api/legacy/v1/class-wc-api-authentication.php b/includes/api/legacy/v1/class-wc-api-authentication.php index ba9372d993a..87e79f3de92 100644 --- a/includes/api/legacy/v1/class-wc-api-authentication.php +++ b/includes/api/legacy/v1/class-wc-api-authentication.php @@ -293,7 +293,7 @@ class WC_API_Authentication { * * @since 2.1 * @see rawurlencode() - * @param array $parameters un-normalized pararmeters + * @param array $parameters un-normalized parameters * @return array normalized parameters */ private function normalize_parameters( $parameters ) { diff --git a/includes/api/legacy/v2/class-wc-api-authentication.php b/includes/api/legacy/v2/class-wc-api-authentication.php index 8d5f3d4e24f..52b619a59c7 100644 --- a/includes/api/legacy/v2/class-wc-api-authentication.php +++ b/includes/api/legacy/v2/class-wc-api-authentication.php @@ -291,7 +291,7 @@ class WC_API_Authentication { * * @since 2.1 * @see rawurlencode() - * @param array $parameters un-normalized pararmeters + * @param array $parameters un-normalized parameters * @return array normalized parameters */ private function normalize_parameters( $parameters ) { diff --git a/includes/api/legacy/v2/class-wc-api-customers.php b/includes/api/legacy/v2/class-wc-api-customers.php index a38f2450d3b..df92232aea6 100644 --- a/includes/api/legacy/v2/class-wc-api-customers.php +++ b/includes/api/legacy/v2/class-wc-api-customers.php @@ -608,7 +608,7 @@ class WC_API_Customers extends WC_API_Resource { $query_args['order'] = $args['order']; } - // Orderby + // Order by if ( ! empty( $args['orderby'] ) ) { $query_args['orderby'] = $args['orderby']; diff --git a/includes/api/legacy/v3/class-wc-api-authentication.php b/includes/api/legacy/v3/class-wc-api-authentication.php index 75613c05dea..74c456309fd 100644 --- a/includes/api/legacy/v3/class-wc-api-authentication.php +++ b/includes/api/legacy/v3/class-wc-api-authentication.php @@ -290,7 +290,7 @@ class WC_API_Authentication { * * @since 2.1 * @see rawurlencode() - * @param array $parameters un-normalized pararmeters + * @param array $parameters un-normalized parameters * @return array normalized parameters */ private function normalize_parameters( $parameters ) { diff --git a/includes/api/legacy/v3/class-wc-api-customers.php b/includes/api/legacy/v3/class-wc-api-customers.php index 959faabda4b..4beca31c239 100644 --- a/includes/api/legacy/v3/class-wc-api-customers.php +++ b/includes/api/legacy/v3/class-wc-api-customers.php @@ -598,7 +598,7 @@ class WC_API_Customers extends WC_API_Resource { $query_args['order'] = $args['order']; } - // Orderby + // Order by if ( ! empty( $args['orderby'] ) ) { $query_args['orderby'] = $args['orderby']; diff --git a/includes/api/legacy/v3/class-wc-api-taxes.php b/includes/api/legacy/v3/class-wc-api-taxes.php index 2fbd6184109..5fafe782e44 100644 --- a/includes/api/legacy/v3/class-wc-api-taxes.php +++ b/includes/api/legacy/v3/class-wc-api-taxes.php @@ -297,7 +297,7 @@ class WC_API_Taxes extends WC_API_Resource { continue; } - // Fix compund and shipping values + // Fix compound and shipping values if ( in_array( $key, array( 'compound', 'shipping' ) ) ) { $value = $value ? 1 : 0; } diff --git a/includes/api/v1/class-wc-rest-coupons-controller.php b/includes/api/v1/class-wc-rest-coupons-controller.php index 2f19cb7958d..fcedf22ae1f 100644 --- a/includes/api/v1/class-wc-rest-coupons-controller.php +++ b/includes/api/v1/class-wc-rest-coupons-controller.php @@ -219,7 +219,7 @@ class WC_REST_Coupons_V1_Controller extends WC_REST_Posts_Controller { } /** - * Only reutrn writeable props from schema. + * Only return writable props from schema. * @param array $schema * @return bool */ diff --git a/includes/api/v1/class-wc-rest-customers-controller.php b/includes/api/v1/class-wc-rest-customers-controller.php index 0e46e6df95c..3034377c197 100644 --- a/includes/api/v1/class-wc-rest-customers-controller.php +++ b/includes/api/v1/class-wc-rest-customers-controller.php @@ -279,7 +279,7 @@ class WC_REST_Customers_V1_Controller extends WC_REST_Controller { $response = rest_ensure_response( $users ); - // Store pagation values for headers then unset for count query. + // Store pagination values for headers then unset for count query. $per_page = (int) $prepared_args['number']; $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); diff --git a/includes/api/v1/class-wc-rest-orders-controller.php b/includes/api/v1/class-wc-rest-orders-controller.php index 55766ab5d97..11af9597512 100644 --- a/includes/api/v1/class-wc-rest-orders-controller.php +++ b/includes/api/v1/class-wc-rest-orders-controller.php @@ -496,7 +496,7 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller { * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being * prepared for the response. * - * @param WC_Order $order The prder object. + * @param WC_Order $order The order object. * @param WP_REST_Request $request Request object. */ return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $order, $request ); @@ -513,7 +513,7 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller { } /** - * Only reutrn writeable props from schema. + * Only return writable props from schema. * @param array $schema * @return bool */ diff --git a/includes/api/v1/class-wc-rest-taxes-controller.php b/includes/api/v1/class-wc-rest-taxes-controller.php index 0f924db5674..1b8c9bdd0aa 100644 --- a/includes/api/v1/class-wc-rest-taxes-controller.php +++ b/includes/api/v1/class-wc-rest-taxes-controller.php @@ -252,14 +252,14 @@ class WC_REST_Taxes_V1_Controller extends WC_REST_Controller { $response = rest_ensure_response( $taxes ); - // Store pagation values for headers then unset for count query. + // Store pagination values for headers then unset for count query. $per_page = (int) $prepared_args['number']; $page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); // Query only for ids. $wpdb->get_results( str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ) ); - // Calcule totals. + // Calculate totals. $total_taxes = (int) $wpdb->num_rows; $response->header( 'X-WP-Total', (int) $total_taxes ); $max_pages = ceil( $total_taxes / $per_page ); diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index bc0f0f89953..1e41cb4a022 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -592,7 +592,7 @@ class WC_AJAX { $variation_object->set_parent_id( $product_id ); $variation_id = $variation_object->save(); $variation = get_post( $variation_id ); - $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compat. + $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility. include( 'admin/meta-boxes/views/html-variation-admin.php' ); wp_die(); } @@ -1040,7 +1040,7 @@ class WC_AJAX { // Save order items first wc_save_order_items( $order_id, $items ); - // Grab the order and recalc taxes + // Grab the order and recalculate taxes $order = wc_get_order( $order_id ); $order->calculate_taxes( $calculate_tax_args ); $order->calculate_totals( false ); @@ -1654,7 +1654,7 @@ class WC_AJAX { foreach ( $variations as $variation_object ) { $variation_id = $variation_object->get_id(); $variation = get_post( $variation_id ); - $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compat. + $variation_data = array_merge( array_map( 'maybe_unserialize', get_post_custom( $variation_id ) ), wc_get_product_variation_attributes( $variation_id ) ); // kept for BW compatibility. include( 'admin/meta-boxes/views/html-variation-admin.php' ); $loop++; } diff --git a/includes/class-wc-auth.php b/includes/class-wc-auth.php index 0c4c69c0cc4..d1886e76e30 100644 --- a/includes/class-wc-auth.php +++ b/includes/class-wc-auth.php @@ -379,7 +379,7 @@ class WC_Auth { } catch ( Exception $e ) { $this->maybe_delete_key( $consumer_data ); - /* translators: %s: error messase */ + /* translators: %s: error message */ wp_die( sprintf( __( 'Error: %s.', 'woocommerce' ), $e->getMessage() ), __( 'Access denied', 'woocommerce' ), array( 'response' => 401 ) ); } } diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 96f1a75723c..e0edaa7cc3f 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -1339,7 +1339,7 @@ class WC_Cart { /** * Store costs + taxes for lines. For tax inclusive prices, we do some extra rounding logic so the stored - * values "add up" when viewing the order in admin. This does have the disadvatage of not being able to + * values "add up" when viewing the order in admin. This does have the disadvantage of not being able to * recalculate the tax total/subtotal accurately in the future, but it does ensure the data looks correct. * * Tax exclusive prices are not affected. diff --git a/includes/class-wc-coupon.php b/includes/class-wc-coupon.php index 227634f5ed7..16cbb28ec0a 100644 --- a/includes/class-wc-coupon.php +++ b/includes/class-wc-coupon.php @@ -316,7 +316,7 @@ class WC_Coupon extends WC_Legacy_Coupon { } /** - * Get minium spend amount. + * Get minimum spend amount. * @since 3.0.0 * @param string $context * @return float @@ -665,7 +665,7 @@ class WC_Coupon extends WC_Legacy_Coupon { */ /** - * Developers can programically return coupons. This function will read those values into our WC_Coupon class. + * Developers can programmatically return coupons. This function will read those values into our WC_Coupon class. * @since 3.0.0 * @param string $code Coupon code * @param array $coupon Array of coupon properties @@ -929,7 +929,7 @@ class WC_Coupon extends WC_Legacy_Coupon { } /** - * Cart discounts cannot be added if non-eligble product is found in cart. + * Cart discounts cannot be added if non-eligible product is found in cart. */ private function validate_cart_excluded_items() { if ( ! $this->is_type( wc_get_product_coupon_types() ) ) { diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php index b11c3822f55..02da845080c 100644 --- a/includes/class-wc-form-handler.php +++ b/includes/class-wc-form-handler.php @@ -40,7 +40,7 @@ class WC_Form_Handler { } /** - * Remove key and login from querystring, set cookie, and redirect to account page to show the form. + * Remove key and login from query string, set cookie, and redirect to account page to show the form. */ public static function redirect_reset_password_link() { if ( is_account_page() && ! empty( $_GET['key'] ) && ! empty( $_GET['login'] ) ) { diff --git a/includes/class-wc-order-item-coupon.php b/includes/class-wc-order-item-coupon.php index b0b3ad04474..8300743fd22 100644 --- a/includes/class-wc-order-item-coupon.php +++ b/includes/class-wc-order-item-coupon.php @@ -131,7 +131,7 @@ class WC_Order_Item_Coupon extends WC_Order_Item { | Array Access Methods |-------------------------------------------------------------------------- | - | For backwards compat with legacy arrays. + | For backwards compatibility with legacy arrays. | */ diff --git a/includes/class-wc-order-item-fee.php b/includes/class-wc-order-item-fee.php index f811684a8f1..8974ba392f8 100644 --- a/includes/class-wc-order-item-fee.php +++ b/includes/class-wc-order-item-fee.php @@ -188,7 +188,7 @@ class WC_Order_Item_Fee extends WC_Order_Item { | Array Access Methods |-------------------------------------------------------------------------- | - | For backwards compat with legacy arrays. + | For backwards compatibility with legacy arrays. | */ diff --git a/includes/class-wc-order-item-meta.php b/includes/class-wc-order-item-meta.php index 72f5b693f5a..b7186975b86 100644 --- a/includes/class-wc-order-item-meta.php +++ b/includes/class-wc-order-item-meta.php @@ -37,7 +37,7 @@ class WC_Order_Item_Meta { public function __construct( $item = array(), $product = null ) { wc_deprecated_function( 'WC_Order_Item_Meta::__construct', '3.1', 'WC_Order_Item_Product' ); - // Backwards (pre 2.4) compat + // Backwards (pre 2.4) compatibility if ( ! isset( $item['item_meta'] ) ) { $this->legacy = true; $this->meta = array_filter( (array) $item ); diff --git a/includes/class-wc-order-item-product.php b/includes/class-wc-order-item-product.php index 1aafdcd45d7..2cf90dfb801 100644 --- a/includes/class-wc-order-item-product.php +++ b/includes/class-wc-order-item-product.php @@ -397,7 +397,7 @@ class WC_Order_Item_Product extends WC_Order_Item { | Array Access Methods |-------------------------------------------------------------------------- | - | For backwards compat with legacy arrays. + | For backwards compatibility with legacy arrays. | */ diff --git a/includes/class-wc-order-item-shipping.php b/includes/class-wc-order-item-shipping.php index df20fae8400..9cd19a4b0e9 100644 --- a/includes/class-wc-order-item-shipping.php +++ b/includes/class-wc-order-item-shipping.php @@ -205,7 +205,7 @@ class WC_Order_Item_Shipping extends WC_Order_Item { | Array Access Methods |-------------------------------------------------------------------------- | - | For backwards compat with legacy arrays. + | For backwards compatibility with legacy arrays. | */ diff --git a/includes/class-wc-order-item-tax.php b/includes/class-wc-order-item-tax.php index 890a3cfea13..63f431701e6 100644 --- a/includes/class-wc-order-item-tax.php +++ b/includes/class-wc-order-item-tax.php @@ -215,7 +215,7 @@ class WC_Order_Item_Tax extends WC_Order_Item { | Array Access Methods |-------------------------------------------------------------------------- | - | For backwards compat with legacy arrays. + | For backwards compatibility with legacy arrays. | */ diff --git a/includes/class-wc-order-item.php b/includes/class-wc-order-item.php index 873a4f04cd0..36fe47b77cc 100644 --- a/includes/class-wc-order-item.php +++ b/includes/class-wc-order-item.php @@ -219,7 +219,7 @@ class WC_Order_Item extends WC_Data implements ArrayAccess { | Array Access Methods |-------------------------------------------------------------------------- | - | For backwards compat with legacy arrays. + | For backwards compatibility with legacy arrays. | */ diff --git a/includes/class-wc-product-external.php b/includes/class-wc-product-external.php index 0508a4f461a..d9b7e72964a 100644 --- a/includes/class-wc-product-external.php +++ b/includes/class-wc-product-external.php @@ -122,7 +122,7 @@ class WC_Product_External extends WC_Product { } /** - * xternal products cannot be backordered. + * External products cannot be backordered. * * @since 3.0.0 * @param string $backorders Options: 'yes', 'no' or 'notify'. diff --git a/includes/class-wc-product-grouped.php b/includes/class-wc-product-grouped.php index dadf9fe2caa..c10e4d28949 100644 --- a/includes/class-wc-product-grouped.php +++ b/includes/class-wc-product-grouped.php @@ -162,7 +162,7 @@ class WC_Product_Grouped extends WC_Product { * upwards (from child to parent) when the variation is saved. * * @param WC_Product|int $product Product object or ID for which you wish to sync. - * @param bool $save If true, the prouduct object will be saved to the DB before returning it. + * @param bool $save If true, the product object will be saved to the DB before returning it. * @return WC_Product Synced product object. */ public static function sync( $product, $save = true ) { diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index 6afe8461a57..c64ecfc0c6b 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -538,7 +538,7 @@ class WC_Product_Variable extends WC_Product { * Sync parent stock status with the status of all children and save. * * @param WC_Product|int $product Product object or ID for which you wish to sync. - * @param bool $save If true, the prouduct object will be saved to the DB before returning it. + * @param bool $save If true, the product object will be saved to the DB before returning it. * @return WC_Product Synced product object. */ public static function sync_stock_status( $product, $save = true ) { diff --git a/includes/class-wc-query.php b/includes/class-wc-query.php index a6f11531fc4..43fb49c8b16 100644 --- a/includes/class-wc-query.php +++ b/includes/class-wc-query.php @@ -516,7 +516,7 @@ class WC_Query { } /** - * WP Core doens't let us change the sort direction for invidual orderby params - https://core.trac.wordpress.org/ticket/17065. + * WP Core doens't let us change the sort direction for individual orderby params - https://core.trac.wordpress.org/ticket/17065. * * This lets us sort by meta value desc, and have a second orderby param. * diff --git a/includes/class-wc-shipping-zones.php b/includes/class-wc-shipping-zones.php index 2fe22367b27..bcbfae2e892 100644 --- a/includes/class-wc-shipping-zones.php +++ b/includes/class-wc-shipping-zones.php @@ -82,7 +82,7 @@ class WC_Shipping_Zones { * * @param $instance_id * - * @return bool|WC_Shipping_Meethod + * @return bool|WC_Shipping_Method */ public static function get_shipping_method( $instance_id ) { $data_store = WC_Data_Store::load( 'shipping-zone' ); diff --git a/includes/class-wc-tax.php b/includes/class-wc-tax.php index 2325f0e9cc9..7132f694eb4 100644 --- a/includes/class-wc-tax.php +++ b/includes/class-wc-tax.php @@ -558,7 +558,7 @@ class WC_Tax { // This will be per order shipping - loop through the order and find the highest tax class rate $cart_tax_classes = WC()->cart->get_cart_item_tax_classes(); - // If multiple classes are found, use the first one found unless a standard rate item is found. This will be the first listed in the 'additonal tax class' section. + // If multiple classes are found, use the first one found unless a standard rate item is found. This will be the first listed in the 'additional tax class' section. if ( sizeof( $cart_tax_classes ) > 1 && ! in_array( '', $cart_tax_classes ) ) { $tax_classes = self::get_tax_class_slugs(); diff --git a/includes/cli/class-wc-cli-rest-command.php b/includes/cli/class-wc-cli-rest-command.php index daad9e4ed73..363993c1764 100644 --- a/includes/cli/class-wc-cli-rest-command.php +++ b/includes/cli/class-wc-cli-rest-command.php @@ -94,7 +94,7 @@ class WC_CLI_REST_Command { } /** - * Peturns an ID of supported ID arguments (things like product_id, order_id, etc) that we should look for in addition to id. + * Returns an ID of supported ID arguments (things like product_id, order_id, etc) that we should look for in addition to id. * * @return array */ diff --git a/includes/data-stores/abstract-wc-order-item-type-data-store.php b/includes/data-stores/abstract-wc-order-item-type-data-store.php index 61d8e71de46..3de53d7c547 100644 --- a/includes/data-stores/abstract-wc-order-item-type-data-store.php +++ b/includes/data-stores/abstract-wc-order-item-type-data-store.php @@ -137,7 +137,7 @@ abstract class Abstract_WC_Order_Item_Type_Data_Store extends WC_Data_Store_WP i public function save_item_data( &$item ) {} /** - * Clear meta cachce. + * Clear meta cache. * * @param WC_Order_Item $item */ diff --git a/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/includes/data-stores/class-wc-product-variable-data-store-cpt.php index dab9847dbdf..d9e243052b8 100644 --- a/includes/data-stores/class-wc-product-variable-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-variable-data-store-cpt.php @@ -227,7 +227,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple /** * Create unique cache key based on the tax location (affects displayed/cached prices), product version and active price filters. - * DEVELOPERS should filter this hash if offering conditonal pricing to keep it unique. + * DEVELOPERS should filter this hash if offering conditional pricing to keep it unique. * * @since 3.0.0 * @param WC_Product diff --git a/includes/emails/class-wc-email-customer-completed-order.php b/includes/emails/class-wc-email-customer-completed-order.php index 68d499750d9..6146606a669 100644 --- a/includes/emails/class-wc-email-customer-completed-order.php +++ b/includes/emails/class-wc-email-customer-completed-order.php @@ -36,7 +36,7 @@ class WC_Email_Customer_Completed_Order extends WC_Email { // Triggers for this email add_action( 'woocommerce_order_status_completed_notification', array( $this, 'trigger' ), 10, 2 ); - // Call parent constuctor + // Call parent constructor parent::__construct(); } diff --git a/includes/emails/class-wc-email-customer-refunded-order.php b/includes/emails/class-wc-email-customer-refunded-order.php index f52223c8b10..8a17908efbf 100644 --- a/includes/emails/class-wc-email-customer-refunded-order.php +++ b/includes/emails/class-wc-email-customer-refunded-order.php @@ -48,7 +48,7 @@ class WC_Email_Customer_Refunded_Order extends WC_Email { add_action( 'woocommerce_order_fully_refunded_notification', array( $this, 'trigger_full' ), 10, 2 ); add_action( 'woocommerce_order_partially_refunded_notification', array( $this, 'trigger_partial' ), 10, 2 ); - // Call parent constuctor + // Call parent constructor. parent::__construct(); } diff --git a/includes/emails/class-wc-email.php b/includes/emails/class-wc-email.php index 9de23a39779..f96f84bc99b 100644 --- a/includes/emails/class-wc-email.php +++ b/includes/emails/class-wc-email.php @@ -49,7 +49,7 @@ class WC_Email extends WC_Settings_API { * Default heading. * * Supported for backwards compatibility but we recommend overloading the - * get_default_x methods instead so localication can be done when needed. + * get_default_x methods instead so localization can be done when needed. * * @var string */ @@ -59,7 +59,7 @@ class WC_Email extends WC_Settings_API { * Default subject. * * Supported for backwards compatibility but we recommend overloading the - * get_default_x methods instead so localication can be done when needed. + * get_default_x methods instead so localization can be done when needed. * * @var string */ diff --git a/includes/export/class-wc-product-csv-exporter.php b/includes/export/class-wc-product-csv-exporter.php index ab1e4152668..994ce0cbb15 100644 --- a/includes/export/class-wc-product-csv-exporter.php +++ b/includes/export/class-wc-product-csv-exporter.php @@ -36,7 +36,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { protected $enable_meta_export = false; /** - * Which product types are beign exported. + * Which product types are being exported. * @var array */ protected $product_types_to_export = array(); diff --git a/includes/gateways/simplify-commerce/includes/Simplify/Constants.php b/includes/gateways/simplify-commerce/includes/Simplify/Constants.php index e96ac699cd6..c2174da8984 100644 --- a/includes/gateways/simplify-commerce/includes/Simplify/Constants.php +++ b/includes/gateways/simplify-commerce/includes/Simplify/Constants.php @@ -52,7 +52,7 @@ class Simplify_Constants const API_BASE_SANDBOX_URL = 'https://sandbox.simplify.com/v1/api'; /** - * @var string OAUTH_BASE_URL URL for the oauth enpoint + * @var string OAUTH_BASE_URL URL for the oauth endpoint */ const OAUTH_BASE_URL = 'https://www.simplify.com/commerce/oauth'; } diff --git a/includes/gateways/simplify-commerce/includes/Simplify/Event.php b/includes/gateways/simplify-commerce/includes/Simplify/Event.php index b632c9dcaad..bdb682edac5 100644 --- a/includes/gateways/simplify-commerce/includes/Simplify/Event.php +++ b/includes/gateways/simplify-commerce/includes/Simplify/Event.php @@ -36,7 +36,7 @@ class Simplify_Event extends Simplify_Object { /** * Creates an Event object * @param array $hash A map of parameters; valid keys are: - *
paylod
The raw JWS payload.
required + *
payload
The raw JWS payload.
required *
url
The URL for the webhook. If present it must match the URL registered for the webhook.
* @param $authentication Object that contains the API public and private keys. If null the values of the static * Simplify::$publicKey and Simplify::$privateKey will be used. @@ -53,7 +53,7 @@ class Simplify_Event extends Simplify_Object { $jsonObject = $paymentsApi->jwsDecode($hash, $authentication); if ($jsonObject['event'] == null) { - throw new InvalidArgumentException("Incorect data in webhook event"); + throw new InvalidArgumentException("Incorrect data in webhook event"); } return $paymentsApi->convertFromHashToObject($jsonObject['event'], self::getClazz()); diff --git a/includes/gateways/simplify-commerce/includes/Simplify/Http.php b/includes/gateways/simplify-commerce/includes/Simplify/Http.php index 603038b6644..e0925a1bfa4 100644 --- a/includes/gateways/simplify-commerce/includes/Simplify/Http.php +++ b/includes/gateways/simplify-commerce/includes/Simplify/Http.php @@ -394,7 +394,7 @@ class Simplify_HTTP break; case 3: $s = $s . "="; break; - default: throw new InvalidArgumentException('incorrecly formatted JWS payload'); + default: throw new InvalidArgumentException('incorrectly formatted JWS payload'); } return base64_decode(str_replace(array('-', '_'), array('+', '/'), $s)); } diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php index 9d07816c71f..b1684c41dbc 100644 --- a/includes/import/abstract-wc-product-importer.php +++ b/includes/import/abstract-wc-product-importer.php @@ -471,7 +471,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { // Check if attribute handle variations. if ( isset( $parent_attributes[ $attribute_name ] ) && ! $parent_attributes[ $attribute_name ]->get_variation() ) { - // Re-create the attribute to CRUD save and genarate again. + // Re-create the attribute to CRUD save and generate again. $parent_attributes[ $attribute_name ] = clone $parent_attributes[ $attribute_name ]; $parent_attributes[ $attribute_name ]->set_variation( 1 ); diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php index ece7a0b29be..19b52b692de 100644 --- a/includes/import/class-wc-product-csv-importer.php +++ b/includes/import/class-wc-product-csv-importer.php @@ -228,7 +228,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { } /** - * Parse reletive comma-delineated field and return product ID. + * Parse relative comma-delineated field and return product ID. * * @param string $field Field value. * @return array diff --git a/includes/legacy/abstract-wc-legacy-order.php b/includes/legacy/abstract-wc-legacy-order.php index d606a72808f..976013e120e 100644 --- a/includes/legacy/abstract-wc-legacy-order.php +++ b/includes/legacy/abstract-wc-legacy-order.php @@ -158,7 +158,7 @@ abstract class WC_Abstract_Legacy_Order extends WC_Data { } } - // Handly qty if set + // Handle qty if set. if ( isset( $args['qty'] ) ) { if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) { $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_stock_quantity() ), true ); diff --git a/includes/legacy/abstract-wc-legacy-product.php b/includes/legacy/abstract-wc-legacy-product.php index 577989f4d31..15b7ba0f8b5 100644 --- a/includes/legacy/abstract-wc-legacy-product.php +++ b/includes/legacy/abstract-wc-legacy-product.php @@ -681,7 +681,7 @@ abstract class WC_Abstract_Legacy_Product extends WC_Data { } /** - * @deprected 3.0.0 Sync is taken care of during save - no need to call this directly. + * @deprecated 3.0.0 Sync is taken care of during save - no need to call this directly. */ public function grouped_product_sync() { wc_deprecated_function( 'WC_Product::grouped_product_sync', '3.0' ); diff --git a/includes/libraries/class-wc-eval-math.php b/includes/libraries/class-wc-eval-math.php index 999f5afc39e..eaf174c5dc8 100644 --- a/includes/libraries/class-wc-eval-math.php +++ b/includes/libraries/class-wc-eval-math.php @@ -135,7 +135,7 @@ if ( ! class_exists( 'WC_Eval_Math', false ) ) { return self::trigger( "illegal character '_'" ); // but not in the input expression // =============== } elseif ( ( in_array( $op, $ops ) or $ex ) and $expecting_op ) { // are we putting an operator on the stack? - if ( $ex ) { // are we expecting an operator but have a number/variable/function/opening parethesis? + if ( $ex ) { // are we expecting an operator but have a number/variable/function/opening parenthesis? $op = '*'; $index--; // it's an implicit multiplication } diff --git a/includes/log-handlers/class-wc-log-handler-file.php b/includes/log-handlers/class-wc-log-handler-file.php index c13afb4eca7..3bd5209d135 100644 --- a/includes/log-handlers/class-wc-log-handler-file.php +++ b/includes/log-handlers/class-wc-log-handler-file.php @@ -283,7 +283,7 @@ class WC_Log_Handler_File extends WC_Log_Handler { /** * Rotate log files. * - * Logs are rotatated by prepending '.x' to the '.log' suffix. + * Logs are rotated by prepending '.x' to the '.log' suffix. * The current log plus 10 historical logs are maintained. * For example: * base.9.log -> [ REMOVED ] diff --git a/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php b/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php index 18838c25358..4adda364d1d 100644 --- a/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php +++ b/includes/shipping/legacy-flat-rate/class-wc-shipping-legacy-flat-rate.php @@ -6,7 +6,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Flat Rate Shipping Method. * - * This class is here for backwards commpatility for methods existing before zones existed. + * This class is here for backwards compatibility for methods existing before zones existed. * * @deprecated 2.6.0 * @version 2.4.0 diff --git a/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php b/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php index 9564f385e5e..dd2170f2d91 100644 --- a/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php +++ b/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php @@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Free Shipping Method. * - * This class is here for backwards commpatility for methods existing before zones existed. + * This class is here for backwards compatibility for methods existing before zones existed. * * @deprecated 2.6.0 * @version 2.4.0 diff --git a/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php b/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php index e6a76557c2e..8889195609b 100644 --- a/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php +++ b/includes/shipping/legacy-international-delivery/class-wc-shipping-legacy-international-delivery.php @@ -6,7 +6,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * International Delivery - Based on the Flat Rate Shipping Method. * - * This class is here for backwards commpatility for methods existing before zones existed. + * This class is here for backwards compatibility for methods existing before zones existed. * * @deprecated 2.6.0 * @version 2.4.0 diff --git a/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php b/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php index 643aa6ac064..065c4667e32 100644 --- a/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php +++ b/includes/shipping/legacy-local-delivery/class-wc-shipping-legacy-local-delivery.php @@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Local Delivery Shipping Method. * - * This class is here for backwards commpatility for methods existing before zones existed. + * This class is here for backwards compatibility for methods existing before zones existed. * * @deprecated 2.6.0 * @version 2.3.0 diff --git a/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php b/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php index c0f9697b9cf..2d303ae708c 100644 --- a/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php +++ b/includes/shipping/legacy-local-pickup/class-wc-shipping-legacy-local-pickup.php @@ -7,7 +7,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Local Pickup Shipping Method. * - * This class is here for backwards commpatility for methods existing before zones existed. + * This class is here for backwards compatibility for methods existing before zones existed. * * @deprecated 2.6.0 * @version 2.3.0 diff --git a/includes/shortcodes/class-wc-shortcode-checkout.php b/includes/shortcodes/class-wc-shortcode-checkout.php index 2836803c94e..3d91c8a5885 100644 --- a/includes/shortcodes/class-wc-shortcode-checkout.php +++ b/includes/shortcodes/class-wc-shortcode-checkout.php @@ -39,7 +39,7 @@ class WC_Shortcode_Checkout { return; } - // Backwards compat with old pay and thanks link arguments + // Backwards compatibility with old pay and thanks link arguments if ( isset( $_GET['order'] ) && isset( $_GET['key'] ) ) { wc_deprecated_argument( __CLASS__ . '->' . __FUNCTION__, '2.1', '"order" is no longer used to pass an order ID. Use the order-pay or order-received endpoint instead.' ); diff --git a/includes/vendor/abstract-wp-rest-controller.php b/includes/vendor/abstract-wp-rest-controller.php index 1b170e5d380..779a88bf7f0 100644 --- a/includes/vendor/abstract-wp-rest-controller.php +++ b/includes/vendor/abstract-wp-rest-controller.php @@ -27,7 +27,7 @@ abstract class WP_REST_Controller { * Register the routes for the objects of the controller. */ public function register_routes() { - wc_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overriden', 'woocommerce' ), 'WPAPI-2.0' ); + wc_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overridden', 'woocommerce' ), 'WPAPI-2.0' ); } /** diff --git a/includes/vendor/wp-rest-functions.php b/includes/vendor/wp-rest-functions.php index 1e55b8f2049..a3262f58d3a 100644 --- a/includes/vendor/wp-rest-functions.php +++ b/includes/vendor/wp-rest-functions.php @@ -139,7 +139,7 @@ if ( ! function_exists( 'register_rest_field' ) ) { if ( ! function_exists( 'register_api_field' ) ) { /** - * Backwards compat shim + * Backwards compatibility shim * * @param array|string $object_type * @param string $attributes diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php index b510d625ce2..1f75eda82a8 100644 --- a/includes/wc-order-functions.php +++ b/includes/wc-order-functions.php @@ -223,7 +223,7 @@ function wc_get_order_types( $for = '' ) { /** * Get an order type by post type name. * @param string post type name - * @return bool|array of datails about the order type + * @return bool|array of details about the order type */ function wc_get_order_type( $type ) { global $wc_order_types; diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index 997c811f9fc..9900373b170 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -1329,7 +1329,7 @@ if ( ! function_exists( 'woocommerce_related_products' ) ) { $args = wp_parse_args( $args, $defaults ); - // Get visble related products then sort them at random. + // Get visible related products then sort them at random. $args['related_products'] = array_filter( array_map( 'wc_get_product', wc_get_related_products( $product->get_id(), $args['posts_per_page'], $product->get_upsell_ids() ) ), 'wc_products_array_filter_visible' ); // Handle orderby. @@ -1371,7 +1371,7 @@ if ( ! function_exists( 'woocommerce_upsell_display' ) ) { $orderby = apply_filters( 'woocommerce_upsells_orderby', isset( $args['orderby'] ) ? $args['orderby'] : $orderby ); $limit = apply_filters( 'woocommerce_upsells_total', isset( $args['posts_per_page'] ) ? $args['posts_per_page'] : $limit ); - // Get visble upsells then sort them at random, then limit result set. + // Get visible upsells then sort them at random, then limit result set. $upsells = wc_products_array_orderby( array_filter( array_map( 'wc_get_product', $product->get_upsell_ids() ), 'wc_products_array_filter_visible' ), $orderby, $order ); $upsells = $limit > 0 ? array_slice( $upsells, 0, $limit ) : $upsells; diff --git a/includes/wc-user-functions.php b/includes/wc-user-functions.php index 9d572654eea..407865f05cf 100644 --- a/includes/wc-user-functions.php +++ b/includes/wc-user-functions.php @@ -17,7 +17,7 @@ if ( ! defined( 'ABSPATH' ) ) { /** * Prevent any user who cannot 'edit_posts' (subscribers, customers etc) from seeing the admin bar. * - * Note: get_option( 'woocommerce_lock_down_admin', true ) is a deprecated option here for backwards compat. Defaults to true. + * Note: get_option( 'woocommerce_lock_down_admin', true ) is a deprecated option here for backwards compatibility. Defaults to true. * * @access public * @param bool $show_admin_bar @@ -320,7 +320,7 @@ function wc_modify_editable_roles( $roles ) { add_filter( 'editable_roles', 'wc_modify_editable_roles' ); /** - * Modify capabiltiies to prevent non-admin users editing admin users. + * Modify capabilities to prevent non-admin users editing admin users. * * $args[0] will be the user being edited in this case. * diff --git a/includes/widgets/class-wc-widget-layered-nav-filters.php b/includes/widgets/class-wc-widget-layered-nav-filters.php index 1d85c6e7404..8ae2251f99b 100644 --- a/includes/widgets/class-wc-widget-layered-nav-filters.php +++ b/includes/widgets/class-wc-widget-layered-nav-filters.php @@ -62,7 +62,7 @@ class WC_Widget_Layered_Nav_Filters extends WC_Widget { $link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link ); } - // Orderby + // Order by if ( isset( $_GET['orderby'] ) ) { $link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link ); } diff --git a/includes/widgets/class-wc-widget-layered-nav.php b/includes/widgets/class-wc-widget-layered-nav.php index e267c81b2e8..6bd3b08d459 100644 --- a/includes/widgets/class-wc-widget-layered-nav.php +++ b/includes/widgets/class-wc-widget-layered-nav.php @@ -331,7 +331,7 @@ class WC_Widget_Layered_Nav extends WC_Widget { $link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link ); } - // Orderby + // Order by if ( isset( $_GET['orderby'] ) ) { $link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link ); } diff --git a/includes/widgets/class-wc-widget-rating-filter.php b/includes/widgets/class-wc-widget-rating-filter.php index 3a78a8bd623..da52ea377d3 100644 --- a/includes/widgets/class-wc-widget-rating-filter.php +++ b/includes/widgets/class-wc-widget-rating-filter.php @@ -56,7 +56,7 @@ class WC_Widget_Rating_Filter extends WC_Widget { $link = add_query_arg( 'max_price', wc_clean( $_GET['max_price'] ), $link ); } - // Orderby + // Order by if ( isset( $_GET['orderby'] ) ) { $link = add_query_arg( 'orderby', wc_clean( $_GET['orderby'] ), $link ); } From 8b1b8b6b28b35e555d73e030aff8fe68bdf64b69 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 18 Jul 2017 01:43:31 -0300 Subject: [PATCH 035/224] Initial WC_Discounts class --- includes/class-wc-discounts.php | 153 ++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 includes/class-wc-discounts.php diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php new file mode 100644 index 00000000000..14a7bd5a006 --- /dev/null +++ b/includes/class-wc-discounts.php @@ -0,0 +1,153 @@ +name = $name; + } + + /** + * Set amount. + * + * @param float $amount Total discount amount. + */ + public function set_amount( $amount ) { + $this->amount = $amount; + } + + /** + * Set type. + * + * @param string $type Type of discount. + */ + public function set_type( $type ) { + $this->type = in_array( $type, $this->get_available_types(), true ) ? $type : 'percent'; + } + + /** + * Set individual use. + * + * @param bool $individual_use Allow individual use. + */ + public function set_individual_use( $individual_use ) { + $this->individual_use = $individual_use; + } + + /** + * Set coupon ID. + * + * @param int $coupon_id Coupon ID. + */ + public function set_coupon_id( $coupon_id ) { + $this->coupon_id = $coupon_id; + } + + /** + * Get name. + * + * @return string. + */ + public function get_name() { + return $this->name; + } + + /** + * Get amount. + * + * @return float + */ + public function get_amount() { + return $this->amount; + } + + /** + * Get type. + * + * @return string + */ + public function get_type() { + return $this->type; + } + + /** + * Get individual use. + * + * @return bool + */ + public function get_individual_use() { + return $this->individual_use; + } + + /** + * Get coupon ID. + * + * @return int + */ + public function get_coupon_id() { + return $this->coupon_id; + } +} From 96f0a6e375767433977f686eb743568fb6b4d95e Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 11:46:07 +0100 Subject: [PATCH 036/224] DateTime docblocks --- includes/class-wc-datetime.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-datetime.php b/includes/class-wc-datetime.php index fe7873d78f9..985efff2d78 100644 --- a/includes/class-wc-datetime.php +++ b/includes/class-wc-datetime.php @@ -5,7 +5,8 @@ if ( ! defined( 'ABSPATH' ) ) { } /** - * WC Wrapper for PHP DateTime. + * WC Wrapper for PHP DateTime which adds support for gmt/utc offset when a + * timezone is absent. * * @class WC_DateTime * @since 3.0.0 @@ -16,13 +17,15 @@ if ( ! defined( 'ABSPATH' ) ) { class WC_DateTime extends DateTime { /** - * UTC Offset if needed. + * UTC Offset, if needed. Only used when a timezone is not set. When + * timezones are used this will equal 0. + * * @var integer */ protected $utc_offset = 0; /** - * Output an ISO 8601 date string in local timezone. + * Output an ISO 8601 date string in local (WordPress) timezone. * * @since 3.0.0 * @return string @@ -32,7 +35,7 @@ class WC_DateTime extends DateTime { } /** - * Set UTC offset. + * Set UTC offset - this is a fixed offset instead of a timezone. * * @param int $offset */ @@ -41,7 +44,7 @@ class WC_DateTime extends DateTime { } /** - * getOffset. + * Get UTC offset if set, or default to the DateTime object's offset. */ public function getOffset() { if ( $this->utc_offset ) { @@ -64,7 +67,7 @@ class WC_DateTime extends DateTime { } /** - * Missing in PHP 5.2. + * Missing in PHP 5.2 so just here so it can be supported consistently. * * @since 3.0.0 * @return int From 31d535c932d9f23e25e48b5f4371e3fbd0a30e3e Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 14:04:56 +0100 Subject: [PATCH 037/224] Methods and tests --- includes/class-wc-discounts.php | 179 ++++++++--------------- includes/class-woocommerce.php | 1 + tests/unit-tests/discounts/discounts.php | 34 +++++ 3 files changed, 93 insertions(+), 121 deletions(-) create mode 100644 tests/unit-tests/discounts/discounts.php diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 14a7bd5a006..7738dc0197c 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -14,140 +14,77 @@ class WC_Discounts { /** - * Display name. + * An array of items to discount. * - * @var string + * @var array */ - protected $name = ''; + protected $items = array(); /** - * Amount of discount. - * - * @var float - */ - protected $amount = 0; - - /** - * Type of discount. - * - * @var string - */ - protected $type = 'percent'; - - /** - * Allow or not accumulate with other discounts. - * - * @var bool - */ - protected $individual_use = false; - - /** - * Coupon ID. - * - * @var int - */ - protected $coupon_id = 0; - - /** - * Get available discount types. + * Get items. * + * @since 3.2.0 * @return array */ - public function get_available_types() { + public function get_items() { + return $this->items; + } + + /** + * Set cart/order items which will be discounted. + * + * @since 3.2.0 + * @param array $raw_items + * @todo Create https://github.com/woocommerce/woocommerce/pull/11889/files#diff-d9e4f5367e9d615985099b0d135629b8 class. + */ + public function set_items( $raw_items ) { + foreach ( $raw_items as $raw_item ) { + $item = array( + 'price' => 0, // Unit price without discounts. + 'qty' => 0, // Line qty. + 'discount' => 0, // Total discounts to apply. + ); + + if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { + + } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { + + } else { + // @todo remove when we implement WC_Cart_Item. This is the old cart item schema. + $item['qty'] = $values['quantity']; + $item['price'] = $values['data']->get_price(); + } + + $this->items[] = $item; + } + } + + /** + * Get all discount totals. + * + * @since 3.2.0 + * @return array + */ + public function get_discounts() { return array( - 'percent', - 'fixed_cart', - 'fixed_product', + 'items', + 'discount_totals' => array( + // 'code' => 'amount' + ) ); } /** - * Set name. + * Apply a discount to all items using a coupon. * - * @param string $name New name. + * @since 3.2.0 + * @param WC_Coupon $coupon + * @return bool True if applied. */ - public function set_name( $name ) { - $this->name = $name; - } - - /** - * Set amount. - * - * @param float $amount Total discount amount. - */ - public function set_amount( $amount ) { - $this->amount = $amount; - } - - /** - * Set type. - * - * @param string $type Type of discount. - */ - public function set_type( $type ) { - $this->type = in_array( $type, $this->get_available_types(), true ) ? $type : 'percent'; - } - - /** - * Set individual use. - * - * @param bool $individual_use Allow individual use. - */ - public function set_individual_use( $individual_use ) { - $this->individual_use = $individual_use; - } - - /** - * Set coupon ID. - * - * @param int $coupon_id Coupon ID. - */ - public function set_coupon_id( $coupon_id ) { - $this->coupon_id = $coupon_id; - } - - /** - * Get name. - * - * @return string. - */ - public function get_name() { - return $this->name; - } - - /** - * Get amount. - * - * @return float - */ - public function get_amount() { - return $this->amount; - } - - /** - * Get type. - * - * @return string - */ - public function get_type() { - return $this->type; - } - - /** - * Get individual use. - * - * @return bool - */ - public function get_individual_use() { - return $this->individual_use; - } - - /** - * Get coupon ID. - * - * @return int - */ - public function get_coupon_id() { - return $this->coupon_id; + public function apply_discount( $coupon ) { + if ( ! is_a( $coupon, 'WC_Coupon' ) ) { + return false; + } + // Do something to the items. } } diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index fea2e8a57d7..3b2fdbe3ec7 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -333,6 +333,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-deprecated-action-hooks.php' ); include_once( WC_ABSPATH . 'includes/class-wc-deprecated-filter-hooks.php' ); include_once( WC_ABSPATH . 'includes/class-wc-background-emailer.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-discounts.php' ); /** * Data stores - used to store and retrieve CRUD object data from the database. diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php new file mode 100644 index 00000000000..4a2f680a772 --- /dev/null +++ b/tests/unit-tests/discounts/discounts.php @@ -0,0 +1,34 @@ +cart->add_to_cart( $product->get_id(), 1 ); + WC()->cart->calculate_totals(); + + // Tests. + $discounts = new WC_Discounts(); + $discounts->set_items( WC()->cart->get_cart() ); + + $this->assertEquals( array( array( 'price' => '10', 'qty' => 1, 'discount' => 0 ) ), $discounts->get_items() ); + + // Cleanup. + WC()->cart->empty_cart(); + WC()->cart->remove_coupons(); + WC_Helper_Product::delete_product( $product->get_id() ); + } +} From dfd7aa78d53ba025a90d7f8e272811f20ee3784c Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 14:20:14 +0100 Subject: [PATCH 038/224] Use class raw data, don't pass it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #16127 caused an issue with fields which don’t require passing args e.g. esc_url_raw callback. This broke tests. --- includes/import/class-wc-product-csv-importer.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php index 19b52b692de..8ac995e10c0 100644 --- a/includes/import/class-wc-product-csv-importer.php +++ b/includes/import/class-wc-product-csv-importer.php @@ -186,10 +186,9 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { * for rows following this one. * * @param stirng $field - * @param array $raw_data * @return int */ - public function parse_id_field( $field, $raw_data = array() ) { + public function parse_id_field( $field ) { global $wpdb; $id = absint( $field ); @@ -208,7 +207,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { // Not updating? Make sure we have a new placeholder for this ID. if ( ! $this->params['update_existing'] ) { // If row has a SKU, make sure placeholder was not made already. - if ( isset( $raw_data['sku'] ) && $id = wc_get_product_id_by_sku( $raw_data['sku'] ) ) { + if ( isset( $this->raw_data['sku'] ) && $id = wc_get_product_id_by_sku( $this->raw_data['sku'] ) ) { return $id; } @@ -218,8 +217,8 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { $product->add_meta_data( '_original_id', $id, true ); // If row has a SKU, make sure placeholder has it too. - if ( isset( $raw_data['sku'] ) ) { - $product->set_sku( $raw_data['sku'] ); + if ( isset( $this->raw_data['sku'] ) ) { + $product->set_sku( $this->raw_data['sku'] ); } $id = $product->save(); } @@ -705,7 +704,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { $value = wp_check_invalid_utf8( $value, true ); } - $data[ $mapped_keys[ $id ] ] = call_user_func( $parse_functions[ $id ], $value, array_combine( $mapped_keys, $row ) ); + $data[ $mapped_keys[ $id ] ] = call_user_func( $parse_functions[ $id ], $value ); } $this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ), $this ); From 91dbdb0c4c8a8c021d933d05d79159ecc16cf153 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 14:29:14 +0100 Subject: [PATCH 039/224] Correct value --- includes/class-wc-discounts.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 7738dc0197c..706a0ffd354 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -51,8 +51,8 @@ class WC_Discounts { } else { // @todo remove when we implement WC_Cart_Item. This is the old cart item schema. - $item['qty'] = $values['quantity']; - $item['price'] = $values['data']->get_price(); + $item['qty'] = $raw_item['quantity']; + $item['price'] = $raw_item['data']->get_price(); } $this->items[] = $item; From 60bbb41be8e7834324ece6632c5abc602874008a Mon Sep 17 00:00:00 2001 From: Paul Wilde Date: Tue, 18 Jul 2017 15:03:26 +0100 Subject: [PATCH 040/224] Introduce `wc_get_account_orders_actions` function to reduce the amount of logic inside the account orders template files. --- includes/wc-account-functions.php | 39 +++++++++++++++++++++++++++++++ templates/myaccount/my-orders.php | 33 +++++--------------------- templates/myaccount/orders.php | 35 ++++++--------------------- 3 files changed, 52 insertions(+), 55 deletions(-) diff --git a/includes/wc-account-functions.php b/includes/wc-account-functions.php index a05f5acc872..54796c53d10 100644 --- a/includes/wc-account-functions.php +++ b/includes/wc-account-functions.php @@ -240,6 +240,45 @@ function wc_get_account_payment_methods_types() { ) ); } +/** + * Get account orders actions. + * + * @since 3.2.0 + * @param int|WC_Order $order + * @return array + */ +function wc_get_account_orders_actions( $order ) { + if ( ! is_object( $order ) ) { + $order_id = absint( $order ); + $order = wc_get_order( $order_id ); + } + + $actions = array( + 'pay' => array( + 'url' => $order->get_checkout_payment_url(), + 'name' => __( 'Pay', 'woocommerce' ), + ), + 'view' => array( + 'url' => $order->get_view_order_url(), + 'name' => __( 'View', 'woocommerce' ), + ), + 'cancel' => array( + 'url' => $order->get_cancel_order_url( wc_get_page_permalink( 'myaccount' ) ), + 'name' => __( 'Cancel', 'woocommerce' ), + ), + ); + + if ( ! $order->needs_payment() ) { + unset( $actions['pay'] ); + } + + if ( ! in_array( $order->get_status(), apply_filters( 'woocommerce_valid_order_statuses_for_cancel', array( 'pending', 'failed' ), $order ) ) ) { + unset( $actions['cancel'] ); + } + + return apply_filters( 'woocommerce_my_account_my_orders_actions', $actions, $order ); +} + /** * Returns an array of a user's saved payments list for output on the account tab. * diff --git a/templates/myaccount/my-orders.php b/templates/myaccount/my-orders.php index 417b0c09c31..45d40445928 100644 --- a/templates/myaccount/my-orders.php +++ b/templates/myaccount/my-orders.php @@ -69,34 +69,13 @@ if ( $customer_orders ) : ?> array( - 'url' => $order->get_checkout_payment_url(), - 'name' => __( 'Pay', 'woocommerce' ), - ), - 'view' => array( - 'url' => $order->get_view_order_url(), - 'name' => __( 'View', 'woocommerce' ), - ), - 'cancel' => array( - 'url' => $order->get_cancel_order_url( wc_get_page_permalink( 'myaccount' ) ), - 'name' => __( 'Cancel', 'woocommerce' ), - ), - ); - - if ( ! $order->needs_payment() ) { - unset( $actions['pay'] ); - } - - if ( ! in_array( $order->get_status(), apply_filters( 'woocommerce_valid_order_statuses_for_cancel', array( 'pending', 'failed' ), $order ) ) ) { - unset( $actions['cancel'] ); - } - - if ( $actions = apply_filters( 'woocommerce_my_account_my_orders_actions', $actions, $order ) ) { - foreach ( $actions as $key => $action ) { - echo '' . esc_html( $action['name'] ) . ''; - } + $actions = wc_get_account_orders_actions( $order ); + + if ( ! empty( $actions ) ) { + foreach ( $actions as $key => $action ) { + echo '' . esc_html( $action['name'] ) . ''; } + } ?> diff --git a/templates/myaccount/orders.php b/templates/myaccount/orders.php index 528ac40adae..e8c329e19d2 100644 --- a/templates/myaccount/orders.php +++ b/templates/myaccount/orders.php @@ -15,7 +15,7 @@ * @see https://docs.woocommerce.com/document/template-structure/ * @author WooThemes * @package WooCommerce/Templates - * @version 3.0.0 + * @version 3.2.0 */ if ( ! defined( 'ABSPATH' ) ) { @@ -65,34 +65,13 @@ do_action( 'woocommerce_before_account_orders', $has_orders ); ?> array( - 'url' => $order->get_checkout_payment_url(), - 'name' => __( 'Pay', 'woocommerce' ), - ), - 'view' => array( - 'url' => $order->get_view_order_url(), - 'name' => __( 'View', 'woocommerce' ), - ), - 'cancel' => array( - 'url' => $order->get_cancel_order_url( wc_get_page_permalink( 'myaccount' ) ), - 'name' => __( 'Cancel', 'woocommerce' ), - ), - ); - - if ( ! $order->needs_payment() ) { - unset( $actions['pay'] ); - } - - if ( ! in_array( $order->get_status(), apply_filters( 'woocommerce_valid_order_statuses_for_cancel', array( 'pending', 'failed' ), $order ) ) ) { - unset( $actions['cancel'] ); - } - - if ( $actions = apply_filters( 'woocommerce_my_account_my_orders_actions', $actions, $order ) ) { - foreach ( $actions as $key => $action ) { - echo '' . esc_html( $action['name'] ) . ''; - } + $actions = wc_get_account_orders_actions( $order ); + + if ( ! empty( $actions ) ) { + foreach ( $actions as $key => $action ) { + echo '' . esc_html( $action['name'] ) . ''; } + } ?> From b64d855d96e0192bf533b5be04297aa4506c7869 Mon Sep 17 00:00:00 2001 From: Paul Wilde Date: Tue, 18 Jul 2017 15:18:53 +0100 Subject: [PATCH 041/224] Introduce `wc_get_account_formatted_address` function which removes a bunch of logic outside of the my-account template file. --- includes/wc-account-functions.php | 30 ++++++++++++++++++++++++++++++ templates/myaccount/my-address.php | 21 ++------------------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/includes/wc-account-functions.php b/includes/wc-account-functions.php index a05f5acc872..20a35a3adbb 100644 --- a/includes/wc-account-functions.php +++ b/includes/wc-account-functions.php @@ -240,6 +240,36 @@ function wc_get_account_payment_methods_types() { ) ); } +/** + * Get account formatted address. + * + * @since 3.2.0 + * @param string $name + * @return string + */ +function wc_get_account_formatted_address( $name ) { + $customer_id = get_current_user_id(); + $meta_keys = [ + 'first_name', + 'last_name', + 'company', + 'address_1', + 'address_2', + 'city', + 'postcode', + 'country', + ]; + + $meta = []; + foreach ( $meta_keys as $key ) { + $meta[ $key ] = get_user_meta( $customer_id, $name . '_' . $key, true ); + } + + $address = apply_filters( 'woocommerce_my_account_my_address_formatted_address', $meta, $customer_id, $name ); + + return WC()->countries->get_formatted_address( $address ); +} + /** * Returns an array of a user's saved payments list for output on the account tab. * diff --git a/templates/myaccount/my-address.php b/templates/myaccount/my-address.php index db51bc2aa41..420c414ceca 100644 --- a/templates/myaccount/my-address.php +++ b/templates/myaccount/my-address.php @@ -54,25 +54,8 @@ $col = 1;
get_user_meta( $customer_id, $name . '_first_name', true ), - 'last_name' => get_user_meta( $customer_id, $name . '_last_name', true ), - 'company' => get_user_meta( $customer_id, $name . '_company', true ), - 'address_1' => get_user_meta( $customer_id, $name . '_address_1', true ), - 'address_2' => get_user_meta( $customer_id, $name . '_address_2', true ), - 'city' => get_user_meta( $customer_id, $name . '_city', true ), - 'state' => get_user_meta( $customer_id, $name . '_state', true ), - 'postcode' => get_user_meta( $customer_id, $name . '_postcode', true ), - 'country' => get_user_meta( $customer_id, $name . '_country', true ), - ), $customer_id, $name ); - - $formatted_address = WC()->countries->get_formatted_address( $address ); - - if ( ! $formatted_address ) { - _e( 'You have not set up this type of address yet.', 'woocommerce' ); - } else { - echo $formatted_address; - } + $address = wc_get_account_formatted_address( $name ); + echo $address ? $address : __( 'You have not set up this type of address yet.', 'woocommerce' ); ?>
From 7f715e477633911f66d73043659093696d303c11 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 15:42:29 +0100 Subject: [PATCH 042/224] WC_Cart_Item class --- includes/class-wc-cart-item.php | 218 ++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 includes/class-wc-cart-item.php diff --git a/includes/class-wc-cart-item.php b/includes/class-wc-cart-item.php new file mode 100644 index 00000000000..2ba4afe31bb --- /dev/null +++ b/includes/class-wc-cart-item.php @@ -0,0 +1,218 @@ + 0, + 'quantity' => 0, + 'variation' => array(), + ); + + /** + * Product this item represents. + * + * @var WC_Product + */ + protected $product = null; + + /** + * Constructor. + * + * @param array $data + */ + public function __construct( $data = array() ) { + $this->set_data( $data ); + } + + /** + * Gets price of the product. + * @return float + */ + public function get_price() { + return $this->get_product() ? $this->get_product()->get_price() : 0; + } + + /** + * Gets price of the product. + * @return float + */ + public function get_weight() { + return $this->get_product() ? $this->get_product()->get_weight() : 0; + } + + /** + * Gets price of the product. + * @return float + */ + public function get_tax_class() { + return $this->get_product() ? $this->get_product()->get_tax_class() : ''; + } + + /** + * Set product. + * @param int $value + */ + public function set_product( $value ) { + $this->product = $value; + $this->data['product_id'] = is_callable( array( $this->product, 'get_variation_id' ) ) ? $this->product->get_variation_id() : $this->product->get_id(); + } + + /** + * Get product object. + * + * @return WC_Product + */ + public function get_product() { + return ! is_null( $this->product ) ? $this->product : ( $this->product = wc_get_product( $this->get_product_id() ) ); + } + + /** + * Get all item data. + * @return array + */ + public function get_data() { + return $this->data; + } + + /** + * Product or variation ID this item represents. + * @return int + */ + public function get_product_id() { + return $this->data['product_id']; + } + + /** + * Get quantity in cart. + * @return int + */ + public function get_quantity() { + return $this->data['quantity']; + } + + /** + * Get variation data. + * @return array + */ + public function get_variation() { + return $this->data['variation']; + } + + /** + * Set product ID. + * @param int $value + */ + public function set_product_id( $value ) { + $this->data['product_id'] = absint( $value ); + $this->product = null; + } + + /** + * Set Quantity. + * @param int $value + */ + public function set_quantity( $value ) { + $this->data['quantity'] = wc_stock_amount( $value ); + } + + /** + * Set variation data. + * @param array $value + */ + public function set_variation( $value ) { + $this->data['variation'] = (array) $value; + } + + /** + * Set all data. + * @param array $value + */ + public function set_data( $values ) { + if ( is_a( $values, 'WC_Cart_Item' ) ) { + $values = $values->get_data(); + } + foreach ( $values as $key => $value ) { + if ( in_array( $key, array( 'quantity', 'product_id', 'variation', 'product' ) ) ) { + $this->{ "set_$key" }( $value ); + } else { + $this->data[ $key ] = $value; + } + } + } + + /** + * ArrayAccess/Backwards compatibility. + * + * @param string $offset + * @return mixed + */ + public function offsetGet( $offset ) { + switch ( $offset ) { + case 'data' : + return $this->get_product(); + case 'variation_id' : + return is_callable( array( $this, 'get_variation_id' ) ) ? $this->get_product()->get_variation_id() : 0; + } + return isset( $this->data[ $offset ] ) ? $this->data[ $offset ] : ''; + } + + /** + * ArrayAccess/Backwards compatibility. + * + * @param string $offset + * @param mixed $value + */ + public function offsetSet( $offset, $value ) { + switch ( $offset ) { + case 'data' : + $this->set_product( $value ); + break; + case 'variation_id' : + $this->set_product( wc_get_product( $value ) ); + break; + default : + $this->data[ $offset ] = $value; + break; + } + } + + /** + * ArrayAccess/Backwards compatibility. + * + * @param string $offset + * @return bool + */ + public function offsetExists( $offset ) { + if ( in_array( $offset, array( 'data' ) ) || isset( $this->data[ $offset ] ) ) { + return true; + } + return false; + } + + /** + * ArrayAccess/Backwards compatibility. + * + * @param string $offset + */ + public function offsetUnset( $offset ) { + unset( $this->data[ $offset ] ); + } +} From cc8eb287247ff5bd6e6a924c811a903c67449d91 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 15:42:46 +0100 Subject: [PATCH 043/224] Set items based on cart or an order, with tests --- includes/class-wc-discounts.php | 37 +++++++++++++----------- tests/unit-tests/discounts/discounts.php | 31 ++++++++++++++++---- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 706a0ffd354..b94debb51d4 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -24,7 +24,7 @@ class WC_Discounts { * Get items. * * @since 3.2.0 - * @return array + * @return object[] */ public function get_items() { return $this->items; @@ -35,27 +35,30 @@ class WC_Discounts { * * @since 3.2.0 * @param array $raw_items - * @todo Create https://github.com/woocommerce/woocommerce/pull/11889/files#diff-d9e4f5367e9d615985099b0d135629b8 class. */ public function set_items( $raw_items ) { - foreach ( $raw_items as $raw_item ) { - $item = array( - 'price' => 0, // Unit price without discounts. - 'qty' => 0, // Line qty. - 'discount' => 0, // Total discounts to apply. - ); + if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { + foreach ( $raw_items as $raw_item ) { + $item = (object) array( + 'price' => 0, // Unit price without discounts. + 'quantity' => 0, // Line qty. + 'discount' => 0, // Total discounts to apply. + ); - if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { + if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { + $item->quantity = $raw_item->get_quantity(); + $item->price = $raw_item->get_price(); + } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { + $item->quantity = $raw_item->get_quantity(); + $item->price = $raw_item->get_subtotal(); + } else { + // @todo remove when we implement WC_Cart_Item. This is the old cart item schema. + $item->quantity = $raw_item['quantity']; + $item->price = $raw_item['data']->get_price(); + } - } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { - - } else { - // @todo remove when we implement WC_Cart_Item. This is the old cart item schema. - $item['qty'] = $raw_item['quantity']; - $item['price'] = $raw_item['data']->get_price(); + $this->items[] = $item; } - - $this->items[] = $item; } } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 4a2f680a772..09946d25daa 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -6,7 +6,9 @@ */ class WC_Tests_Discounts extends WC_Unit_Test_Case { - + /** + * Test get and set items. + */ public function test_get_set_items() { // We need this to have the calculate_totals() method calculate totals if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { @@ -16,15 +18,34 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Create dummy product - price will be 10 $product = WC_Helper_Product::create_simple_product(); - // Add product to cart x1, calc and test + // Add product to the cart. WC()->cart->add_to_cart( $product->get_id(), 1 ); - WC()->cart->calculate_totals(); - // Tests. + // Add product to a dummy order. + $order = new WC_Order(); + $order->add_product( $product, 4 ); + $order->calculate_totals(); + $order->save(); + + // Test setting items to the cart. $discounts = new WC_Discounts(); $discounts->set_items( WC()->cart->get_cart() ); + $this->assertEquals( array( (object) array( 'price' => '10', 'quantity' => 1, 'discount' => 0 ) ), $discounts->get_items() ); - $this->assertEquals( array( array( 'price' => '10', 'qty' => 1, 'discount' => 0 ) ), $discounts->get_items() ); + // Test setting items to an order. + $discounts = new WC_Discounts(); + $discounts->set_items( $order->get_items() ); + $this->assertEquals( array( (object) array( 'price' => '40', 'quantity' => 4, 'discount' => 0 ) ), $discounts->get_items() ); + + // Empty array of items. + $discounts = new WC_Discounts(); + $discounts->set_items( array() ); + $this->assertEquals( array(), $discounts->get_items() ); + + // Invalid items. + $discounts = new WC_Discounts(); + $discounts->set_items( false ); + $this->assertEquals( array(), $discounts->get_items() ); // Cleanup. WC()->cart->empty_cart(); From 268628c09c06f95de602b7455dd11ea44d9d9952 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 15:42:54 +0100 Subject: [PATCH 044/224] Include cart item class --- includes/class-woocommerce.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 3b2fdbe3ec7..58876dd9f54 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -404,6 +404,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-frontend-scripts.php' ); // Frontend Scripts. include_once( WC_ABSPATH . 'includes/class-wc-form-handler.php' ); // Form Handlers. include_once( WC_ABSPATH . 'includes/class-wc-cart.php' ); // The main cart class. + include_once( WC_ABSPATH . 'includes/class-wc-cart-item.php' ); include_once( WC_ABSPATH . 'includes/class-wc-tax.php' ); // Tax class. include_once( WC_ABSPATH . 'includes/class-wc-shipping-zones.php' ); // Shipping Zones class. include_once( WC_ABSPATH . 'includes/class-wc-customer.php' ); // Customer class. From 25038f6d0fa7641ec690321f2a7d1d3eed0deed9 Mon Sep 17 00:00:00 2001 From: Nick Hoobin Date: Tue, 18 Jul 2017 10:38:36 -0500 Subject: [PATCH 045/224] Add a filter to arguments passed to wc_get_products during prepare_data_to_export() --- includes/export/class-wc-product-csv-exporter.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/export/class-wc-product-csv-exporter.php b/includes/export/class-wc-product-csv-exporter.php index 994ce0cbb15..e65a655ddb5 100644 --- a/includes/export/class-wc-product-csv-exporter.php +++ b/includes/export/class-wc-product-csv-exporter.php @@ -124,7 +124,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { */ public function prepare_data_to_export() { $columns = $this->get_column_names(); - $products = wc_get_products( array( + $args = apply_filters( "woocommerce_product_export_{$this->export_type}_query_args", array( 'status' => array( 'private', 'publish' ), 'type' => $this->product_types_to_export, 'limit' => $this->get_limit(), @@ -135,6 +135,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { 'return' => 'objects', 'paginate' => true, ) ); + $products = wc_get_products( $args ); $this->total_rows = $products->total; $this->row_data = array(); From 6a9e612a8371eee866b051035e9ad939162d8517 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 18:07:46 +0100 Subject: [PATCH 046/224] Some progress on actual discounts --- includes/class-wc-discounts.php | 101 +++++++++++++++++++++-- tests/unit-tests/discounts/discounts.php | 54 +++++++++--- 2 files changed, 139 insertions(+), 16 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index b94debb51d4..88a0f5cf39b 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -20,6 +20,20 @@ class WC_Discounts { */ protected $items = array(); + /** + * An array of coupons which have been applied. + * + * @var array + */ + protected $coupons = array(); + + /** + * An array of discounts which have been applied. + * + * @var array + */ + protected $discounts = array(); + /** * Get items. * @@ -34,15 +48,15 @@ class WC_Discounts { * Set cart/order items which will be discounted. * * @since 3.2.0 - * @param array $raw_items + * @param array $raw_items List of raw cart or order items. */ public function set_items( $raw_items ) { if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { foreach ( $raw_items as $raw_item ) { $item = (object) array( - 'price' => 0, // Unit price without discounts. - 'quantity' => 0, // Line qty. - 'discount' => 0, // Total discounts to apply. + 'price' => 0, // Unit price without discounts. + 'discounted_price' => 0, // Unit price with discounts. + 'quantity' => 0, // Line qty. ); if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { @@ -57,11 +71,18 @@ class WC_Discounts { $item->price = $raw_item['data']->get_price(); } + $item->discounted_price = $item->price; $this->items[] = $item; } } } + public function get_discounted_totals() {} + + public function get_applied_discounts() {} + + public function get_applied_coupons() {} + /** * Get all discount totals. * @@ -84,10 +105,78 @@ class WC_Discounts { * @param WC_Coupon $coupon * @return bool True if applied. */ - public function apply_discount( $coupon ) { + public function apply_coupon( $coupon ) { if ( ! is_a( $coupon, 'WC_Coupon' ) ) { return false; } - // Do something to the items. + $items = $this->get_items(); + $type = $coupon->get_discount_type(); + $amount = $coupon->get_amount(); + + switch ( $type ) { + case 'percent' : + $this->apply_percentage_discount( $amount ); + break; + case 'fixed' : + $this->apply_fixed_discount( $amount ); + break; + } + } + + /** + * Apply percent discount to items. + * + * @param float $amount + */ + private function apply_percentage_discount( $amount ) { + foreach ( $this->items as $item ) { + $discount = (float) $amount * ( $item->discounted_price / 100 ); + $item->discounted_price -= $discount; + $item->discounted_price = max( 0, $item->discounted_price ); + } + } + + /** + * Apply fixed discount to items. + * + * @param float $amount + */ + private function apply_fixed_discount( $total_amount ) { + // Fixed amount needs to be divided equally between items. + $item_count = array_sum( wp_list_pluck( $this->items, 'quantity' ) ); + $discount = floor( $total_amount / $item_count ); + $discounted = 0; // Keep track of what actually gets discounted, since some products may be cheaper than the discount. + + if ( $discount ) { + foreach ( $this->items as $item ) { + $discounted += min( $discount, $item->discounted_price ); + $item->discounted_price -= $discount; + } + + // Anything left? + if ( $remaining_discount = $total_amount - $discounted ) { + $this->apply_fixed_discount( $remaining_discount ); + } + + // Amount is too small to divide equally. + } elseif ( $total_amount ) { + $discount = $total_amount; + + while ( $discount > 0 ) { + $did_discount = false; + + foreach ( $this->items as $item ) { + if ( $item->discounted_price ) { + $item->discounted_price --; + $discount --; + $did_discount = true; + } + } + + if ( ! $did_discount ) { + break; + } + } + } } } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 09946d25daa..67a4cec86fc 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -10,11 +10,6 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { * Test get and set items. */ public function test_get_set_items() { - // We need this to have the calculate_totals() method calculate totals - if ( ! defined( 'WOOCOMMERCE_CHECKOUT' ) ) { - define( 'WOOCOMMERCE_CHECKOUT', true ); - } - // Create dummy product - price will be 10 $product = WC_Helper_Product::create_simple_product(); @@ -23,19 +18,19 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Add product to a dummy order. $order = new WC_Order(); - $order->add_product( $product, 4 ); + $order->add_product( $product, 1 ); $order->calculate_totals(); $order->save(); // Test setting items to the cart. $discounts = new WC_Discounts(); $discounts->set_items( WC()->cart->get_cart() ); - $this->assertEquals( array( (object) array( 'price' => '10', 'quantity' => 1, 'discount' => 0 ) ), $discounts->get_items() ); + $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '10', 'quantity' => 1 ) ), $discounts->get_items() ); // Test setting items to an order. $discounts = new WC_Discounts(); $discounts->set_items( $order->get_items() ); - $this->assertEquals( array( (object) array( 'price' => '40', 'quantity' => 4, 'discount' => 0 ) ), $discounts->get_items() ); + $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '10', 'quantity' => 1 ) ), $discounts->get_items() ); // Empty array of items. $discounts = new WC_Discounts(); @@ -49,7 +44,46 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Cleanup. WC()->cart->empty_cart(); - WC()->cart->remove_coupons(); - WC_Helper_Product::delete_product( $product->get_id() ); + $product->delete( true ); + $order->delete( true ); + } + + /** + * Test applying a coupon to a set of items. + */ + public function test_apply_coupon() { + $discounts = new WC_Discounts(); + + // Create dummy content. + $product = WC_Helper_Product::create_simple_product(); + WC()->cart->add_to_cart( $product->get_id(), 1 ); + $coupon = new WC_Coupon; + $coupon->set_code( 'test' ); + $coupon->set_discount_type( 'percent' ); + $coupon->set_amount( 20 ); + + // Apply a percent discount. + $coupon->set_discount_type( 'percent' ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '8', 'quantity' => 1 ) ), $discounts->get_items() ); + + // Apply a fixed coupon. + $coupon->set_discount_type( 'fixed' ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '0', 'quantity' => 1 ) ), $discounts->get_items() ); + + // Apply a fixed coupon. + $coupon->set_discount_type( 'fixed_cart' ); + WC()->cart->empty_cart(); + WC()->cart->add_to_cart( $product->get_id(), 4 ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( array( (object) array( 'price' => '40', 'discounted_price' => '20', 'quantity' => 4 ) ), $discounts->get_items() ); + + // Cleanup. + WC()->cart->empty_cart(); + $product->delete( true ); } } From 2b9e3aafdd03829340e7b6c23937cc08a35a7e91 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 18:47:05 +0100 Subject: [PATCH 047/224] fix endless loop of death --- includes/class-wc-discounts.php | 42 +++++++++++++++++++----- tests/unit-tests/discounts/discounts.php | 17 ++++++++-- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 88a0f5cf39b..6e116af7c81 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -51,6 +51,8 @@ class WC_Discounts { * @param array $raw_items List of raw cart or order items. */ public function set_items( $raw_items ) { + $this->items = array(); + if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { foreach ( $raw_items as $raw_item ) { $item = (object) array( @@ -117,8 +119,11 @@ class WC_Discounts { case 'percent' : $this->apply_percentage_discount( $amount ); break; - case 'fixed' : - $this->apply_fixed_discount( $amount ); + case 'fixed_product' : + $this->apply_fixed_product_discount( $amount ); + break; + case 'fixed_cart' : + $this->apply_fixed_cart_discount( $amount ); break; } } @@ -137,25 +142,44 @@ class WC_Discounts { } /** - * Apply fixed discount to items. + * Apply fixed product discount to items. * * @param float $amount */ - private function apply_fixed_discount( $total_amount ) { + private function apply_fixed_product_discount( $discount ) { + foreach ( $this->items as $item ) { + $item->discounted_price -= $discount; + $item->discounted_price = max( 0, $item->discounted_price ); + } + } + + private function apply_discount_to_item( &$item, $discount ) { + if ( $discount > $item->discounted_price ) { + $discount = $item->discounted_price; + } + $item->discounted_price = $item->discounted_price - $discount; + return $discount; + } + + /** + * Apply fixed cart discount to items. + * + * @param float $amount + */ + private function apply_fixed_cart_discount( $total_amount ) { // Fixed amount needs to be divided equally between items. $item_count = array_sum( wp_list_pluck( $this->items, 'quantity' ) ); $discount = floor( $total_amount / $item_count ); $discounted = 0; // Keep track of what actually gets discounted, since some products may be cheaper than the discount. - if ( $discount ) { + if ( 0 < $discount ) { foreach ( $this->items as $item ) { - $discounted += min( $discount, $item->discounted_price ); - $item->discounted_price -= $discount; + $discounted += $this->apply_discount_to_item( $item, $discount ); } // Anything left? - if ( $remaining_discount = $total_amount - $discounted ) { - $this->apply_fixed_discount( $remaining_discount ); + if ( $discounted && ( $remaining_discount = $total_amount - $discounted ) ) { + $this->apply_fixed_cart_discount( $remaining_discount ); } // Amount is too small to divide equally. diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 67a4cec86fc..ae45793b371 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -56,6 +56,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Create dummy content. $product = WC_Helper_Product::create_simple_product(); + WC()->cart->empty_cart(); WC()->cart->add_to_cart( $product->get_id(), 1 ); $coupon = new WC_Coupon; $coupon->set_code( 'test' ); @@ -68,13 +69,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $discounts->apply_coupon( $coupon ); $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '8', 'quantity' => 1 ) ), $discounts->get_items() ); - // Apply a fixed coupon. - $coupon->set_discount_type( 'fixed' ); + // Apply a fixed cart coupon. + $coupon->set_discount_type( 'fixed_cart' ); $discounts->set_items( WC()->cart->get_cart() ); $discounts->apply_coupon( $coupon ); + var_dump($discounts->get_items()); $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '0', 'quantity' => 1 ) ), $discounts->get_items() ); - // Apply a fixed coupon. + // Apply a fixed cart coupon. $coupon->set_discount_type( 'fixed_cart' ); WC()->cart->empty_cart(); WC()->cart->add_to_cart( $product->get_id(), 4 ); @@ -82,6 +84,15 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $discounts->apply_coupon( $coupon ); $this->assertEquals( array( (object) array( 'price' => '40', 'discounted_price' => '20', 'quantity' => 4 ) ), $discounts->get_items() ); + // Apply a fixed product coupon. + $coupon->set_discount_type( 'fixed_product' ); + $coupon->set_amount( 1 ); + WC()->cart->empty_cart(); + WC()->cart->add_to_cart( $product->get_id(), 4 ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( array( (object) array( 'price' => '40', 'discounted_price' => '36', 'quantity' => 4 ) ), $discounts->get_items() ); + // Cleanup. WC()->cart->empty_cart(); $product->delete( true ); From 37fbd96de8533ece005b6ae6b95d7d2a49c46bd3 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 18:52:50 +0100 Subject: [PATCH 048/224] Small tidyup --- includes/class-wc-discounts.php | 35 +++++++++++++----------- tests/unit-tests/discounts/discounts.php | 1 - 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 6e116af7c81..c2439fe48fe 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -135,9 +135,7 @@ class WC_Discounts { */ private function apply_percentage_discount( $amount ) { foreach ( $this->items as $item ) { - $discount = (float) $amount * ( $item->discounted_price / 100 ); - $item->discounted_price -= $discount; - $item->discounted_price = max( 0, $item->discounted_price ); + $this->apply_discount_to_item( $item, (float) $amount * ( $item->discounted_price / 100 ) ); } } @@ -148,19 +146,10 @@ class WC_Discounts { */ private function apply_fixed_product_discount( $discount ) { foreach ( $this->items as $item ) { - $item->discounted_price -= $discount; - $item->discounted_price = max( 0, $item->discounted_price ); + $this->apply_discount_to_item( $item, $discount ); } } - private function apply_discount_to_item( &$item, $discount ) { - if ( $discount > $item->discounted_price ) { - $discount = $item->discounted_price; - } - $item->discounted_price = $item->discounted_price - $discount; - return $discount; - } - /** * Apply fixed cart discount to items. * @@ -190,9 +179,8 @@ class WC_Discounts { $did_discount = false; foreach ( $this->items as $item ) { - if ( $item->discounted_price ) { - $item->discounted_price --; - $discount --; + if ( $this->apply_discount_to_item( $item, 0.1 ) ) { + $discount -= 0.1; $did_discount = true; } } @@ -203,4 +191,19 @@ class WC_Discounts { } } } + + /** + * Apply a discount amount to an item and ensure it does not go negative. + * + * @param object $item + * @param float $discount + * @return float + */ + private function apply_discount_to_item( &$item, $discount ) { + if ( $discount > $item->discounted_price ) { + $discount = $item->discounted_price; + } + $item->discounted_price = $item->discounted_price - $discount; + return $discount; + } } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index ae45793b371..e5cf4f0c367 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -73,7 +73,6 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $coupon->set_discount_type( 'fixed_cart' ); $discounts->set_items( WC()->cart->get_cart() ); $discounts->apply_coupon( $coupon ); - var_dump($discounts->get_items()); $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '0', 'quantity' => 1 ) ), $discounts->get_items() ); // Apply a fixed cart coupon. From d24faebea83dc1afa8b2723443e3e23424a4f044 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 20:42:47 +0100 Subject: [PATCH 049/224] Split fixed cart from other discounts in class. --- includes/class-wc-cart.php | 11 +- includes/class-wc-discounts.php | 196 ++++++++++++----------- tests/unit-tests/discounts/discounts.php | 32 +++- 3 files changed, 135 insertions(+), 104 deletions(-) diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index e0edaa7cc3f..23e17377e94 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -954,11 +954,12 @@ class WC_Cart { // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item $this->cart_contents[ $cart_item_key ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array( - 'product_id' => $product_id, - 'variation_id' => $variation_id, - 'variation' => $variation, - 'quantity' => $quantity, - 'data' => $product_data, + 'key' => $cart_item_key, + 'product_id' => $product_id, + 'variation_id' => $variation_id, + 'variation' => $variation, + 'quantity' => $quantity, + 'data' => $product_data, ) ), $cart_item_key ); } diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index c2439fe48fe..b24cfbad5bb 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -20,13 +20,6 @@ class WC_Discounts { */ protected $items = array(); - /** - * An array of coupons which have been applied. - * - * @var array - */ - protected $coupons = array(); - /** * An array of discounts which have been applied. * @@ -34,6 +27,14 @@ class WC_Discounts { */ protected $discounts = array(); + /** + * Reset items and discounts to 0. + */ + protected function reset() { + $this->items = array(); + $this->discounts = array( 'cart' => 0 ); + } + /** * Get items. * @@ -51,40 +52,38 @@ class WC_Discounts { * @param array $raw_items List of raw cart or order items. */ public function set_items( $raw_items ) { - $this->items = array(); + $this->reset(); if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { foreach ( $raw_items as $raw_item ) { $item = (object) array( - 'price' => 0, // Unit price without discounts. - 'discounted_price' => 0, // Unit price with discounts. - 'quantity' => 0, // Line qty. + 'price' => 0, // Line price without discounts. + 'quantity' => 0, // Line qty. + 'product' => false, ); - if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { - $item->quantity = $raw_item->get_quantity(); - $item->price = $raw_item->get_price(); + //$item->quantity = $raw_item->get_quantity(); + //$item->price = $raw_item->get_price() * $raw_item->get_quantity(); + //$item->is_taxable = $raw_item->is_taxable(); + //$item->tax_class = $raw_item->get_tax_class(); + // @todo } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { + $item->key = $raw_item->get_id(); $item->quantity = $raw_item->get_quantity(); - $item->price = $raw_item->get_subtotal(); + $item->product = $raw_item->get_product(); } else { + $item->key = $raw_item['key']; // @todo remove when we implement WC_Cart_Item. This is the old cart item schema. $item->quantity = $raw_item['quantity']; - $item->price = $raw_item['data']->get_price(); + $item->price = $raw_item['data']->get_price() * $raw_item['quantity']; + $item->product = $raw_item['data']; } - - $item->discounted_price = $item->price; - $this->items[] = $item; + $this->items[ $item->key ] = $item; + $this->discounts[ $item->key ] = 0; } } } - public function get_discounted_totals() {} - - public function get_applied_discounts() {} - - public function get_applied_coupons() {} - /** * Get all discount totals. * @@ -92,12 +91,17 @@ class WC_Discounts { * @return array */ public function get_discounts() { - return array( - 'items', - 'discount_totals' => array( - // 'code' => 'amount' - ) - ); + return $this->discounts; + } + + /** + * Get discount by key. + * + * @since 3.2.0 + * @return array + */ + public function get_discount( $key ) { + return isset( $this->discounts[ $key ] ) ? $this->discounts[ $key ] : 0; } /** @@ -111,99 +115,105 @@ class WC_Discounts { if ( ! is_a( $coupon, 'WC_Coupon' ) ) { return false; } - $items = $this->get_items(); - $type = $coupon->get_discount_type(); - $amount = $coupon->get_amount(); - - switch ( $type ) { + switch ( $coupon->get_discount_type() ) { case 'percent' : - $this->apply_percentage_discount( $amount ); + $this->apply_percentage_discount( $coupon->get_amount() ); break; case 'fixed_product' : - $this->apply_fixed_product_discount( $amount ); + $this->apply_fixed_product_discount( $coupon->get_amount() ); break; case 'fixed_cart' : - $this->apply_fixed_cart_discount( $amount ); + $this->apply_fixed_cart_discount( $coupon->get_amount() ); break; } } + /** + * Get discounted price of an item. + * + * @since 3.2.0 + * @param object $item + * @return float + */ + public function get_discounted_price( $item ) { + return $item->price - $this->discounts[ $item->key ]; + } + + /** + * Apply a discount amount to an item and ensure it does not go negative. + * + * @since 3.2.0 + * @param object $item + * @param float $discount + * @return float + */ + protected function apply_discount_to_item( &$item, $discount ) { + $discounted_price = $this->get_discounted_price( $item ); + $discount = $discount > $discounted_price ? $discounted_price : $discount; + $this->discounts[ $item->key ] = $this->discounts[ $item->key ] + $discount; + return $discount; + } + + /** + * Apply a discount amount to the cart. + * + * @since 3.2.0 + * @param object $item + * @param float $discount + */ + protected function apply_discount_to_cart( $discount ) { + $this->discounts['cart'] += $discount; + } + /** * Apply percent discount to items. * + * @since 3.2.0 * @param float $amount */ - private function apply_percentage_discount( $amount ) { + protected function apply_percentage_discount( $amount ) { foreach ( $this->items as $item ) { - $this->apply_discount_to_item( $item, (float) $amount * ( $item->discounted_price / 100 ) ); + $this->apply_discount_to_item( $item, (float) $amount * ( $this->get_discounted_price( $item ) / 100 ) ); } } /** * Apply fixed product discount to items. * + * @since 3.2.0 * @param float $amount */ - private function apply_fixed_product_discount( $discount ) { + protected function apply_fixed_product_discount( $discount ) { foreach ( $this->items as $item ) { - $this->apply_discount_to_item( $item, $discount ); + $this->apply_discount_to_item( $item, $discount * $item->quantity ); } } - /** - * Apply fixed cart discount to items. - * - * @param float $amount - */ - private function apply_fixed_cart_discount( $total_amount ) { - // Fixed amount needs to be divided equally between items. - $item_count = array_sum( wp_list_pluck( $this->items, 'quantity' ) ); - $discount = floor( $total_amount / $item_count ); - $discounted = 0; // Keep track of what actually gets discounted, since some products may be cheaper than the discount. - - if ( 0 < $discount ) { - foreach ( $this->items as $item ) { - $discounted += $this->apply_discount_to_item( $item, $discount ); - } - - // Anything left? - if ( $discounted && ( $remaining_discount = $total_amount - $discounted ) ) { - $this->apply_fixed_cart_discount( $remaining_discount ); - } - - // Amount is too small to divide equally. - } elseif ( $total_amount ) { - $discount = $total_amount; - - while ( $discount > 0 ) { - $did_discount = false; - - foreach ( $this->items as $item ) { - if ( $this->apply_discount_to_item( $item, 0.1 ) ) { - $discount -= 0.1; - $did_discount = true; - } - } - - if ( ! $did_discount ) { - break; - } - } - } - } + /*protected function filter_taxable_items( $item ) { + return $item->is_taxable; + }*/ /** - * Apply a discount amount to an item and ensure it does not go negative. + * Apply fixed cart discount to items. Cart discounts will be stored and + * displayed separately to items, and taxes will be apportioned. * - * @param object $item - * @param float $discount - * @return float + * @since 3.2.0 + * @param float $cart_discount */ - private function apply_discount_to_item( &$item, $discount ) { - if ( $discount > $item->discounted_price ) { - $discount = $item->discounted_price; + protected function apply_fixed_cart_discount( $cart_discount ) { + // @todo Fixed cart discounts will be apportioned based on tax class. + /*$tax_class_counts = array_count_values( wp_list_pluck( array_filter( $this->items, array( $this, 'filter_taxable_items' ) ), 'tax_class' ) ); + $item_count = array_sum( wp_list_pluck( $this->items, 'quantity' ) ); + $cart_discount_taxes = array(); + + foreach ( $tax_class_counts as $tax_class => $tax_class_count ) { + $proportion = $tax_class_count / $item_count; + $cart_discount_proportion = $cart_discount * $proportion; + $tax_rates = WC_Tax::get_rates( $tax_class ); + $cart_discount_taxes[ $tax_class ] = WC_Tax::calc_tax( $cart_discount_proportion, $tax_rates ); } - $item->discounted_price = $item->discounted_price - $discount; - return $discount; + + var_dump($cart_discount_taxes);*/ + $this->apply_discount_to_cart( $cart_discount ); } } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index e5cf4f0c367..e0ccc69e86a 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -25,12 +25,12 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Test setting items to the cart. $discounts = new WC_Discounts(); $discounts->set_items( WC()->cart->get_cart() ); - $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '10', 'quantity' => 1 ) ), $discounts->get_items() ); + $this->assertEquals( 1, count( $discounts->get_items() ) ); // Test setting items to an order. $discounts = new WC_Discounts(); $discounts->set_items( $order->get_items() ); - $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '10', 'quantity' => 1 ) ), $discounts->get_items() ); + $this->assertEquals( 1, count( $discounts->get_items() ) ); // Empty array of items. $discounts = new WC_Discounts(); @@ -55,7 +55,22 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $discounts = new WC_Discounts(); // Create dummy content. + $tax_rate = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '20.0000', + 'tax_rate_name' => 'VAT', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => '', + ); + $tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate ); + update_option( 'woocommerce_calc_taxes', 'yes' ); $product = WC_Helper_Product::create_simple_product(); + $product->set_tax_status( 'taxable' ); + $product->save(); WC()->cart->empty_cart(); WC()->cart->add_to_cart( $product->get_id(), 1 ); $coupon = new WC_Coupon; @@ -67,13 +82,13 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $coupon->set_discount_type( 'percent' ); $discounts->set_items( WC()->cart->get_cart() ); $discounts->apply_coupon( $coupon ); - $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '8', 'quantity' => 1 ) ), $discounts->get_items() ); + $this->assertEquals( 8, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); // Apply a fixed cart coupon. $coupon->set_discount_type( 'fixed_cart' ); $discounts->set_items( WC()->cart->get_cart() ); $discounts->apply_coupon( $coupon ); - $this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '0', 'quantity' => 1 ) ), $discounts->get_items() ); + $this->assertEquals( 20, $discounts->get_discount( 'cart' ) ); // Apply a fixed cart coupon. $coupon->set_discount_type( 'fixed_cart' ); @@ -81,7 +96,10 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { WC()->cart->add_to_cart( $product->get_id(), 4 ); $discounts->set_items( WC()->cart->get_cart() ); $discounts->apply_coupon( $coupon ); - $this->assertEquals( array( (object) array( 'price' => '40', 'discounted_price' => '20', 'quantity' => 4 ) ), $discounts->get_items() ); + $this->assertEquals( 20, $discounts->get_discount( 'cart' ) ); + + $discounts->apply_coupon( $coupon ); + $this->assertEquals( 40, $discounts->get_discount( 'cart' ) ); // Apply a fixed product coupon. $coupon->set_discount_type( 'fixed_product' ); @@ -90,10 +108,12 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { WC()->cart->add_to_cart( $product->get_id(), 4 ); $discounts->set_items( WC()->cart->get_cart() ); $discounts->apply_coupon( $coupon ); - $this->assertEquals( array( (object) array( 'price' => '40', 'discounted_price' => '36', 'quantity' => 4 ) ), $discounts->get_items() ); + $this->assertEquals( 36, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); // Cleanup. WC()->cart->empty_cart(); $product->delete( true ); + WC_Tax::_delete_tax_rate( $tax_rate_id ); + update_option( 'woocommerce_calc_taxes', 'no' ); } } From 3a76e4492e7e3f7618208db58f66086072551d5b Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 18 Jul 2017 20:48:19 +0100 Subject: [PATCH 050/224] todos --- includes/class-wc-discounts.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index b24cfbad5bb..89d2693ef75 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -10,6 +10,8 @@ /** * Discounts class. + * + * @todo this class will need to be called instead get_discounted_price, in the cart? */ class WC_Discounts { @@ -115,6 +117,27 @@ class WC_Discounts { if ( ! is_a( $coupon, 'WC_Coupon' ) ) { return false; } + + + /* @todo add this logic here somewhere + if ( null === $this->get_limit_usage_to_x_items() ) { + $limit_usage_qty = $cart_item_qty; + } else { + $limit_usage_qty = min( $this->get_limit_usage_to_x_items(), $cart_item_qty ); + + $this->set_limit_usage_to_x_items( max( 0, ( $this->get_limit_usage_to_x_items() - $limit_usage_qty ) ) ); + } + if ( $single ) { + $discount = ( $discount * $limit_usage_qty ) / $cart_item_qty; + } else { + $discount = ( $discount / $cart_item_qty ) * $limit_usage_qty; + }*/ + + // @todo how can we support the old woocommerce_coupon_get_discount_amount filter? + + + + switch ( $coupon->get_discount_type() ) { case 'percent' : $this->apply_percentage_discount( $coupon->get_amount() ); From 55100562b3073aacc29ee96a50dd86d48754a1bd Mon Sep 17 00:00:00 2001 From: jaydeeprami Date: Wed, 19 Jul 2017 08:14:41 +0530 Subject: [PATCH 051/224] Fix typo in '@oaram' to '@param' --- includes/data-stores/class-wc-customer-data-store.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/includes/data-stores/class-wc-customer-data-store.php b/includes/data-stores/class-wc-customer-data-store.php index d6596487d5c..1a0f800161a 100644 --- a/includes/data-stores/class-wc-customer-data-store.php +++ b/includes/data-stores/class-wc-customer-data-store.php @@ -369,8 +369,9 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat /** * Search customers and return customer IDs. * - * @param string $term - * @oaram int|string $limit @since 3.0.7 + * @param string $term + * @param int|string $limit @since 3.0.7 + * * @return array */ public function search_customers( $term, $limit = '' ) { From 8d277a263be3d3051368b3d4f4f2edf5d476f279 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 10:16:50 +0100 Subject: [PATCH 052/224] Sort by order + zone ID as a fallback in shipping zones Fixes #16170 --- assets/js/admin/wc-shipping-zones.js | 8 +++++--- assets/js/admin/wc-shipping-zones.min.js | 2 +- .../data-stores/class-wc-shipping-zone-data-store.php | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/assets/js/admin/wc-shipping-zones.js b/assets/js/admin/wc-shipping-zones.js index c4eaeea9e85..79026f2d964 100644 --- a/assets/js/admin/wc-shipping-zones.js +++ b/assets/js/admin/wc-shipping-zones.js @@ -102,9 +102,11 @@ if ( _.size( zones ) ) { // Sort zones - zones = _.sortBy( zones, function( zone ) { - return parseInt( zone.zone_order, 10 ); - } ); + zones = _( zones ) + .chain() + .sortBy( function ( zone ) { return parseInt( zone.zone_id, 10 ); } ) + .sortBy( function ( zone ) { return parseInt( zone.zone_order, 10 ); } ) + .value(); // Populate $tbody with the current zones $.each( zones, function( id, rowData ) { diff --git a/assets/js/admin/wc-shipping-zones.min.js b/assets/js/admin/wc-shipping-zones.min.js index e0d923138b7..66498e7fc0e 100644 --- a/assets/js/admin/wc-shipping-zones.min.js +++ b/assets/js/admin/wc-shipping-zones.min.js @@ -1 +1 @@ -!function(e,n,i,o){e(function(){var t=e(".wc-shipping-zones"),s=e(".wc-shipping-zone-rows"),d=e(".wc-shipping-zone-save"),a=i.template("wc-shipping-zone-row"),r=i.template("wc-shipping-zone-row-blank"),l=Backbone.Model.extend({changes:{},logChanges:function(e){var n=this.changes||{};_.each(e,function(e,i){n[i]=_.extend(n[i]||{zone_id:i},e)}),this.changes=n,this.trigger("change:zones")},discardChanges:function(e){var n=this.changes||{},i=null,o=_.indexBy(this.get("zones"),"zone_id");n[e]&&n[e].zone_order!==undefined&&(i=n[e].zone_order),delete n[e],null!==i&&o[e]&&o[e].zone_order!==i&&(n[e]=_.extend(n[e]||{},{zone_id:e,zone_order:i})),this.changes=n,0===_.size(this.changes)&&p.clearUnloadConfirmation()},save:function(){_.size(this.changes)?e.post(o+(o.indexOf("?")>0?"&":"?")+"action=woocommerce_shipping_zones_save_changes",{wc_shipping_zones_nonce:n.wc_shipping_zones_nonce,changes:this.changes},this.onSaveResponse,"json"):h.trigger("saved:zones")},onSaveResponse:function(e,i){"success"===i&&(e.success?(h.set("zones",e.data.zones),h.trigger("change:zones"),h.changes={},h.trigger("saved:zones")):window.alert(n.strings.save_failed))}}),c=Backbone.View.extend({rowTemplate:a,initialize:function(){this.listenTo(this.model,"change:zones",this.setUnloadConfirmation),this.listenTo(this.model,"saved:zones",this.clearUnloadConfirmation),this.listenTo(this.model,"saved:zones",this.render),s.on("change",{view:this},this.updateModelOnChange),s.on("sortupdate",{view:this},this.updateModelOnSort),e(window).on("beforeunload",{view:this},this.unloadConfirmation),e(document.body).on("click",".wc-shipping-zone-add",{view:this},this.onAddNewRow)},block:function(){e(this.el).block({message:null,overlayCSS:{background:"#fff",opacity:.6}})},unblock:function(){e(this.el).unblock()},render:function(){var n=_.indexBy(this.model.get("zones"),"zone_id"),i=this;i.$el.empty(),i.unblock(),_.size(n)?(n=_.sortBy(n,function(e){return parseInt(e.zone_order,10)}),e.each(n,function(e,n){i.renderRow(n)})):i.$el.append(r),i.initRows()},renderRow:function(e){var n=this;n.$el.append(n.rowTemplate(e)),n.initRow(e)},initRow:function(e){var n=this,i=n.$el.find('tr[data-id="'+e.zone_id+'"]');n.renderShippingMethods(e.zone_id,e.shipping_methods),i.find(".wc-shipping-zone-delete").on("click",{view:this},this.onDeleteRow)},initRows:function(){0==e("tbody.wc-shipping-zone-rows tr").length%2?t.find("tbody.wc-shipping-zone-rows").next("tbody").find("tr").addClass("odd"):t.find("tbody.wc-shipping-zone-rows").next("tbody").find("tr").removeClass("odd"),e("#tiptip_holder").removeAttr("style"),e("#tiptip_arrow").removeAttr("style"),e(".tips").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:50})},renderShippingMethods:function(i,o){var t=e('.wc-shipping-zones tr[data-id="'+i+'"]').find(".wc-shipping-zone-methods ul");t.find(".wc-shipping-zone-method").remove(),_.size(o)?(o=_.sortBy(o,function(e){return parseInt(e.method_order,10)}),_.each(o,function(e){var n="method_disabled";"yes"===e.enabled&&(n="method_enabled"),t.append('
  • '+e.title+"
  • ")})):t.append('
  • '+n.strings.no_shipping_methods_offered+"
  • ")},onDeleteRow:function(i){var o=i.data.view.model,t=_.indexBy(o.get("zones"),"zone_id"),s={},d=e(this).closest("tr").data("id");i.preventDefault(),window.confirm(n.strings.delete_confirmation_msg)&&t[d]&&(delete t[d],s[d]=_.extend(s[d]||{},{deleted:"deleted"}),o.set("zones",t),o.logChanges(s),i.data.view.block(),i.data.view.model.save())},setUnloadConfirmation:function(){this.needsUnloadConfirm=!0,d.prop("disabled",!1)},clearUnloadConfirmation:function(){this.needsUnloadConfirm=!1,d.prop("disabled",!0)},unloadConfirmation:function(e){if(e.data.view.needsUnloadConfirm)return e.returnValue=n.strings.unload_confirmation_msg,window.event.returnValue=n.strings.unload_confirmation_msg,n.strings.unload_confirmation_msg},updateModelOnChange:function(n){var i=n.data.view.model,o=e(n.target),t=o.closest("tr").data("id"),s=o.data("attribute"),d=o.val(),a=_.indexBy(i.get("zones"),"zone_id"),r={};a[t]&&a[t][s]===d||(r[t]={},r[t][s]=d),i.logChanges(r)},updateModelOnSort:function(n){var i=n.data.view.model,o=_.indexBy(i.get("zones"),"zone_id"),t=e("tbody.wc-shipping-zone-rows tr"),s={};_.each(t,function(n){var i=e(n).data("id"),t=null,d=parseInt(e(n).index(),10);o[i]&&(t=parseInt(o[i].zone_order,10)),t!==d&&(s[i]=_.extend(s[i]||{},{zone_order:d}))}),_.size(s)&&(i.logChanges(s),n.data.view.block(),n.data.view.model.save())}}),h=new l({zones:n.zones}),p=new c({model:h,el:s});p.render(),s.sortable({items:"tr",cursor:"move",axis:"y",handle:"td.wc-shipping-zone-sort",scrollSensitivity:40})})}(jQuery,shippingZonesLocalizeScript,wp,ajaxurl); \ No newline at end of file +!function(e,n,i,o){e(function(){var t=e(".wc-shipping-zones"),s=e(".wc-shipping-zone-rows"),d=e(".wc-shipping-zone-save"),a=i.template("wc-shipping-zone-row"),r=i.template("wc-shipping-zone-row-blank"),l=Backbone.Model.extend({changes:{},logChanges:function(e){var n=this.changes||{};_.each(e,function(e,i){n[i]=_.extend(n[i]||{zone_id:i},e)}),this.changes=n,this.trigger("change:zones")},discardChanges:function(e){var n=this.changes||{},i=null,o=_.indexBy(this.get("zones"),"zone_id");n[e]&&n[e].zone_order!==undefined&&(i=n[e].zone_order),delete n[e],null!==i&&o[e]&&o[e].zone_order!==i&&(n[e]=_.extend(n[e]||{},{zone_id:e,zone_order:i})),this.changes=n,0===_.size(this.changes)&&p.clearUnloadConfirmation()},save:function(){_.size(this.changes)?e.post(o+(o.indexOf("?")>0?"&":"?")+"action=woocommerce_shipping_zones_save_changes",{wc_shipping_zones_nonce:n.wc_shipping_zones_nonce,changes:this.changes},this.onSaveResponse,"json"):h.trigger("saved:zones")},onSaveResponse:function(e,i){"success"===i&&(e.success?(h.set("zones",e.data.zones),h.trigger("change:zones"),h.changes={},h.trigger("saved:zones")):window.alert(n.strings.save_failed))}}),c=Backbone.View.extend({rowTemplate:a,initialize:function(){this.listenTo(this.model,"change:zones",this.setUnloadConfirmation),this.listenTo(this.model,"saved:zones",this.clearUnloadConfirmation),this.listenTo(this.model,"saved:zones",this.render),s.on("change",{view:this},this.updateModelOnChange),s.on("sortupdate",{view:this},this.updateModelOnSort),e(window).on("beforeunload",{view:this},this.unloadConfirmation),e(document.body).on("click",".wc-shipping-zone-add",{view:this},this.onAddNewRow)},block:function(){e(this.el).block({message:null,overlayCSS:{background:"#fff",opacity:.6}})},unblock:function(){e(this.el).unblock()},render:function(){var n=_.indexBy(this.model.get("zones"),"zone_id"),i=this;i.$el.empty(),i.unblock(),_.size(n)?(n=_(n).chain().sortBy(function(e){return-1*parseInt(e.zone_id,10)}).sortBy(function(e){return parseInt(e.zone_order,10)}).value(),e.each(n,function(e,n){i.renderRow(n)})):i.$el.append(r),i.initRows()},renderRow:function(e){var n=this;n.$el.append(n.rowTemplate(e)),n.initRow(e)},initRow:function(e){var n=this,i=n.$el.find('tr[data-id="'+e.zone_id+'"]');n.renderShippingMethods(e.zone_id,e.shipping_methods),i.find(".wc-shipping-zone-delete").on("click",{view:this},this.onDeleteRow)},initRows:function(){0==e("tbody.wc-shipping-zone-rows tr").length%2?t.find("tbody.wc-shipping-zone-rows").next("tbody").find("tr").addClass("odd"):t.find("tbody.wc-shipping-zone-rows").next("tbody").find("tr").removeClass("odd"),e("#tiptip_holder").removeAttr("style"),e("#tiptip_arrow").removeAttr("style"),e(".tips").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:50})},renderShippingMethods:function(i,o){var t=e('.wc-shipping-zones tr[data-id="'+i+'"]').find(".wc-shipping-zone-methods ul");t.find(".wc-shipping-zone-method").remove(),_.size(o)?(o=_.sortBy(o,function(e){return parseInt(e.method_order,10)}),_.each(o,function(e){var n="method_disabled";"yes"===e.enabled&&(n="method_enabled"),t.append('
  • '+e.title+"
  • ")})):t.append('
  • '+n.strings.no_shipping_methods_offered+"
  • ")},onDeleteRow:function(i){var o=i.data.view.model,t=_.indexBy(o.get("zones"),"zone_id"),s={},d=e(this).closest("tr").data("id");i.preventDefault(),window.confirm(n.strings.delete_confirmation_msg)&&t[d]&&(delete t[d],s[d]=_.extend(s[d]||{},{deleted:"deleted"}),o.set("zones",t),o.logChanges(s),i.data.view.block(),i.data.view.model.save())},setUnloadConfirmation:function(){this.needsUnloadConfirm=!0,d.prop("disabled",!1)},clearUnloadConfirmation:function(){this.needsUnloadConfirm=!1,d.prop("disabled",!0)},unloadConfirmation:function(e){if(e.data.view.needsUnloadConfirm)return e.returnValue=n.strings.unload_confirmation_msg,window.event.returnValue=n.strings.unload_confirmation_msg,n.strings.unload_confirmation_msg},updateModelOnChange:function(n){var i=n.data.view.model,o=e(n.target),t=o.closest("tr").data("id"),s=o.data("attribute"),d=o.val(),a=_.indexBy(i.get("zones"),"zone_id"),r={};a[t]&&a[t][s]===d||(r[t]={},r[t][s]=d),i.logChanges(r)},updateModelOnSort:function(n){var i=n.data.view.model,o=_.indexBy(i.get("zones"),"zone_id"),t=e("tbody.wc-shipping-zone-rows tr"),s={};_.each(t,function(n){var i=e(n).data("id"),t=null,d=parseInt(e(n).index(),10);o[i]&&(t=parseInt(o[i].zone_order,10)),t!==d&&(s[i]=_.extend(s[i]||{},{zone_order:d}))}),_.size(s)&&(i.logChanges(s),n.data.view.block(),n.data.view.model.save())}}),h=new l({zones:n.zones}),p=new c({model:h,el:s});p.render(),s.sortable({items:"tr",cursor:"move",axis:"y",handle:"td.wc-shipping-zone-sort",scrollSensitivity:40})})}(jQuery,shippingZonesLocalizeScript,wp,ajaxurl); \ No newline at end of file diff --git a/includes/data-stores/class-wc-shipping-zone-data-store.php b/includes/data-stores/class-wc-shipping-zone-data-store.php index b1cff12151e..f9ff319439f 100644 --- a/includes/data-stores/class-wc-shipping-zone-data-store.php +++ b/includes/data-stores/class-wc-shipping-zone-data-store.php @@ -220,7 +220,7 @@ class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shippin SELECT zones.zone_id FROM {$wpdb->prefix}woocommerce_shipping_zones as zones LEFT OUTER JOIN {$wpdb->prefix}woocommerce_shipping_zone_locations as locations ON zones.zone_id = locations.zone_id AND location_type != 'postcode' WHERE " . implode( ' ', $criteria ) . " - ORDER BY zone_order ASC LIMIT 1 + ORDER BY zone_order ASC, zone_id ASC LIMIT 1 " ); } @@ -232,7 +232,7 @@ class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shippin */ public function get_zones() { global $wpdb; - return $wpdb->get_results( "SELECT zone_id, zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones order by zone_order ASC;" ); + return $wpdb->get_results( "SELECT zone_id, zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones order by zone_order ASC, zone_id ASC;" ); } From dd7fe5f1589aa154aaa851ccf5b8b2ed432e5cdb Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 12:26:01 +0100 Subject: [PATCH 053/224] More tests and precision fixes --- includes/class-wc-discounts.php | 205 ++++++++++------- tests/unit-tests/discounts/discounts.php | 268 +++++++++++++++++++---- 2 files changed, 359 insertions(+), 114 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 89d2693ef75..6744aace191 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -29,12 +29,36 @@ class WC_Discounts { */ protected $discounts = array(); + /** + * Precision so we can work in cents. + * + * @var int + */ + protected $precision = 1; + + /** + * Constructor. + */ + public function __construct() { + $this->precision = pow( 10, wc_get_price_decimals() ); + } + + /** + * Remove precision from a price. + * + * @param int $value + * @return float + */ + protected function remove_precision( $value ) { + return wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); + } + /** * Reset items and discounts to 0. */ protected function reset() { $this->items = array(); - $this->discounts = array( 'cart' => 0 ); + $this->discounts = array(); } /** @@ -47,6 +71,48 @@ class WC_Discounts { return $this->items; } + /** + * Get discount by key without precision. + * + * @since 3.2.0 + * @return array + */ + public function get_discount( $key ) { + return isset( $this->discounts[ $key ] ) ? $this->remove_precision( $this->discounts[ $key ] ) : 0; + } + + /** + * Get all discount totals without precision. + * + * @since 3.2.0 + * @return array + */ + public function get_discounts() { + return array_map( array( $this, 'remove_precision' ), $this->discounts ); + } + + /** + * Get discounted price of an item without precision. + * + * @since 3.2.0 + * @param object $item + * @return float + */ + public function get_discounted_price( $item ) { + return $this->remove_precision( $this->get_discounted_price_in_cents( $item ) ); + } + + /** + * Get discounted price of an item to precision (in cents). + * + * @since 3.2.0 + * @param object $item + * @return float + */ + public function get_discounted_price_in_cents( $item ) { + return $item->price - $this->discounts[ $item->key ]; + } + /** * Set cart/order items which will be discounted. * @@ -59,7 +125,7 @@ class WC_Discounts { if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { foreach ( $raw_items as $raw_item ) { $item = (object) array( - 'price' => 0, // Line price without discounts. + 'price' => 0, // Line price without discounts, in cents. 'quantity' => 0, // Line qty. 'product' => false, ); @@ -72,12 +138,12 @@ class WC_Discounts { } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { $item->key = $raw_item->get_id(); $item->quantity = $raw_item->get_quantity(); + $item->price = $raw_item->get_subtotal() * $this->precision; $item->product = $raw_item->get_product(); } else { $item->key = $raw_item['key']; - // @todo remove when we implement WC_Cart_Item. This is the old cart item schema. $item->quantity = $raw_item['quantity']; - $item->price = $raw_item['data']->get_price() * $raw_item['quantity']; + $item->price = $raw_item['data']->get_price() * $this->precision * $raw_item['quantity']; $item->product = $raw_item['data']; } $this->items[ $item->key ] = $item; @@ -86,26 +152,6 @@ class WC_Discounts { } } - /** - * Get all discount totals. - * - * @since 3.2.0 - * @return array - */ - public function get_discounts() { - return $this->discounts; - } - - /** - * Get discount by key. - * - * @since 3.2.0 - * @return array - */ - public function get_discount( $key ) { - return isset( $this->discounts[ $key ] ) ? $this->discounts[ $key ] : 0; - } - /** * Apply a discount to all items using a coupon. * @@ -143,51 +189,29 @@ class WC_Discounts { $this->apply_percentage_discount( $coupon->get_amount() ); break; case 'fixed_product' : - $this->apply_fixed_product_discount( $coupon->get_amount() ); + $this->apply_fixed_product_discount( $coupon->get_amount() * $this->precision ); break; case 'fixed_cart' : - $this->apply_fixed_cart_discount( $coupon->get_amount() ); + $this->apply_fixed_cart_discount( $coupon->get_amount() * $this->precision ); break; } } - /** - * Get discounted price of an item. - * - * @since 3.2.0 - * @param object $item - * @return float - */ - public function get_discounted_price( $item ) { - return $item->price - $this->discounts[ $item->key ]; - } - /** * Apply a discount amount to an item and ensure it does not go negative. * * @since 3.2.0 * @param object $item - * @param float $discount - * @return float + * @param int $discount + * @return int Amount discounted. */ - protected function apply_discount_to_item( &$item, $discount ) { - $discounted_price = $this->get_discounted_price( $item ); + protected function add_item_discount( &$item, $discount ) { + $discounted_price = $this->get_discounted_price_in_cents( $item ); $discount = $discount > $discounted_price ? $discounted_price : $discount; $this->discounts[ $item->key ] = $this->discounts[ $item->key ] + $discount; return $discount; } - /** - * Apply a discount amount to the cart. - * - * @since 3.2.0 - * @param object $item - * @param float $discount - */ - protected function apply_discount_to_cart( $discount ) { - $this->discounts['cart'] += $discount; - } - /** * Apply percent discount to items. * @@ -196,7 +220,7 @@ class WC_Discounts { */ protected function apply_percentage_discount( $amount ) { foreach ( $this->items as $item ) { - $this->apply_discount_to_item( $item, (float) $amount * ( $this->get_discounted_price( $item ) / 100 ) ); + $this->add_item_discount( $item, $amount * ( $this->get_discounted_price_in_cents( $item ) / 100 ) ); } } @@ -204,39 +228,72 @@ class WC_Discounts { * Apply fixed product discount to items. * * @since 3.2.0 - * @param float $amount + * @param int $amount */ protected function apply_fixed_product_discount( $discount ) { foreach ( $this->items as $item ) { - $this->apply_discount_to_item( $item, $discount * $item->quantity ); + $this->add_item_discount( $item, $discount * $item->quantity ); } } - /*protected function filter_taxable_items( $item ) { - return $item->is_taxable; - }*/ - /** - * Apply fixed cart discount to items. Cart discounts will be stored and - * displayed separately to items, and taxes will be apportioned. + * Filter out all products which have been fully discounted to 0. + * Used as array_filter callback. * - * @since 3.2.0 - * @param float $cart_discount + * @param object $item + * @return bool + */ + protected function filter_products_with_price( $item ) { + return $this->get_discounted_price_in_cents( $item ) > 0; + } + + /** + * Apply fixed cart discount to items. + * + * @since 3.2.0 + * @param int $cart_discount */ protected function apply_fixed_cart_discount( $cart_discount ) { - // @todo Fixed cart discounts will be apportioned based on tax class. - /*$tax_class_counts = array_count_values( wp_list_pluck( array_filter( $this->items, array( $this, 'filter_taxable_items' ) ), 'tax_class' ) ); - $item_count = array_sum( wp_list_pluck( $this->items, 'quantity' ) ); - $cart_discount_taxes = array(); + $items_to_discount = array_filter( $this->items, array( $this, 'filter_products_with_price' ) ); - foreach ( $tax_class_counts as $tax_class => $tax_class_count ) { - $proportion = $tax_class_count / $item_count; - $cart_discount_proportion = $cart_discount * $proportion; - $tax_rates = WC_Tax::get_rates( $tax_class ); - $cart_discount_taxes[ $tax_class ] = WC_Tax::calc_tax( $cart_discount_proportion, $tax_rates ); + if ( ! $item_count = array_sum( wp_list_pluck( $items_to_discount, 'quantity' ) ) ) { + return; } - var_dump($cart_discount_taxes);*/ - $this->apply_discount_to_cart( $cart_discount ); + $per_item_discount = floor( $cart_discount / $item_count ); // round it down to the nearest cent number. + $amount_discounted = 0; + + if ( $per_item_discount > 0 ) { + foreach ( $items_to_discount as $item ) { + $amount_discounted += $this->add_item_discount( $item, $per_item_discount * $item->quantity ); + } + + /** + * If there is still discount remaining, repeat the process. + */ + if ( $amount_discounted > 0 && $amount_discounted < $cart_discount ) { + $this->apply_fixed_cart_discount( $cart_discount - $amount_discounted ); + } + return; + } + + /** + * Deal with remaining fractional discounts by splitting it over items + * until the amount is expired, discounting 1 cent at a time. + */ + if ( $cart_discount > 0 ) { + foreach ( $items_to_discount as $item ) { + for ( $i = 0; $i < $item->quantity; $i ++ ) { + $amount_discounted += $this->add_item_discount( $item, 1 ); + + if ( $amount_discounted >= $cart_discount ) { + break 2; + } + } + if ( $amount_discounted >= $cart_discount ) { + break; + } + } + } } } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index e0ccc69e86a..8de3e11e887 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -49,12 +49,48 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { } /** - * Test applying a coupon to a set of items. + * Test applying a coupon (make sure it changes prices). */ public function test_apply_coupon() { $discounts = new WC_Discounts(); // Create dummy content. + $product = WC_Helper_Product::create_simple_product(); + $product->set_tax_status( 'taxable' ); + $product->save(); + WC()->cart->empty_cart(); + WC()->cart->add_to_cart( $product->get_id(), 1 ); + $coupon = new WC_Coupon; + $coupon->set_code( 'test' ); + $coupon->set_amount( 10 ); + + // Apply a percent discount. + $coupon->set_discount_type( 'percent' ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( 9, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); + + // Apply a fixed cart coupon. + $coupon->set_discount_type( 'fixed_cart' ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); + + // Apply a fixed product coupon. + $coupon->set_discount_type( 'fixed_product' ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); + + // Cleanup. + WC()->cart->empty_cart(); + $product->delete( true ); + } + + /** + * Test various discount calculations are working correctly and produding expected results. + */ + public function test_calculations() { $tax_rate = array( 'tax_rate_country' => '', 'tax_rate_state' => '', @@ -68,51 +104,203 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { ); $tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate ); update_option( 'woocommerce_calc_taxes', 'yes' ); - $product = WC_Helper_Product::create_simple_product(); - $product->set_tax_status( 'taxable' ); - $product->save(); - WC()->cart->empty_cart(); - WC()->cart->add_to_cart( $product->get_id(), 1 ); - $coupon = new WC_Coupon; - $coupon->set_code( 'test' ); - $coupon->set_discount_type( 'percent' ); - $coupon->set_amount( 20 ); - // Apply a percent discount. - $coupon->set_discount_type( 'percent' ); - $discounts->set_items( WC()->cart->get_cart() ); - $discounts->apply_coupon( $coupon ); - $this->assertEquals( 8, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); + $tests = array( + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 1, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'percent', + 'amount' => '20', + ) + ), + 'expected_total_discount' => 2, + ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 2, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'fixed_cart', + 'amount' => '10', + ) + ), + 'expected_total_discount' => 10, + ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'fixed_cart', + 'amount' => '10', + ) + ), + 'expected_total_discount' => 10, + ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'fixed_cart', + 'amount' => '10', + ) + ), + 'expected_total_discount' => 10, + ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 2, + ), + array( + 'price' => 10, + 'qty' => 3, + ), + array( + 'price' => 10, + 'qty' => 2, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'fixed_cart', + 'amount' => '10', + ) + ), + 'expected_total_discount' => 10, + ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ), + array( + 'price' => 10, + 'qty' => 1, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'fixed_cart', + 'amount' => '10', + ) + ), + 'expected_total_discount' => 10, + ) + ); - // Apply a fixed cart coupon. - $coupon->set_discount_type( 'fixed_cart' ); - $discounts->set_items( WC()->cart->get_cart() ); - $discounts->apply_coupon( $coupon ); - $this->assertEquals( 20, $discounts->get_discount( 'cart' ) ); + foreach ( $tests as $test_index => $test ) { + $discounts = new WC_Discounts(); + $products = array(); - // Apply a fixed cart coupon. - $coupon->set_discount_type( 'fixed_cart' ); - WC()->cart->empty_cart(); - WC()->cart->add_to_cart( $product->get_id(), 4 ); - $discounts->set_items( WC()->cart->get_cart() ); - $discounts->apply_coupon( $coupon ); - $this->assertEquals( 20, $discounts->get_discount( 'cart' ) ); + foreach ( $test['cart'] as $item ) { + $product = WC_Helper_Product::create_simple_product(); + $product->set_regular_price( $item['price'] ); + $product->set_tax_status( 'taxable' ); + $product->save(); + WC()->cart->add_to_cart( $product->get_id(), $item['qty'] ); + $products[] = $product; + } - $discounts->apply_coupon( $coupon ); - $this->assertEquals( 40, $discounts->get_discount( 'cart' ) ); + $discounts->set_items( WC()->cart->get_cart() ); - // Apply a fixed product coupon. - $coupon->set_discount_type( 'fixed_product' ); - $coupon->set_amount( 1 ); - WC()->cart->empty_cart(); - WC()->cart->add_to_cart( $product->get_id(), 4 ); - $discounts->set_items( WC()->cart->get_cart() ); - $discounts->apply_coupon( $coupon ); - $this->assertEquals( 36, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); + foreach ( $test['coupons'] as $coupon_props ) { + $coupon = new WC_Coupon; + $coupon->set_props( $coupon_props ); + $discounts->apply_coupon( $coupon ); + } + + $this->assertEquals( $test['expected_total_discount'], array_sum( $discounts->get_discounts() ), 'Test case ' . $test_index . ' failed (' . print_r( $test, true ) . ' - ' . print_r( $discounts->get_discounts(), true ) . ')' ); + + // Clean. + WC()->cart->empty_cart(); + + foreach ( $products as $product ) { + $product->delete( true ); + } + } - // Cleanup. - WC()->cart->empty_cart(); - $product->delete( true ); WC_Tax::_delete_tax_rate( $tax_rate_id ); update_option( 'woocommerce_calc_taxes', 'no' ); } From baa7efcc3035e777714b40180fd1775f72d5d9da Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 12:27:50 +0100 Subject: [PATCH 054/224] Another test --- tests/unit-tests/discounts/discounts.php | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 8de3e11e887..33a4c9a403f 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -267,7 +267,32 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { ) ), 'expected_total_discount' => 10, - ) + ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 1, + 'qty' => 1, + ), + array( + 'price' => 1, + 'qty' => 1, + ), + array( + 'price' => 1, + 'qty' => 1, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'fixed_cart', + 'amount' => '1', + ) + ), + 'expected_total_discount' => 1, + ), ); foreach ( $tests as $test_index => $test ) { From 3c12a06c46481b8f4d46961f216278c2a45856af Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 13:49:22 +0100 Subject: [PATCH 055/224] Limit to x --- includes/class-wc-discounts.php | 133 +++++++++++++++-------- tests/unit-tests/discounts/discounts.php | 40 +++++++ 2 files changed, 126 insertions(+), 47 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 6744aace191..c47f45dd76e 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -54,11 +54,19 @@ class WC_Discounts { } /** - * Reset items and discounts to 0. + * Sort by price. + * + * @param array $a + * @param array $b + * @return int */ - protected function reset() { - $this->items = array(); - $this->discounts = array(); + private function sort_by_price( $a, $b ) { + $price_1 = $a->price * $a->quantity; + $price_2 = $b->price * $b->quantity;; + if ( $price_1 === $price_2 ) { + return 0; + } + return ( $price_1 < $price_2 ) ? 1 : -1; } /** @@ -120,7 +128,8 @@ class WC_Discounts { * @param array $raw_items List of raw cart or order items. */ public function set_items( $raw_items ) { - $this->reset(); + $this->items = array(); + $this->discounts = array(); if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { foreach ( $raw_items as $raw_item ) { @@ -149,12 +158,66 @@ class WC_Discounts { $this->items[ $item->key ] = $item; $this->discounts[ $item->key ] = 0; } + uasort( $this->items, array( $this, 'sort_by_price' ) ); } } + /** + * Filter out all products which have been fully discounted to 0. + * Used as array_filter callback. + * + * @param object $item + * @return bool + */ + protected function filter_products_with_price( $item ) { + return $this->get_discounted_price_in_cents( $item ) > 0; + } + + /** + * Get items which the coupon should be applied to. + * + * @param object $coupon + * @return array + */ + protected function get_items_to_apply_coupon( $coupon ) { + $items_to_apply = array(); + $limit_usage_qty = 0; + $applied_count = 0; + + if ( null !== $coupon->get_limit_usage_to_x_items() ) { + $limit_usage_qty = $coupon->get_limit_usage_to_x_items(); + } + + foreach ( $this->items as $item ) { + if ( 0 === $this->get_discounted_price_in_cents( $item ) ) { + continue; + } + if ( ! $coupon->is_valid_for_product( $item->product ) && ! $coupon->is_valid_for_cart() ) { // @todo is this enough? + continue; + } + if ( $limit_usage_qty && $applied_count > $limit_usage_qty ) { + break; + } + if ( $limit_usage_qty && $item->quantity > ( $limit_usage_qty - $applied_count ) ) { + $limit_to_qty = absint( $limit_usage_qty - $applied_count ); + $item->price = ( $item->price / $item->quantity ) * $limit_to_qty; + $item->quantity = $limit_to_qty; // Lower the qty so the discount is applied less. + } + if ( 0 >= $item->quantity ) { + continue; + } + $items_to_apply[] = $item; + $applied_count += $item->quantity; + } + return $items_to_apply; + } + /** * Apply a discount to all items using a coupon. * + * @todo Coupon class has lots of WC()->cart calls and needs decoupling. This makes 'is valid' hard to use here. + * @todo is_valid_for_product accepts values - how can we deal with that? + * * @since 3.2.0 * @param WC_Coupon $coupon * @return bool True if applied. @@ -164,35 +227,19 @@ class WC_Discounts { return false; } - - /* @todo add this logic here somewhere - if ( null === $this->get_limit_usage_to_x_items() ) { - $limit_usage_qty = $cart_item_qty; - } else { - $limit_usage_qty = min( $this->get_limit_usage_to_x_items(), $cart_item_qty ); - - $this->set_limit_usage_to_x_items( max( 0, ( $this->get_limit_usage_to_x_items() - $limit_usage_qty ) ) ); - } - if ( $single ) { - $discount = ( $discount * $limit_usage_qty ) / $cart_item_qty; - } else { - $discount = ( $discount / $cart_item_qty ) * $limit_usage_qty; - }*/ - // @todo how can we support the old woocommerce_coupon_get_discount_amount filter? - - - + // @todo is valid for product - filter items here and pass to function? + $items_to_apply = $this->get_items_to_apply_coupon( $coupon ); switch ( $coupon->get_discount_type() ) { case 'percent' : - $this->apply_percentage_discount( $coupon->get_amount() ); + $this->apply_percentage_discount( $items_to_apply, $coupon->get_amount() ); break; case 'fixed_product' : - $this->apply_fixed_product_discount( $coupon->get_amount() * $this->precision ); + $this->apply_fixed_product_discount( $items_to_apply, $coupon->get_amount() * $this->precision ); break; case 'fixed_cart' : - $this->apply_fixed_cart_discount( $coupon->get_amount() * $this->precision ); + $this->apply_fixed_cart_discount( $items_to_apply, $coupon->get_amount() * $this->precision ); break; } } @@ -216,10 +263,11 @@ class WC_Discounts { * Apply percent discount to items. * * @since 3.2.0 + * @param array $items_to_apply Array of items to apply the coupon to. * @param float $amount */ - protected function apply_percentage_discount( $amount ) { - foreach ( $this->items as $item ) { + protected function apply_percentage_discount( $items_to_apply, $amount ) { + foreach ( $items_to_apply as $item ) { $this->add_item_discount( $item, $amount * ( $this->get_discounted_price_in_cents( $item ) / 100 ) ); } } @@ -228,35 +276,26 @@ class WC_Discounts { * Apply fixed product discount to items. * * @since 3.2.0 + * @param array $items_to_apply Array of items to apply the coupon to. * @param int $amount */ - protected function apply_fixed_product_discount( $discount ) { - foreach ( $this->items as $item ) { + protected function apply_fixed_product_discount( $items_to_apply, $discount ) { + foreach ( $items_to_apply as $item ) { $this->add_item_discount( $item, $discount * $item->quantity ); } } - /** - * Filter out all products which have been fully discounted to 0. - * Used as array_filter callback. - * - * @param object $item - * @return bool - */ - protected function filter_products_with_price( $item ) { - return $this->get_discounted_price_in_cents( $item ) > 0; - } - /** * Apply fixed cart discount to items. * * @since 3.2.0 + * @param array $items_to_apply Array of items to apply the coupon to. * @param int $cart_discount */ - protected function apply_fixed_cart_discount( $cart_discount ) { - $items_to_discount = array_filter( $this->items, array( $this, 'filter_products_with_price' ) ); + protected function apply_fixed_cart_discount( $items_to_apply, $cart_discount ) { + $items_to_apply = array_filter( $items_to_apply, array( $this, 'filter_products_with_price' ) ); - if ( ! $item_count = array_sum( wp_list_pluck( $items_to_discount, 'quantity' ) ) ) { + if ( ! $item_count = array_sum( wp_list_pluck( $items_to_apply, 'quantity' ) ) ) { return; } @@ -264,7 +303,7 @@ class WC_Discounts { $amount_discounted = 0; if ( $per_item_discount > 0 ) { - foreach ( $items_to_discount as $item ) { + foreach ( $items_to_apply as $item ) { $amount_discounted += $this->add_item_discount( $item, $per_item_discount * $item->quantity ); } @@ -272,7 +311,7 @@ class WC_Discounts { * If there is still discount remaining, repeat the process. */ if ( $amount_discounted > 0 && $amount_discounted < $cart_discount ) { - $this->apply_fixed_cart_discount( $cart_discount - $amount_discounted ); + $this->apply_fixed_cart_discount( $items_to_apply, $cart_discount - $amount_discounted ); } return; } @@ -282,7 +321,7 @@ class WC_Discounts { * until the amount is expired, discounting 1 cent at a time. */ if ( $cart_discount > 0 ) { - foreach ( $items_to_discount as $item ) { + foreach ( $items_to_apply as $item ) { for ( $i = 0; $i < $item->quantity; $i ++ ) { $amount_discounted += $this->add_item_discount( $item, 1 ); diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 33a4c9a403f..f14cf09d478 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -293,6 +293,46 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { ), 'expected_total_discount' => 1, ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 2, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'percent', + 'amount' => '10', + 'limit_usage_to_x_items' => 1, + ) + ), + 'expected_total_discount' => 1, + ), + array( + 'prices_include_tax' => false, + 'cart' => array( + array( + 'price' => 10, + 'qty' => 2, + ), + array( + 'price' => 10, + 'qty' => 2, + ) + ), + 'coupons' => array( + array( + 'code' => 'test', + 'discount_type' => 'percent', + 'amount' => '10', + 'limit_usage_to_x_items' => 1, + ) + ), + 'expected_total_discount' => 1, + ), ); foreach ( $tests as $test_index => $test ) { From 28cb0909c84508b30dbae4c41ac82a4cddcb7d5e Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 14:25:00 +0100 Subject: [PATCH 056/224] Add placeholders array used in descriptions and for find and replace Fixes #16156 --- .../emails/class-wc-email-cancelled-order.php | 23 ++++--- ...lass-wc-email-customer-completed-order.php | 20 +++--- .../class-wc-email-customer-invoice.php | 19 +++--- .../class-wc-email-customer-new-account.php | 3 - .../emails/class-wc-email-customer-note.php | 20 +++--- .../class-wc-email-customer-on-hold-order.php | 30 ++++----- ...ass-wc-email-customer-processing-order.php | 17 ++--- ...class-wc-email-customer-refunded-order.php | 17 ++--- .../emails/class-wc-email-failed-order.php | 23 ++++--- includes/emails/class-wc-email-new-order.php | 23 ++++--- includes/emails/class-wc-email.php | 62 +++++++++++-------- 11 files changed, 135 insertions(+), 122 deletions(-) diff --git a/includes/emails/class-wc-email-cancelled-order.php b/includes/emails/class-wc-email-cancelled-order.php index 8dbac9969c3..1791a3fd402 100644 --- a/includes/emails/class-wc-email-cancelled-order.php +++ b/includes/emails/class-wc-email-cancelled-order.php @@ -23,11 +23,16 @@ class WC_Email_Cancelled_Order extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'cancelled_order'; - $this->title = __( 'Cancelled order', 'woocommerce' ); - $this->description = __( 'Cancelled order emails are sent to chosen recipient(s) when orders have been marked cancelled (if they were previously processing or on-hold).', 'woocommerce' ); - $this->template_html = 'emails/admin-cancelled-order.php'; - $this->template_plain = 'emails/plain/admin-cancelled-order.php'; + $this->id = 'cancelled_order'; + $this->title = __( 'Cancelled order', 'woocommerce' ); + $this->description = __( 'Cancelled order emails are sent to chosen recipient(s) when orders have been marked cancelled (if they were previously processing or on-hold).', 'woocommerce' ); + $this->template_html = 'emails/admin-cancelled-order.php'; + $this->template_plain = 'emails/plain/admin-cancelled-order.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers for this email add_action( 'woocommerce_order_status_processing_to_cancelled_notification', array( $this, 'trigger' ), 10, 2 ); @@ -72,11 +77,9 @@ class WC_Email_Cancelled_Order extends WC_Email { } if ( is_a( $order, 'WC_Order' ) ) { - $this->object = $order; - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = $order; + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/emails/class-wc-email-customer-completed-order.php b/includes/emails/class-wc-email-customer-completed-order.php index 6146606a669..c986ddbfa99 100644 --- a/includes/emails/class-wc-email-customer-completed-order.php +++ b/includes/emails/class-wc-email-customer-completed-order.php @@ -23,15 +23,17 @@ class WC_Email_Customer_Completed_Order extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'customer_completed_order'; $this->customer_email = true; - $this->title = __( 'Completed order', 'woocommerce' ); $this->description = __( 'Order complete emails are sent to customers when their orders are marked completed and usually indicate that their orders have been shipped.', 'woocommerce' ); - $this->template_html = 'emails/customer-completed-order.php'; $this->template_plain = 'emails/plain/customer-completed-order.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers for this email add_action( 'woocommerce_order_status_completed_notification', array( $this, 'trigger' ), 10, 2 ); @@ -52,14 +54,10 @@ class WC_Email_Customer_Completed_Order extends WC_Email { } if ( is_a( $order, 'WC_Order' ) ) { - $this->object = $order; - $this->recipient = $this->object->get_billing_email(); - - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = $order; + $this->recipient = $this->object->get_billing_email(); + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/emails/class-wc-email-customer-invoice.php b/includes/emails/class-wc-email-customer-invoice.php index d585621bde5..9cc37b15d46 100644 --- a/includes/emails/class-wc-email-customer-invoice.php +++ b/includes/emails/class-wc-email-customer-invoice.php @@ -37,14 +37,17 @@ class WC_Email_Customer_Invoice extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'customer_invoice'; $this->customer_email = true; - $this->title = __( 'Customer invoice', 'woocommerce' ); $this->description = __( 'Customer invoice emails can be sent to customers containing their order information and payment links.', 'woocommerce' ); $this->template_html = 'emails/customer-invoice.php'; $this->template_plain = 'emails/plain/customer-invoice.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Call parent constructor parent::__construct(); @@ -126,14 +129,10 @@ class WC_Email_Customer_Invoice extends WC_Email { } if ( is_a( $order, 'WC_Order' ) ) { - $this->object = $order; - $this->recipient = $this->object->get_billing_email(); - - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = $order; + $this->recipient = $this->object->get_billing_email(); + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! $this->get_recipient() ) { diff --git a/includes/emails/class-wc-email-customer-new-account.php b/includes/emails/class-wc-email-customer-new-account.php index c9a0ddc25e6..c161cb0305e 100644 --- a/includes/emails/class-wc-email-customer-new-account.php +++ b/includes/emails/class-wc-email-customer-new-account.php @@ -51,13 +51,10 @@ class WC_Email_Customer_New_Account extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'customer_new_account'; $this->customer_email = true; - $this->title = __( 'New account', 'woocommerce' ); $this->description = __( 'Customer "new account" emails are sent to the customer when a customer signs up via checkout or account pages.', 'woocommerce' ); - $this->template_html = 'emails/customer-new-account.php'; $this->template_plain = 'emails/plain/customer-new-account.php'; diff --git a/includes/emails/class-wc-email-customer-note.php b/includes/emails/class-wc-email-customer-note.php index 2c9bc939eec..7dda8552d21 100644 --- a/includes/emails/class-wc-email-customer-note.php +++ b/includes/emails/class-wc-email-customer-note.php @@ -30,15 +30,17 @@ class WC_Email_Customer_Note extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'customer_note'; $this->customer_email = true; - $this->title = __( 'Customer note', 'woocommerce' ); $this->description = __( 'Customer note emails are sent when you add a note to an order.', 'woocommerce' ); - $this->template_html = 'emails/customer-note.php'; $this->template_plain = 'emails/plain/customer-note.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers add_action( 'woocommerce_new_customer_note_notification', array( $this, 'trigger' ) ); @@ -84,14 +86,10 @@ class WC_Email_Customer_Note extends WC_Email { extract( $args ); if ( $order_id && ( $this->object = wc_get_order( $order_id ) ) ) { - $this->recipient = $this->object->get_billing_email(); - $this->customer_note = $customer_note; - - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->recipient = $this->object->get_billing_email(); + $this->customer_note = $customer_note; + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } else { return; } diff --git a/includes/emails/class-wc-email-customer-on-hold-order.php b/includes/emails/class-wc-email-customer-on-hold-order.php index 300e8516c00..772b8af7b69 100644 --- a/includes/emails/class-wc-email-customer-on-hold-order.php +++ b/includes/emails/class-wc-email-customer-on-hold-order.php @@ -23,13 +23,17 @@ class WC_Email_Customer_On_Hold_Order extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'customer_on_hold_order'; - $this->customer_email = true; - - $this->title = __( 'Order on-hold', 'woocommerce' ); - $this->description = __( 'This is an order notification sent to customers containing order details after an order is placed on-hold.', 'woocommerce' ); - $this->template_html = 'emails/customer-on-hold-order.php'; - $this->template_plain = 'emails/plain/customer-on-hold-order.php'; + $this->id = 'customer_on_hold_order'; + $this->customer_email = true; + $this->title = __( 'Order on-hold', 'woocommerce' ); + $this->description = __( 'This is an order notification sent to customers containing order details after an order is placed on-hold.', 'woocommerce' ); + $this->template_html = 'emails/customer-on-hold-order.php'; + $this->template_plain = 'emails/plain/customer-on-hold-order.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers for this email add_action( 'woocommerce_order_status_pending_to_on-hold_notification', array( $this, 'trigger' ), 10, 2 ); @@ -71,14 +75,10 @@ class WC_Email_Customer_On_Hold_Order extends WC_Email { } if ( is_a( $order, 'WC_Order' ) ) { - $this->object = $order; - $this->recipient = $this->object->get_billing_email(); - - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = $order; + $this->recipient = $this->object->get_billing_email(); + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/emails/class-wc-email-customer-processing-order.php b/includes/emails/class-wc-email-customer-processing-order.php index 85980d9f1f5..295e72d3c14 100644 --- a/includes/emails/class-wc-email-customer-processing-order.php +++ b/includes/emails/class-wc-email-customer-processing-order.php @@ -30,6 +30,11 @@ class WC_Email_Customer_Processing_Order extends WC_Email { $this->description = __( 'This is an order notification sent to customers containing order details after payment.', 'woocommerce' ); $this->template_html = 'emails/customer-processing-order.php'; $this->template_plain = 'emails/plain/customer-processing-order.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers for this email add_action( 'woocommerce_order_status_failed_to_processing_notification', array( $this, 'trigger' ), 10, 2 ); @@ -72,14 +77,10 @@ class WC_Email_Customer_Processing_Order extends WC_Email { } if ( is_a( $order, 'WC_Order' ) ) { - $this->object = $order; - $this->recipient = $this->object->get_billing_email(); - - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = $order; + $this->recipient = $this->object->get_billing_email(); + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/emails/class-wc-email-customer-refunded-order.php b/includes/emails/class-wc-email-customer-refunded-order.php index 8a17908efbf..c2852600cee 100644 --- a/includes/emails/class-wc-email-customer-refunded-order.php +++ b/includes/emails/class-wc-email-customer-refunded-order.php @@ -43,6 +43,11 @@ class WC_Email_Customer_Refunded_Order extends WC_Email { $this->description = __( 'Order refunded emails are sent to customers when their orders are refunded.', 'woocommerce' ); $this->template_html = 'emails/customer-refunded-order.php'; $this->template_plain = 'emails/plain/customer-refunded-order.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers for this email add_action( 'woocommerce_order_fully_refunded_notification', array( $this, 'trigger_full' ), 10, 2 ); @@ -148,14 +153,10 @@ class WC_Email_Customer_Refunded_Order extends WC_Email { $this->id = $this->partial_refund ? 'customer_partially_refunded_order' : 'customer_refunded_order'; if ( $order_id ) { - $this->object = wc_get_order( $order_id ); - $this->recipient = $this->object->get_billing_email(); - - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = wc_get_order( $order_id ); + $this->recipient = $this->object->get_billing_email(); + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! empty( $refund_id ) ) { diff --git a/includes/emails/class-wc-email-failed-order.php b/includes/emails/class-wc-email-failed-order.php index 0b0ff5927e5..586cdfaa24f 100644 --- a/includes/emails/class-wc-email-failed-order.php +++ b/includes/emails/class-wc-email-failed-order.php @@ -23,11 +23,16 @@ class WC_Email_Failed_Order extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'failed_order'; - $this->title = __( 'Failed order', 'woocommerce' ); - $this->description = __( 'Failed order emails are sent to chosen recipient(s) when orders have been marked failed (if they were previously processing or on-hold).', 'woocommerce' ); - $this->template_html = 'emails/admin-failed-order.php'; - $this->template_plain = 'emails/plain/admin-failed-order.php'; + $this->id = 'failed_order'; + $this->title = __( 'Failed order', 'woocommerce' ); + $this->description = __( 'Failed order emails are sent to chosen recipient(s) when orders have been marked failed (if they were previously processing or on-hold).', 'woocommerce' ); + $this->template_html = 'emails/admin-failed-order.php'; + $this->template_plain = 'emails/plain/admin-failed-order.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers for this email add_action( 'woocommerce_order_status_pending_to_failed_notification', array( $this, 'trigger' ), 10, 2 ); @@ -72,11 +77,9 @@ class WC_Email_Failed_Order extends WC_Email { } if ( is_a( $order, 'WC_Order' ) ) { - $this->object = $order; - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = $order; + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/emails/class-wc-email-new-order.php b/includes/emails/class-wc-email-new-order.php index 911c3f4e7a6..bfbd165f4a5 100644 --- a/includes/emails/class-wc-email-new-order.php +++ b/includes/emails/class-wc-email-new-order.php @@ -23,11 +23,16 @@ class WC_Email_New_Order extends WC_Email { * Constructor. */ public function __construct() { - $this->id = 'new_order'; - $this->title = __( 'New order', 'woocommerce' ); - $this->description = __( 'New order emails are sent to chosen recipient(s) when a new order is received.', 'woocommerce' ); - $this->template_html = 'emails/admin-new-order.php'; - $this->template_plain = 'emails/plain/admin-new-order.php'; + $this->id = 'new_order'; + $this->title = __( 'New order', 'woocommerce' ); + $this->description = __( 'New order emails are sent to chosen recipient(s) when a new order is received.', 'woocommerce' ); + $this->template_html = 'emails/admin-new-order.php'; + $this->template_plain = 'emails/plain/admin-new-order.php'; + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_date}' => '', + '{order_number}' => '', + ); // Triggers for this email add_action( 'woocommerce_order_status_pending_to_processing_notification', array( $this, 'trigger' ), 10, 2 ); @@ -76,11 +81,9 @@ class WC_Email_New_Order extends WC_Email { } if ( is_a( $order, 'WC_Order' ) ) { - $this->object = $order; - $this->find['order-date'] = '{order_date}'; - $this->find['order-number'] = '{order_number}'; - $this->replace['order-date'] = wc_format_datetime( $this->object->get_date_created() ); - $this->replace['order-number'] = $this->object->get_order_number(); + $this->object = $order; + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); } if ( ! $this->is_enabled() || ! $this->get_recipient() ) { diff --git a/includes/emails/class-wc-email.php b/includes/emails/class-wc-email.php index f96f84bc99b..62795af8a17 100644 --- a/includes/emails/class-wc-email.php +++ b/includes/emails/class-wc-email.php @@ -95,18 +95,6 @@ class WC_Email extends WC_Settings_API { */ public $object; - /** - * Strings to find in subjects/headings. - * @var array - */ - public $find = array(); - - /** - * Strings to replace in subjects/headings. - * @var array - */ - public $replace = array(); - /** * Mime boundary (for multipart emails). * @var string @@ -195,34 +183,54 @@ class WC_Email extends WC_Settings_API { ' ', // Runs of spaces, post-handling ); + /** + * Strings to find/replace in subjects/headings. + * + * @var array + */ + protected $placeholders = array(); + + /** + * Strings to find in subjects/headings. + * + * @deprecated 3.2.0 in favour of placeholders + * @var array + */ + public $find = array(); + + /** + * Strings to replace in subjects/headings. + * + * @deprecated 3.2.0 in favour of placeholders + * @var array + */ + public $replace = array(); + /** * Constructor. */ public function __construct() { + // Find/replace + if ( empty( $this->placeholders ) ) { + $this->placeholders = array( + '{site_title}' => $this->get_blogname() + ); + } + // Init settings $this->init_form_fields(); $this->init_settings(); - // Save settings hook - add_action( 'woocommerce_update_options_email_' . $this->id, array( $this, 'process_admin_options' ) ); - // Default template base if not declared in child constructor if ( is_null( $this->template_base ) ) { $this->template_base = WC()->plugin_path() . '/templates/'; } - // Settings $this->email_type = $this->get_option( 'email_type' ); $this->enabled = $this->get_option( 'enabled' ); - // Find/replace - $this->find['blogname'] = '{blogname}'; - $this->find['site-title'] = '{site_title}'; - $this->replace['blogname'] = $this->get_blogname(); - $this->replace['site-title'] = $this->get_blogname(); - - // For multipart messages add_action( 'phpmailer_init', array( $this, 'handle_multipart' ) ); + add_action( 'woocommerce_update_options_email_' . $this->id, array( $this, 'process_admin_options' ) ); } /** @@ -246,7 +254,9 @@ class WC_Email extends WC_Settings_API { * @return string */ public function format_string( $string ) { - return str_replace( apply_filters( 'woocommerce_email_format_string_find', $this->find, $this ), apply_filters( 'woocommerce_email_format_string_replace', $this->replace, $this ), $string ); + // handle legacy find and replace. + $string = str_replace( $this->find, $this->replace, $string ); + return str_replace( apply_filters( 'woocommerce_email_format_string_find', array_keys( $this->placeholders ), $this ), apply_filters( 'woocommerce_email_format_string_replace', array_values( $this->placeholders ), $this ), $string ); } /** @@ -538,7 +548,7 @@ class WC_Email extends WC_Settings_API { 'type' => 'text', 'desc_tip' => true, /* translators: %s: list of placeholders */ - 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '{site_title}' ), + 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '' . implode( ', ', array_keys( $this->placeholders ) ) . '' ), 'placeholder' => $this->get_default_subject(), 'default' => '', ), @@ -547,7 +557,7 @@ class WC_Email extends WC_Settings_API { 'type' => 'text', 'desc_tip' => true, /* translators: %s: list of placeholders */ - 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '{site_title}' ), + 'description' => sprintf( __( 'Available placeholders: %s', 'woocommerce' ), '' . implode( ', ', array_keys( $this->placeholders ) ) . '' ), 'placeholder' => $this->get_default_heading(), 'default' => '', ), From 16870c9c255aaf3cafe6168e29be8015990bc40f Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 14:30:47 +0100 Subject: [PATCH 057/224] Fix copy pasta errors in refunded email template Closes #16157 --- includes/emails/class-wc-email-customer-refunded-order.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/emails/class-wc-email-customer-refunded-order.php b/includes/emails/class-wc-email-customer-refunded-order.php index 8a17908efbf..f09b859d986 100644 --- a/includes/emails/class-wc-email-customer-refunded-order.php +++ b/includes/emails/class-wc-email-customer-refunded-order.php @@ -74,9 +74,9 @@ class WC_Email_Customer_Refunded_Order extends WC_Email { */ public function get_default_heading( $partial = false ) { if ( $partial ) { - return __( 'Order {order_number} details', 'woocommerce' ); - } else { return __( 'Your order has been partially refunded', 'woocommerce' ); + } else { + return __( 'Order {order_number} details', 'woocommerce' ); } } @@ -90,7 +90,7 @@ class WC_Email_Customer_Refunded_Order extends WC_Email { if ( $this->partial_refund ) { $subject = $this->get_option( 'subject_partial', $this->get_default_subject( true ) ); } else { - $subject = $this->get_option( 'subject_full', $this->get_default_heading() ); + $subject = $this->get_option( 'subject_full', $this->get_default_subject() ); } return apply_filters( 'woocommerce_email_subject_customer_refunded_order', $this->format_string( $subject ), $this->object ); } From 578da2d1262d37a56370beb3092807da7cf703e7 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 14:58:46 +0100 Subject: [PATCH 058/224] Unchecked checkbox should be an empty string Closes #16184 --- includes/class-wc-checkout.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-checkout.php b/includes/class-wc-checkout.php index d176435df38..0984e30c510 100644 --- a/includes/class-wc-checkout.php +++ b/includes/class-wc-checkout.php @@ -562,7 +562,7 @@ class WC_Checkout { switch ( $type ) { case 'checkbox' : - $value = (int) isset( $_POST[ $key ] ); + $value = isset( $_POST[ $key ] ) ? 1 : ''; break; case 'multiselect' : $value = isset( $_POST[ $key ] ) ? implode( ', ', wc_clean( $_POST[ $key ] ) ) : ''; From 916294b7d039fd83ccf2593b7a774a46e490a985 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 15:18:33 +0100 Subject: [PATCH 059/224] Use comment_status input name Closes #16153 --- includes/admin/meta-boxes/class-wc-meta-box-product-data.php | 2 +- includes/admin/meta-boxes/views/html-product-data-advanced.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/admin/meta-boxes/class-wc-meta-box-product-data.php b/includes/admin/meta-boxes/class-wc-meta-box-product-data.php index d49517932d4..616fd65f132 100644 --- a/includes/admin/meta-boxes/class-wc-meta-box-product-data.php +++ b/includes/admin/meta-boxes/class-wc-meta-box-product-data.php @@ -357,7 +357,7 @@ class WC_Meta_Box_Product_Data { 'product_url' => esc_url_raw( $_POST['_product_url'] ), 'button_text' => wc_clean( $_POST['_button_text'] ), 'children' => 'grouped' === $product_type ? self::prepare_children() : null, - 'reviews_allowed' => ! empty( $_POST['_reviews_allowed'] ), + 'reviews_allowed' => ! empty( $_POST['comment_status'] ) && 'open' === $_POST['comment_status'], 'attributes' => $attributes, 'default_attributes' => self::prepare_set_attributes( $attributes, 'default_attribute_' ), ) ); diff --git a/includes/admin/meta-boxes/views/html-product-data-advanced.php b/includes/admin/meta-boxes/views/html-product-data-advanced.php index 6e31d9eb208..350dadcfa44 100644 --- a/includes/admin/meta-boxes/views/html-product-data-advanced.php +++ b/includes/admin/meta-boxes/views/html-product-data-advanced.php @@ -37,7 +37,7 @@ if ( ! defined( 'ABSPATH' ) ) {
    '_reviews_allowed', + 'id' => 'comment_status', 'value' => $product_object->get_reviews_allowed( 'edit' ) ? 'open' : 'closed', 'label' => __( 'Enable reviews', 'woocommerce' ), 'cbvalue' => 'open', From 24fcf52c5975faea8f842e7e3df2dbc289593545 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 15:55:56 +0100 Subject: [PATCH 060/224] Store applied_coupons --- includes/class-wc-discounts.php | 177 +++++++++++++++++++------------- 1 file changed, 105 insertions(+), 72 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index c47f45dd76e..ef6ce0e74ec 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -23,12 +23,19 @@ class WC_Discounts { protected $items = array(); /** - * An array of discounts which have been applied. + * An array of discounts which have been applied to items. * * @var array */ protected $discounts = array(); + /** + * An array of applied coupons codes and total discount. + * + * @var array + */ + protected $applied_coupons = array(); + /** * Precision so we can work in cents. * @@ -43,32 +50,6 @@ class WC_Discounts { $this->precision = pow( 10, wc_get_price_decimals() ); } - /** - * Remove precision from a price. - * - * @param int $value - * @return float - */ - protected function remove_precision( $value ) { - return wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); - } - - /** - * Sort by price. - * - * @param array $a - * @param array $b - * @return int - */ - private function sort_by_price( $a, $b ) { - $price_1 = $a->price * $a->quantity; - $price_2 = $b->price * $b->quantity;; - if ( $price_1 === $price_2 ) { - return 0; - } - return ( $price_1 < $price_2 ) ? 1 : -1; - } - /** * Get items. * @@ -121,6 +102,17 @@ class WC_Discounts { return $item->price - $this->discounts[ $item->key ]; } + /** + * Returns a list of applied coupons with name value pairs - name being + * the coupon code, and value being the total amount disounted. + * + * @since 3.2.0 + * @return array + */ + public function get_applied_coupons() { + return array_map( array( $this, 'remove_precision' ), $this->applied_coupons ); + } + /** * Set cart/order items which will be discounted. * @@ -162,6 +154,68 @@ class WC_Discounts { } } + /** + * Apply a discount to all items using a coupon. + * + * @todo Coupon class has lots of WC()->cart calls and needs decoupling. This makes 'is valid' hard to use here. + * @todo is_valid_for_product accepts values - how can we deal with that? + * + * @since 3.2.0 + * @param WC_Coupon $coupon + * @return bool True if applied. + */ + public function apply_coupon( $coupon ) { + if ( ! is_a( $coupon, 'WC_Coupon' ) ) { + return false; + } + + if ( ! isset( $this->applied_coupons[ $coupon->get_code() ] ) ) { + $this->applied_coupons[ $coupon->get_code() ] = 0; + } + + // @todo how can we support the old woocommerce_coupon_get_discount_amount filter? + // @todo is valid for product - filter items here and pass to function? + $items_to_apply = $this->get_items_to_apply_coupon( $coupon ); + + switch ( $coupon->get_discount_type() ) { + case 'percent' : + $this->applied_coupons[ $coupon->get_code() ] += $this->apply_percentage_discount( $items_to_apply, $coupon->get_amount() ); + break; + case 'fixed_product' : + $this->applied_coupons[ $coupon->get_code() ] += $this->apply_fixed_product_discount( $items_to_apply, $coupon->get_amount() * $this->precision ); + break; + case 'fixed_cart' : + $this->applied_coupons[ $coupon->get_code() ] += $this->apply_fixed_cart_discount( $items_to_apply, $coupon->get_amount() * $this->precision ); + break; + } + } + + /** + * Remove precision from a price. + * + * @param int $value + * @return float + */ + protected function remove_precision( $value ) { + return wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); + } + + /** + * Sort by price. + * + * @param array $a + * @param array $b + * @return int + */ + protected function sort_by_price( $a, $b ) { + $price_1 = $a->price * $a->quantity; + $price_2 = $b->price * $b->quantity;; + if ( $price_1 === $price_2 ) { + return 0; + } + return ( $price_1 < $price_2 ) ? 1 : -1; + } + /** * Filter out all products which have been fully discounted to 0. * Used as array_filter callback. @@ -212,38 +266,6 @@ class WC_Discounts { return $items_to_apply; } - /** - * Apply a discount to all items using a coupon. - * - * @todo Coupon class has lots of WC()->cart calls and needs decoupling. This makes 'is valid' hard to use here. - * @todo is_valid_for_product accepts values - how can we deal with that? - * - * @since 3.2.0 - * @param WC_Coupon $coupon - * @return bool True if applied. - */ - public function apply_coupon( $coupon ) { - if ( ! is_a( $coupon, 'WC_Coupon' ) ) { - return false; - } - - // @todo how can we support the old woocommerce_coupon_get_discount_amount filter? - // @todo is valid for product - filter items here and pass to function? - $items_to_apply = $this->get_items_to_apply_coupon( $coupon ); - - switch ( $coupon->get_discount_type() ) { - case 'percent' : - $this->apply_percentage_discount( $items_to_apply, $coupon->get_amount() ); - break; - case 'fixed_product' : - $this->apply_fixed_product_discount( $items_to_apply, $coupon->get_amount() * $this->precision ); - break; - case 'fixed_cart' : - $this->apply_fixed_cart_discount( $items_to_apply, $coupon->get_amount() * $this->precision ); - break; - } - } - /** * Apply a discount amount to an item and ensure it does not go negative. * @@ -264,39 +286,50 @@ class WC_Discounts { * * @since 3.2.0 * @param array $items_to_apply Array of items to apply the coupon to. - * @param float $amount + * @param int $amount + * @return int total discounted in cents */ protected function apply_percentage_discount( $items_to_apply, $amount ) { + $total_discounted = 0; + foreach ( $items_to_apply as $item ) { - $this->add_item_discount( $item, $amount * ( $this->get_discounted_price_in_cents( $item ) / 100 ) ); + $total_discounted += $this->add_item_discount( $item, $amount * ( $this->get_discounted_price_in_cents( $item ) / 100 ) ); } + + return $total_discounted; } /** * Apply fixed product discount to items. * * @since 3.2.0 - * @param array $items_to_apply Array of items to apply the coupon to. + * @param array $items_to_apply Array of items to apply the coupon to. * @param int $amount + * @return int total discounted in cents */ protected function apply_fixed_product_discount( $items_to_apply, $discount ) { + $total_discounted = 0; + foreach ( $items_to_apply as $item ) { - $this->add_item_discount( $item, $discount * $item->quantity ); + $total_discounted += $this->add_item_discount( $item, $discount * $item->quantity ); } + + return $total_discounted; } /** * Apply fixed cart discount to items. * - * @since 3.2.0 - * @param array $items_to_apply Array of items to apply the coupon to. - * @param int $cart_discount + * @since 3.2.0 + * @param array $items_to_apply Array of items to apply the coupon to. + * @param int $cart_discount + * @return int total discounted in cents */ protected function apply_fixed_cart_discount( $items_to_apply, $cart_discount ) { - $items_to_apply = array_filter( $items_to_apply, array( $this, 'filter_products_with_price' ) ); + $items_to_apply = array_filter( $items_to_apply, array( $this, 'filter_products_with_price' ) ); if ( ! $item_count = array_sum( wp_list_pluck( $items_to_apply, 'quantity' ) ) ) { - return; + return 0; } $per_item_discount = floor( $cart_discount / $item_count ); // round it down to the nearest cent number. @@ -311,16 +344,14 @@ class WC_Discounts { * If there is still discount remaining, repeat the process. */ if ( $amount_discounted > 0 && $amount_discounted < $cart_discount ) { - $this->apply_fixed_cart_discount( $items_to_apply, $cart_discount - $amount_discounted ); + $amount_discounted += $this->apply_fixed_cart_discount( $items_to_apply, $cart_discount - $amount_discounted ); } - return; - } /** * Deal with remaining fractional discounts by splitting it over items * until the amount is expired, discounting 1 cent at a time. */ - if ( $cart_discount > 0 ) { + } elseif ( $cart_discount > 0 ) { foreach ( $items_to_apply as $item ) { for ( $i = 0; $i < $item->quantity; $i ++ ) { $amount_discounted += $this->add_item_discount( $item, 1 ); @@ -334,5 +365,7 @@ class WC_Discounts { } } } + + return $amount_discounted; } } From f77fc2178548f4a3c01eed5c02463e82e0314679 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 16:17:58 +0100 Subject: [PATCH 061/224] test_get_applied_coupons --- includes/class-wc-discounts.php | 5 ++-- tests/unit-tests/discounts/discounts.php | 33 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index ef6ce0e74ec..26b9b56b6d1 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -120,8 +120,9 @@ class WC_Discounts { * @param array $raw_items List of raw cart or order items. */ public function set_items( $raw_items ) { - $this->items = array(); - $this->discounts = array(); + $this->items = array(); + $this->discounts = array(); + $this->applied_coupons = array(); if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { foreach ( $raw_items as $raw_item ) { diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index f14cf09d478..6d1415bcf0a 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -48,6 +48,39 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $order->delete( true ); } + /** + * test get_applied_coupons + */ + public function test_get_applied_coupons() { + $discounts = new WC_Discounts(); + $product = WC_Helper_Product::create_simple_product(); + WC()->cart->add_to_cart( $product->get_id(), 1 ); + $discounts->set_items( WC()->cart->get_cart() ); + + $coupon = new WC_Coupon; + $coupon->set_code( 'test' ); + $coupon->set_amount( 50 ); + $coupon->set_discount_type( 'percent' ); + $discounts->apply_coupon( $coupon ); + + $this->assertEquals( array( 'test' => 5 ), $discounts->get_applied_coupons() ); + + $coupon2 = new WC_Coupon; + $coupon2->set_code( 'test2' ); + $coupon2->set_amount( 50 ); + $coupon2->set_discount_type( 'percent' ); + $discounts->apply_coupon( $coupon2 ); + + $this->assertEquals( array( 'test' => 5, 'test2' => 2.50 ), $discounts->get_applied_coupons() ); + + $discounts->apply_coupon( $coupon ); + $this->assertEquals( array( 'test' => 6.25, 'test2' => 2.50 ), $discounts->get_applied_coupons() ); + + // Cleanup. + WC()->cart->empty_cart(); + $product->delete( true ); + } + /** * Test applying a coupon (make sure it changes prices). */ From 1b63749a68addff45cfb5d0cb7c3199de3c068e6 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 19 Jul 2017 16:21:35 +0100 Subject: [PATCH 062/224] Tests for other coupon types --- tests/unit-tests/discounts/discounts.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 6d1415bcf0a..247f5257441 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -57,6 +57,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { WC()->cart->add_to_cart( $product->get_id(), 1 ); $discounts->set_items( WC()->cart->get_cart() ); + // Test applying multiple coupons and getting totals. $coupon = new WC_Coupon; $coupon->set_code( 'test' ); $coupon->set_amount( 50 ); @@ -76,6 +77,20 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $discounts->apply_coupon( $coupon ); $this->assertEquals( array( 'test' => 6.25, 'test2' => 2.50 ), $discounts->get_applied_coupons() ); + // Test different coupon types. + WC()->cart->empty_cart(); + WC()->cart->add_to_cart( $product->get_id(), 2 ); + $coupon->set_discount_type( 'fixed_product' ); + $coupon->set_amount( 2 ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( array( 'test' => 4 ), $discounts->get_applied_coupons() ); + + $coupon->set_discount_type( 'fixed_cart' ); + $discounts->set_items( WC()->cart->get_cart() ); + $discounts->apply_coupon( $coupon ); + $this->assertEquals( array( 'test' => 2 ), $discounts->get_applied_coupons() ); + // Cleanup. WC()->cart->empty_cart(); $product->delete( true ); From 68470b51a4d50af983785bbadc0b4538e8ed6044 Mon Sep 17 00:00:00 2001 From: Saul Fautley Date: Wed, 19 Jul 2017 18:53:02 +0200 Subject: [PATCH 063/224] Updated structured data for products - Replaced outdated `priceSpecification` with `AggregateOffer`. - Restructured data according to Google's latest specifications. - Use product short_description if available. --- includes/class-wc-structured-data.php | 67 +++++++++++++++------------ 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/includes/class-wc-structured-data.php b/includes/class-wc-structured-data.php index d689bef9429..1f158de13e8 100644 --- a/includes/class-wc-structured-data.php +++ b/includes/class-wc-structured-data.php @@ -193,54 +193,63 @@ class WC_Structured_Data { return; } - $shop_name = get_bloginfo( 'name' ); - $shop_url = home_url(); - $currency = get_woocommerce_currency(); - $markup = array(); - $markup['@type'] = 'Product'; - $markup['@id'] = get_permalink( $product->get_id() ); - $markup['url'] = $markup['@id']; - $markup['name'] = $product->get_name(); + $shop_name = get_bloginfo( 'name' ); + $shop_url = home_url(); + $currency = get_woocommerce_currency(); + + $markup = array( + '@type' => 'Product', + '@id' => get_permalink( $product->get_id() ), + 'name' => $product->get_name(), + ); if ( apply_filters( 'woocommerce_structured_data_product_limit', is_product_taxonomy() || is_shop() ) ) { + $markup['url'] = $markup['@id']; + $this->set_data( apply_filters( 'woocommerce_structured_data_product_limited', $markup, $product ) ); return; } - if ( '' !== $product->get_price() ) { - $markup_offer = array( - '@type' => 'Offer', - 'priceCurrency' => $currency, - 'availability' => 'https://schema.org/' . $stock = ( $product->is_in_stock() ? 'InStock' : 'OutOfStock' ), - 'sku' => $product->get_sku(), - 'image' => wp_get_attachment_url( $product->get_image_id() ), - 'description' => $product->get_description(), - 'seller' => array( - '@type' => 'Organization', - 'name' => $shop_name, - 'url' => $shop_url, - ), - ); + $markup['image'] = wp_get_attachment_url( $product->get_image_id() ); + $markup['description'] = $product->get_short_description() ?: $product->get_description(); + $markup['sku'] = $product->get_sku(); + if ( '' !== $product->get_price() ) { if ( $product->is_type( 'variable' ) ) { $prices = $product->get_variation_prices(); $lowest = reset( $prices['price'] ); $highest = end( $prices['price'] ); if ( $lowest === $highest ) { - $markup_offer['price'] = wc_format_decimal( $product->get_price(), wc_get_price_decimals() ); + $markup_offer = array( + '@type' => 'Offer', + 'price' => wc_format_decimal( $lowest, wc_get_price_decimals() ), + ); } else { - $markup_offer['priceSpecification'] = array( - 'price' => wc_format_decimal( $product->get_price(), wc_get_price_decimals() ), - 'minPrice' => wc_format_decimal( $lowest, wc_get_price_decimals() ), - 'maxPrice' => wc_format_decimal( $highest, wc_get_price_decimals() ), - 'priceCurrency' => $currency, + $markup_offer = array( + '@type' => 'AggregateOffer', + 'lowPrice' => wc_format_decimal( $lowest, wc_get_price_decimals() ), + 'highPrice' => wc_format_decimal( $highest, wc_get_price_decimals() ), ); } } else { - $markup_offer['price'] = wc_format_decimal( $product->get_price(), wc_get_price_decimals() ); + $markup_offer = array( + '@type' => 'Offer', + 'price' => wc_format_decimal( $product->get_price(), wc_get_price_decimals() ), + ); } + $markup_offer += array( + 'priceCurrency' => $currency, + 'availability' => 'https://schema.org/' . ( $product->is_in_stock() ? 'InStock' : 'OutOfStock' ), + 'url' => $markup['@id'], + 'seller' => array( + '@type' => 'Organization', + 'name' => $shop_name, + 'url' => $shop_url, + ), + ); + $markup['offers'] = array( apply_filters( 'woocommerce_structured_data_product_offer', $markup_offer, $product ) ); } From c569d4fd816c4783bf9037de664b9af10bf63ff2 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 11:23:37 +0100 Subject: [PATCH 064/224] Travis --- .travis.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 401d098fb7d..15aa83513d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,8 @@ language: php sudo: false -# Test main supported versions of PHP and HHVM against latest WP. 5.2 is min supported version. +# Test main supported versions of PHP and HHVM against latest WP. php: - - 5.2 - - 5.3 - 5.6 - 7.0 - 7.1 @@ -18,6 +16,12 @@ matrix: include: - php: 5.6 env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 + - php: 5.3 + env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 + dist: precise + - php: 5.2 + env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 + dist: precise before_script: - export PATH="$HOME/.composer/vendor/bin:$PATH" From aa7f8237631251b38f66d5b4da3950e5c48760f9 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 11:42:53 +0100 Subject: [PATCH 065/224] Travis config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 15aa83513d9..1bb1b568cae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ matrix: include: - php: 5.6 env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 + dist: precise - php: 5.3 env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 dist: precise From 3fbba9d869643feaee6f5a320b9c6a183c885214 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 12:00:21 +0100 Subject: [PATCH 066/224] More travis config --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1bb1b568cae..26835b877f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: php sudo: false +dist: trusty + # Test main supported versions of PHP and HHVM against latest WP. php: - 5.6 @@ -18,10 +20,10 @@ matrix: env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 dist: precise - php: 5.3 - env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 + env: WP_VERSION=master dist: precise - php: 5.2 - env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 + env: WP_VERSION=master dist: precise before_script: From 1c26587948229708963395b3f8f1825e49eeca04 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 12:12:29 +0100 Subject: [PATCH 067/224] travis --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 26835b877f8..4dcde4c2130 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,9 @@ php: - 7.0 - 7.1 +cache: + apt: true + env: - WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 @@ -20,10 +23,10 @@ matrix: env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 dist: precise - php: 5.3 - env: WP_VERSION=master + env: WP_VERSION=latest dist: precise - php: 5.2 - env: WP_VERSION=master + env: WP_VERSION=latest dist: precise before_script: From d7484336c1d9609e5e1e8dcce33171ca84a77031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20M=C3=B6rchen?= Date: Thu, 20 Jul 2017 13:29:54 +0200 Subject: [PATCH 068/224] Fixed wrong class reference in comment --- includes/wc-template-functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index 9900373b170..51d8ed192d1 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -2460,7 +2460,7 @@ if ( ! function_exists( 'wc_display_item_meta' ) ) { /** * Display item meta data. * @since 3.0.0 - * @param WC_Item $item + * @param WC_Order_Item $item * @param array $args * @return string|void */ @@ -2498,7 +2498,7 @@ if ( ! function_exists( 'wc_display_item_downloads' ) ) { /** * Display item download links. * @since 3.0.0 - * @param WC_Item $item + * @param WC_Order_Item $item * @param array $args * @return string|void */ From b14c719fc0890a8a9e93f6132ff7f7776069aab4 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 12:39:46 +0100 Subject: [PATCH 069/224] Travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4dcde4c2130..8f17c8a2059 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,10 +23,10 @@ matrix: env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 dist: precise - php: 5.3 - env: WP_VERSION=latest + env: WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 dist: precise - php: 5.2 - env: WP_VERSION=latest + env: WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 dist: precise before_script: From 3faa1c1ee9615cf7b96cfecaa1caa970120b5b58 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 12:48:14 +0100 Subject: [PATCH 070/224] Unset variable prices on read. Closes #16145 --- .../data-stores/class-wc-product-variable-data-store-cpt.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/includes/data-stores/class-wc-product-variable-data-store-cpt.php index d9e243052b8..70e1112ae2a 100644 --- a/includes/data-stores/class-wc-product-variable-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-variable-data-store-cpt.php @@ -29,6 +29,10 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple protected function read_product_data( &$product ) { parent::read_product_data( $product ); + // Make sure data which does not apply to variables is unset. + $product->set_regular_price( '' ); + $product->set_sale_price( '' ); + // Set directly since individual data needs changed at the WC_Product_Variation level -- these datasets just pull. $children = $this->read_children( $product ); $product->set_children( $children['all'] ); From 8654a94af2efa68e06e96feae9ff14d8de8f3b66 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 13:57:10 +0100 Subject: [PATCH 071/224] update installer --- tests/bin/install.sh | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/tests/bin/install.sh b/tests/bin/install.sh index dcea51f3708..521b86fd857 100755 --- a/tests/bin/install.sh +++ b/tests/bin/install.sh @@ -16,15 +16,17 @@ WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} download() { - if [ `which curl` ]; then - curl -s "$1" > "$2"; - elif [ `which wget` ]; then - wget -nv -O "$2" "$1" - fi + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi } if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then WP_TESTS_TAG="tags/$WP_VERSION" +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" else # http serves a single offer, whereas https serves multiple. we only want one download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json @@ -47,15 +49,21 @@ install_wp() { mkdir -p $WP_CORE_DIR - if [ $WP_VERSION == 'latest' ]; then - local ARCHIVE_NAME='latest' + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p /tmp/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip + unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ + mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR else - local ARCHIVE_NAME="wordpress-$WP_VERSION" + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz + tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR fi - download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz - tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR - download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php } @@ -74,8 +82,6 @@ install_test_suite() { svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes fi - cd $WP_TESTS_DIR - if [ ! -f wp-tests-config.php ]; then download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php From 06c1a2ad8daa5062e53267ab249ef292b6e2b744 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 14:33:38 +0100 Subject: [PATCH 072/224] apply discount --- includes/class-wc-discounts.php | 13 +++++ tests/unit-tests/discounts/discounts.php | 60 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 26b9b56b6d1..3593d45d59b 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -155,6 +155,19 @@ class WC_Discounts { } } + /** + * Allows a discount to be applied to the items programmatically without a coupon. + * @return [type] [description] + */ + public function apply_discount( $discount ) { + if ( strstr( $discount, '%' ) ) { + $discount = absint( rtrim( $discount, '%' ) ); + $discounted = $this->apply_percentage_discount( $this->items, $discount ); + + return $this->remove_precision( $discounted ); + } + } + /** * Apply a discount to all items using a coupon. * diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 247f5257441..3d591bb7b76 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -48,6 +48,66 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $order->delete( true ); } + public function test_apply_discount() { + $tax_rate = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '20.0000', + 'tax_rate_name' => 'VAT', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => '', + ); + $tax_rate2 = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '20.0000', + 'tax_rate_name' => 'VAT', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => 'reduced-rate', + ); + $tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate ); + $tax_rate_id2 = WC_Tax::_insert_tax_rate( $tax_rate2 ); + update_option( 'woocommerce_calc_taxes', 'yes' ); + + $product = WC_Helper_Product::create_simple_product(); + $product2 = WC_Helper_Product::create_simple_product(); + + $product->set_tax_class( '' ); + $product2->set_tax_class( 'reduced-rate' ); + + $product->save(); + $product2->save(); + + // Add product to the cart. + WC()->cart->add_to_cart( $product->get_id(), 1 ); + WC()->cart->add_to_cart( $product2->get_id(), 1 ); + + // Test. + $discounts = new WC_Discounts(); + $discounts->set_items( WC()->cart->get_cart() ); + $result = $discounts->apply_discount( '50%' ); + + echo "\n\n\n"; + print_r( $result ); + echo "\n\n\n"; + + $this->assertEquals( array( 'test' => 2 ), $result ); + + // Cleanup. + WC()->cart->empty_cart(); + $product->delete( true ); + $product2->delete( true ); + WC_Tax::_delete_tax_rate( $tax_rate_id ); + WC_Tax::_delete_tax_rate( $tax_rate_id2 ); + update_option( 'woocommerce_calc_taxes', 'no' ); + } + /** * test get_applied_coupons */ From c72e2d6aab54a08df54d150ffcabfafd91115881 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 14:34:16 +0100 Subject: [PATCH 073/224] test --- tests/bin/install.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/bin/install.sh b/tests/bin/install.sh index 521b86fd857..42e677ce263 100755 --- a/tests/bin/install.sh +++ b/tests/bin/install.sh @@ -2,7 +2,7 @@ # see https://github.com/wp-cli/wp-cli/blob/master/templates/install-wp-tests.sh if [ $# -lt 3 ]; then - echo "usage: $0 [db-host] [wp-version]" + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" exit 1 fi @@ -11,6 +11,7 @@ DB_USER=$2 DB_PASS=$3 DB_HOST=${4-localhost} WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} @@ -80,11 +81,14 @@ install_test_suite() { # set up testing suite mkdir -p $WP_TESTS_DIR svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data fi if [ ! -f wp-tests-config.php ]; then download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php @@ -94,6 +98,11 @@ install_test_suite() { } install_db() { + + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + # parse DB_HOST for port or socket references local PARTS=(${DB_HOST//\:/ }) local DB_HOSTNAME=${PARTS[0]}; From 1f1219599e7a87fe5b680e2145a3482967946240 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 15:00:54 +0100 Subject: [PATCH 074/224] remove quiet --- tests/bin/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bin/install.sh b/tests/bin/install.sh index 42e677ce263..237d3893361 100755 --- a/tests/bin/install.sh +++ b/tests/bin/install.sh @@ -80,8 +80,8 @@ install_test_suite() { if [ ! -d $WP_TESTS_DIR ]; then # set up testing suite mkdir -p $WP_TESTS_DIR - svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes - svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data fi if [ ! -f wp-tests-config.php ]; then From 80cbba49d6d2374935c592ccd177ec2c33f541f7 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 15:41:28 +0100 Subject: [PATCH 075/224] More test tweaks --- .travis.yml | 5 ----- tests/bin/install.sh | 6 +++--- tests/bin/travis.sh | 4 ++-- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8f17c8a2059..035df03dcc1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: php sudo: false -dist: trusty - # Test main supported versions of PHP and HHVM against latest WP. php: - 5.6 @@ -21,13 +19,10 @@ matrix: include: - php: 5.6 env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 - dist: precise - php: 5.3 env: WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 - dist: precise - php: 5.2 env: WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 - dist: precise before_script: - export PATH="$HOME/.composer/vendor/bin:$PATH" diff --git a/tests/bin/install.sh b/tests/bin/install.sh index 237d3893361..8a8b0eb4419 100755 --- a/tests/bin/install.sh +++ b/tests/bin/install.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# see https://github.com/wp-cli/wp-cli/blob/master/templates/install-wp-tests.sh +# See https://raw.githubusercontent.com/wp-cli/scaffold-command/master/templates/install-wp-tests.sh if [ $# -lt 3 ]; then echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" @@ -80,8 +80,8 @@ install_test_suite() { if [ ! -d $WP_TESTS_DIR ]; then # set up testing suite mkdir -p $WP_TESTS_DIR - svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes - svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data fi if [ ! -f wp-tests-config.php ]; then diff --git a/tests/bin/travis.sh b/tests/bin/travis.sh index 03fbd24c565..b3a64a82f6e 100755 --- a/tests/bin/travis.sh +++ b/tests/bin/travis.sh @@ -20,6 +20,8 @@ if [ $1 == 'before' ]; then elif [ $1 == 'during' ]; then +elif [ $1 == 'after' ]; then + ## Only run on latest stable PHP box (defined in .travis.yml). if [[ ${TRAVIS_PHP_VERSION} == ${PHP_LATEST_STABLE} ]]; then # WordPress Coding Standards. @@ -38,8 +40,6 @@ elif [ $1 == 'during' ]; then ./vendor/bin/phpcs -p -s -n ./**/**/**/**/*.php --standard=./phpcs.ruleset.xml --extensions=php --ignore=./vendor/**/**/*.php --ignore=./tests/**/**/*.php fi -elif [ $1 == 'after' ]; then - ## Only run on master, not pull requests, latest stable PHP box (defined in .travis.yml). if [[ ${TRAVIS_BRANCH} == 'master' ]] && [[ ${TRAVIS_EVENT_TYPE} != 'pull_request' ]] && [[ ${TRAVIS_PHP_VERSION} == ${PHP_LATEST_STABLE} ]]; then wget https://scrutinizer-ci.com/ocular.phar From 20f2c60f2dd46a25a9170c26a191c49d666266fb Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 15:43:40 +0100 Subject: [PATCH 076/224] remove during --- tests/bin/travis.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/bin/travis.sh b/tests/bin/travis.sh index b3a64a82f6e..b5731355e8c 100755 --- a/tests/bin/travis.sh +++ b/tests/bin/travis.sh @@ -18,8 +18,6 @@ if [ $1 == 'before' ]; then composer self-update composer install --no-interaction -elif [ $1 == 'during' ]; then - elif [ $1 == 'after' ]; then ## Only run on latest stable PHP box (defined in .travis.yml). From a7fec4475f3dc1788a008f04b082b5624554980d Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 15:55:45 +0100 Subject: [PATCH 077/224] 5.3 does not like dist --- .travis.yml | 6 ++---- tests/bin/travis.sh | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 035df03dcc1..bc49cf91d6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,6 @@ php: - 7.0 - 7.1 -cache: - apt: true - env: - WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 @@ -21,8 +18,10 @@ matrix: env: WP_VERSION=latest WP_MULTISITE=1 PHP_LATEST_STABLE=7.1 - php: 5.3 env: WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 + dist: precise - php: 5.2 env: WP_VERSION=latest WP_MULTISITE=0 PHP_LATEST_STABLE=7.1 + dist: precise before_script: - export PATH="$HOME/.composer/vendor/bin:$PATH" @@ -31,7 +30,6 @@ before_script: script: - bash tests/bin/phpunit.sh - - bash tests/bin/travis.sh during after_script: - bash tests/bin/travis.sh after diff --git a/tests/bin/travis.sh b/tests/bin/travis.sh index b5731355e8c..1e3b68851d0 100755 --- a/tests/bin/travis.sh +++ b/tests/bin/travis.sh @@ -9,10 +9,10 @@ if [ $1 == 'before' ]; then # No Xdebug and therefore no coverage in PHP 5.3 [[ ${TRAVIS_PHP_VERSION} == '5.3' ]] && exit; - if [[ ${TRAVIS_PHP_VERSION:0:2} == "7." ]]; then - composer global require "phpunit/phpunit=5.7.*" - else + if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." ]]; then composer global require "phpunit/phpunit=4.8.*" + else + composer global require "phpunit/phpunit=5.7.*" fi composer self-update From a8e69c2e24af852669e9f93fecf55303d0418e3c Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 12:55:08 -0300 Subject: [PATCH 078/224] Enable external code coverage in Scrutinizer --- .scrutinizer.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 07af2c45c90..543a40cf4ed 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -30,3 +30,5 @@ checks: tools: sensiolabs_security_checker: true + external_code_coverage: + timeout: 600 # Wait 10 minutes for Travis send coverage file. From c7ee52602a21af0af60022b9d106537404363edc Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 13:32:21 -0300 Subject: [PATCH 079/224] Updated PHPUnit version --- composer.json | 2 +- composer.lock | 471 +++++++++++++++++++++++++++++--------------- tests/bin/travis.sh | 2 +- 3 files changed, 309 insertions(+), 166 deletions(-) diff --git a/composer.json b/composer.json index c5263b52868..e0c01286b91 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "require-dev": { "squizlabs/php_codesniffer": "*", "wp-coding-standards/wpcs": "0.10.0", - "phpunit/phpunit": "5.7.19" + "phpunit/phpunit": "6.2.3" }, "scripts": { "post-install-cmd": [ diff --git a/composer.lock b/composer.lock index e548a7eef1b..8135c716e9f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "3e1a8fa4256dcf3ef3caee1b027d6779", + "content-hash": "4028dd750463d5e5ad5f2099196a949a", "packages": [ { "name": "composer/installers", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/composer/installers.git", - "reference": "d78064c68299743e0161004f2de3a0204e33b804" + "reference": "79ad876c7498c0bbfe7eed065b8651c93bfd6045" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/d78064c68299743e0161004f2de3a0204e33b804", - "reference": "d78064c68299743e0161004f2de3a0204e33b804", + "url": "https://api.github.com/repos/composer/installers/zipball/79ad876c7498c0bbfe7eed065b8651c93bfd6045", + "reference": "79ad876c7498c0bbfe7eed065b8651c93bfd6045", "shasum": "" }, "require": { @@ -59,12 +59,16 @@ "keywords": [ "Craft", "Dolibarr", + "Eliasis", "Hurad", "ImageCMS", + "Kanboard", "MODX Evo", "Mautic", + "Maya", "OXID", "Plentymarkets", + "Porto", "RadPHP", "SMF", "Thelia", @@ -87,9 +91,11 @@ "fuelphp", "grav", "installer", + "itop", "joomla", "kohana", "laravel", + "lavalite", "lithium", "magento", "mako", @@ -104,6 +110,7 @@ "roundcube", "shopware", "silverstripe", + "sydes", "symfony", "typo3", "wordpress", @@ -111,7 +118,7 @@ "zend", "zikula" ], - "time": "2016-08-13T20:53:52+00:00" + "time": "2017-04-24T06:37:16+00:00" } ], "packages-dev": [ @@ -211,6 +218,108 @@ ], "time": "2017-04-12T18:52:22+00:00" }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "1.0", @@ -267,22 +376,22 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" + "reference": "46f7e8bb075036c92695b15a1ddb6971c751e585" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/46f7e8bb075036c92695b15a1ddb6971c751e585", + "reference": "46f7e8bb075036c92695b15a1ddb6971c751e585", "shasum": "" }, "require": { "php": ">=5.5", "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", + "phpdocumentor/type-resolver": "^0.4.0", "webmozart/assert": "^1.0" }, "require-dev": { @@ -308,24 +417,24 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-09-30T07:12:33+00:00" + "time": "2017-07-15T11:38:20+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "0.2.1", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", "shasum": "" }, "require": { - "php": ">=5.5", + "php": "^5.5 || ^7.0", "phpdocumentor/reflection-common": "^1.0" }, "require-dev": { @@ -355,7 +464,7 @@ "email": "me@mikevanriel.com" } ], - "time": "2016-11-25T06:54:22+00:00" + "time": "2017-07-14T14:27:02+00:00" }, { "name": "phpspec/prophecy", @@ -422,40 +531,41 @@ }, { "name": "phpunit/php-code-coverage", - "version": "4.0.8", + "version": "5.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d" + "reference": "dc421f9ca5082a0c0cb04afb171c765f79add85b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d", - "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/dc421f9ca5082a0c0cb04afb171c765f79add85b", + "reference": "dc421f9ca5082a0c0cb04afb171c765f79add85b", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^5.6 || ^7.0", + "php": "^7.0", "phpunit/php-file-iterator": "^1.3", "phpunit/php-text-template": "^1.2", - "phpunit/php-token-stream": "^1.4.2 || ^2.0", + "phpunit/php-token-stream": "^1.4.11 || ^2.0", "sebastian/code-unit-reverse-lookup": "^1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "^1.0 || ^2.0" + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0", + "theseer/tokenizer": "^1.1" }, "require-dev": { - "ext-xdebug": "^2.1.4", - "phpunit/phpunit": "^5.7" + "ext-xdebug": "^2.5", + "phpunit/phpunit": "^6.0" }, "suggest": { - "ext-xdebug": "^2.5.1" + "ext-xdebug": "^2.5.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "5.2.x-dev" } }, "autoload": { @@ -481,7 +591,7 @@ "testing", "xunit" ], - "time": "2017-04-02T07:44:40+00:00" + "time": "2017-04-21T08:03:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -671,16 +781,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.7.19", + "version": "6.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1" + "reference": "fa5711d0559fc4b64deba0702be52d41434cbcb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1", - "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fa5711d0559fc4b64deba0702be52d41434cbcb7", + "reference": "fa5711d0559fc4b64deba0702be52d41434cbcb7", "shasum": "" }, "require": { @@ -689,33 +799,35 @@ "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.6.2", - "phpunit/php-code-coverage": "^4.0.4", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", + "myclabs/deep-copy": "^1.3", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.2", + "phpunit/php-file-iterator": "^1.4", + "phpunit/php-text-template": "^1.2", "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "^1.2.4", - "sebastian/diff": "~1.2", - "sebastian/environment": "^1.3.4 || ^2.0", - "sebastian/exporter": "~2.0", - "sebastian/global-state": "^1.1", - "sebastian/object-enumerator": "~2.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0.3|~2.0", - "symfony/yaml": "~2.1|~3.0" + "phpunit/phpunit-mock-objects": "^4.0", + "sebastian/comparator": "^2.0", + "sebastian/diff": "^1.4.3 || ^2.0", + "sebastian/environment": "^3.0.2", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^1.1 || ^2.0", + "sebastian/object-enumerator": "^3.0.2", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-xdebug": "*", - "phpunit/php-invoker": "~1.1" + "phpunit/php-invoker": "^1.1" }, "bin": [ "phpunit" @@ -723,7 +835,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.7.x-dev" + "dev-master": "6.2.x-dev" } }, "autoload": { @@ -749,33 +861,33 @@ "testing", "xunit" ], - "time": "2017-04-03T02:22:27+00:00" + "time": "2017-07-03T15:54:24+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "3.4.3", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24" + "reference": "d8833b396dce9162bb2eb5d59aee5a3ab3cfa5b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", - "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/d8833b396dce9162bb2eb5d59aee5a3ab3cfa5b4", + "reference": "d8833b396dce9162bb2eb5d59aee5a3ab3cfa5b4", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", + "php": "^7.0", "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2 || ^2.0" + "sebastian/exporter": "^3.0" }, "conflict": { - "phpunit/phpunit": "<5.4.0" + "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^5.4" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-soap": "*" @@ -783,7 +895,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -808,7 +920,7 @@ "mock", "xunit" ], - "time": "2016-12-08T20:27:08+00:00" + "time": "2017-06-30T08:15:21+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -857,30 +969,30 @@ }, { "name": "sebastian/comparator", - "version": "1.2.4", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + "reference": "20f84f468cb67efee293246e6a09619b891f55f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/20f84f468cb67efee293246e6a09619b891f55f0", + "reference": "20f84f468cb67efee293246e6a09619b891f55f0", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" + "php": "^7.0", + "sebastian/diff": "^1.2", + "sebastian/exporter": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -917,27 +1029,27 @@ "compare", "equality" ], - "time": "2017-01-29T09:50:25+00:00" + "time": "2017-03-03T06:26:08+00:00" }, { "name": "sebastian/diff", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", "extra": { @@ -969,32 +1081,32 @@ "keywords": [ "diff" ], - "time": "2015-12-08T07:14:41+00:00" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", - "version": "2.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac" + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac", - "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.0" + "phpunit/phpunit": "^6.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1019,34 +1131,34 @@ "environment", "hhvm" ], - "time": "2016-11-26T07:53:53+00:00" + "time": "2017-07-01T08:51:00+00:00" }, { "name": "sebastian/exporter", - "version": "2.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", - "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", "shasum": "" }, "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/recursion-context": "^3.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.1.x-dev" } }, "autoload": { @@ -1086,27 +1198,27 @@ "export", "exporter" ], - "time": "2016-11-19T08:54:04+00:00" + "time": "2017-04-03T13:19:02+00:00" }, { "name": "sebastian/global-state", - "version": "1.1.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.2" + "phpunit/phpunit": "^6.0" }, "suggest": { "ext-uopz": "*" @@ -1114,7 +1226,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1137,33 +1249,34 @@ "keywords": [ "global state" ], - "time": "2015-10-12T03:26:01+00:00" + "time": "2017-04-27T15:39:26+00:00" }, { "name": "sebastian/object-enumerator", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7" + "reference": "31dd3379d16446c5d86dec32ab1ad1f378581ad8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7", - "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/31dd3379d16446c5d86dec32ab1ad1f378581ad8", + "reference": "31dd3379d16446c5d86dec32ab1ad1f378581ad8", "shasum": "" }, "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~2.0" + "php": "^7.0", + "sebastian/object-reflector": "^1.0", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "~5" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -1183,32 +1296,77 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-02-18T15:18:39+00:00" + "time": "2017-03-12T15:17:29+00:00" }, { - "name": "sebastian/recursion-context", - "version": "2.0.0", + "name": "sebastian/object-reflector", + "version": "1.1.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", - "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.4" + "phpunit/phpunit": "^6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -1236,7 +1394,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-11-19T07:33:16+00:00" + "time": "2017-03-03T06:23:57+00:00" }, { "name": "sebastian/resource-operations", @@ -1325,16 +1483,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "2.7.0", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "571e27b6348e5b3a637b2abc82ac0d01e6d7bbed" + "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/571e27b6348e5b3a637b2abc82ac0d01e6d7bbed", - "reference": "571e27b6348e5b3a637b2abc82ac0d01e6d7bbed", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dcbed1074f8244661eecddfc2a675430d8d33f62", + "reference": "dcbed1074f8244661eecddfc2a675430d8d33f62", "shasum": "" }, "require": { @@ -1399,62 +1557,47 @@ "phpcs", "standards" ], - "time": "2016-09-01T23:53:02+00:00" + "time": "2017-05-22T02:43:20+00:00" }, { - "name": "symfony/yaml", - "version": "v3.2.7", + "name": "theseer/tokenizer", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621" + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/62b4cdb99d52cb1ff253c465eb1532a80cebb621", - "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", "shasum": "" }, "require": { - "php": ">=5.5.9" - }, - "require-dev": { - "symfony/console": "~2.8|~3.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" } ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2017-03-20T09:45:15+00:00" + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" }, { "name": "webmozart/assert", diff --git a/tests/bin/travis.sh b/tests/bin/travis.sh index 1e3b68851d0..3c2a13a3ca7 100755 --- a/tests/bin/travis.sh +++ b/tests/bin/travis.sh @@ -12,7 +12,7 @@ if [ $1 == 'before' ]; then if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." ]]; then composer global require "phpunit/phpunit=4.8.*" else - composer global require "phpunit/phpunit=5.7.*" + composer global require "phpunit/phpunit=6.2.*" fi composer self-update From 84be2dd6bbdf65439f115ed0c108be8f871ad22e Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 13:32:47 -0300 Subject: [PATCH 080/224] Fixed declaration of WC_Tests_CRUD_Data::onNotSuccessfulTest --- tests/unit-tests/crud/data.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit-tests/crud/data.php b/tests/unit-tests/crud/data.php index 9b38b26c63d..a3f9c425429 100644 --- a/tests/unit-tests/crud/data.php +++ b/tests/unit-tests/crud/data.php @@ -18,7 +18,7 @@ class WC_Tests_CRUD_Data extends WC_Unit_Test_Case { update_option( 'timezone_string', '' ); } - public function onNotSuccessfulTest( $e ) { + public function onNotSuccessfulTest( Throwable $e ) { // @codingStandardsIgnoreStart date_default_timezone_set( 'UTC' ); // @codingStandardsIgnoreEnd From 0e96bd8a93e9f79f63f6d99592d5610203ee3fad Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 17:34:30 +0100 Subject: [PATCH 081/224] Discount rows --- includes/class-wc-discounts.php | 44 ++++++++++++++++++++---- tests/unit-tests/discounts/discounts.php | 12 ++++--- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 3593d45d59b..f9990d94c07 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -157,15 +157,47 @@ class WC_Discounts { /** * Allows a discount to be applied to the items programmatically without a coupon. - * @return [type] [description] + * + * @param string $raw_discount Discount amount either fixed or percentage. + * @return int discounted amount in cents. */ - public function apply_discount( $discount ) { - if ( strstr( $discount, '%' ) ) { - $discount = absint( rtrim( $discount, '%' ) ); - $discounted = $this->apply_percentage_discount( $this->items, $discount ); + public function apply_discount( $raw_discount ) { + if ( strstr( $raw_discount, '%' ) ) { + $discount = absint( rtrim( $raw_discount, '%' ) ); + $total_to_discount = 0; - return $this->remove_precision( $discounted ); + // Get total item cost right now. + foreach ( $this->items as $item ) { + $total_to_discount += $this->get_discounted_price_in_cents( $item ); + } + + // @todo sum other manual discounts too + foreach ( $this->discounts as $key => $value ) { + if ( strstr( $key, 'discount-' ) ) { + $total_to_discount = $total_to_discount - $value; + } + } + + $discount_total = $discount * ( $total_to_discount / 100 ); + $discount_id = ''; + $index = 1; + + while ( ! $discount_id ) { + $discount_id = 'discount-' . $raw_discount; + + if ( 1 < $index ) { + $discount_id .= '-' . $index; + } + + if ( isset( $this->discounts[ $discount_id ] ) ) { + $index ++; + $discount_id = ''; + } + } + + return $this->discounts[ $discount_id ] = $discount_total; } + // @todo fixed discounts } /** diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 3d591bb7b76..f5d185e4ab5 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -91,13 +91,15 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Test. $discounts = new WC_Discounts(); $discounts->set_items( WC()->cart->get_cart() ); - $result = $discounts->apply_discount( '50%' ); - echo "\n\n\n"; - print_r( $result ); - echo "\n\n\n"; + $discounts->apply_discount( '50%' ); + $all_discounts = $discounts->get_discounts(); + $this->assertEquals( $all_discounts['discount-50%'], 10 ); - $this->assertEquals( array( 'test' => 2 ), $result ); + $discounts->apply_discount( '50%' ); + $all_discounts = $discounts->get_discounts(); + $this->assertEquals( $all_discounts['discount-50%'], 10, print_r( $all_discounts, true ) ); + $this->assertEquals( $all_discounts['discount-50%-2'], 5, print_r( $all_discounts, true ) ); // Cleanup. WC()->cart->empty_cart(); From 498a5daa53615bc502da75133ee5033f7479b755 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 13:34:34 -0300 Subject: [PATCH 082/224] Removed PHPCS from Travis in favor of pre-commit hook --- tests/bin/travis.sh | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/tests/bin/travis.sh b/tests/bin/travis.sh index 3c2a13a3ca7..d57a67be39b 100755 --- a/tests/bin/travis.sh +++ b/tests/bin/travis.sh @@ -20,25 +20,7 @@ if [ $1 == 'before' ]; then elif [ $1 == 'after' ]; then - ## Only run on latest stable PHP box (defined in .travis.yml). - if [[ ${TRAVIS_PHP_VERSION} == ${PHP_LATEST_STABLE} ]]; then - # WordPress Coding Standards. - # @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards - # @link http://pear.php.net/package/PHP_CodeSniffer/ - # -p flag: Show progress of the run. - # -s flag: Show sniff codes in all reports. - # -v flag: Print verbose output. - # -n flag: Do not print warnings. (shortcut for --warning-severity=0) - # --standard: Use WordPress as the standard. - # --extensions: Only sniff PHP files. - ./vendor/bin/phpcs -p -s -n ./*.php --standard=./phpcs.ruleset.xml --extensions=php - ./vendor/bin/phpcs -p -s -n ./**/*.php --standard=./phpcs.ruleset.xml --extensions=php --ignore=./vendor/*.php --ignore=./tests/*.php - ./vendor/bin/phpcs -p -s -n ./**/**/*.php --standard=./phpcs.ruleset.xml --extensions=php --ignore=./vendor/**/*.php --ignore=./tests/**/*.php - ./vendor/bin/phpcs -p -s -n ./**/**/**/*.php --standard=./phpcs.ruleset.xml --extensions=php --ignore=./vendor/**/**/*.php --ignore=./tests/**/**/*.php - ./vendor/bin/phpcs -p -s -n ./**/**/**/**/*.php --standard=./phpcs.ruleset.xml --extensions=php --ignore=./vendor/**/**/*.php --ignore=./tests/**/**/*.php - fi - - ## Only run on master, not pull requests, latest stable PHP box (defined in .travis.yml). + # Only run on master, not pull requests, latest stable PHP box (defined in .travis.yml). if [[ ${TRAVIS_BRANCH} == 'master' ]] && [[ ${TRAVIS_EVENT_TYPE} != 'pull_request' ]] && [[ ${TRAVIS_PHP_VERSION} == ${PHP_LATEST_STABLE} ]]; then wget https://scrutinizer-ci.com/ocular.phar chmod +x ocular.phar From 5a918b8f93f4e4e08df7d53fca6e2d02f677538c Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 13:47:02 -0300 Subject: [PATCH 083/224] Not install project composer file on Travis --- tests/bin/travis.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/bin/travis.sh b/tests/bin/travis.sh index d57a67be39b..b548987e982 100755 --- a/tests/bin/travis.sh +++ b/tests/bin/travis.sh @@ -15,9 +15,6 @@ if [ $1 == 'before' ]; then composer global require "phpunit/phpunit=6.2.*" fi - composer self-update - composer install --no-interaction - elif [ $1 == 'after' ]; then # Only run on master, not pull requests, latest stable PHP box (defined in .travis.yml). From 46d3638089b58b51a2321b67443b83f409c6b4ec Mon Sep 17 00:00:00 2001 From: Jaydeep Rami Date: Thu, 20 Jul 2017 22:31:14 +0530 Subject: [PATCH 084/224] Fix missing return statement (#16148) * Fix missing return statement * Fix missing return statement * Fix missing return statement * Fix missing return statement * Fix missing return statement * Fix missing return statement --- includes/abstracts/abstract-wc-data.php | 2 +- includes/abstracts/abstract-wc-product.php | 3 ++- includes/abstracts/abstract-wc-settings-api.php | 1 - includes/abstracts/abstract-wc-widget.php | 5 +---- includes/admin/class-wc-admin-settings.php | 1 - includes/admin/class-wc-admin.php | 2 -- includes/admin/reports/class-wc-admin-report.php | 2 -- includes/admin/reports/class-wc-report-coupon-usage.php | 2 -- .../admin/reports/class-wc-report-sales-by-category.php | 2 -- includes/admin/reports/class-wc-report-sales-by-date.php | 2 -- includes/admin/reports/class-wc-report-sales-by-product.php | 2 -- includes/admin/reports/class-wc-report-taxes-by-code.php | 2 -- includes/admin/reports/class-wc-report-taxes-by-date.php | 2 -- includes/api/legacy/v1/class-wc-api-authentication.php | 1 - includes/api/legacy/v1/class-wc-api-customers.php | 1 - includes/api/legacy/v1/class-wc-api-resource.php | 1 - includes/api/legacy/v1/class-wc-api-server.php | 1 - includes/api/legacy/v2/class-wc-api-authentication.php | 1 - includes/api/legacy/v2/class-wc-api-customers.php | 1 - includes/api/legacy/v2/class-wc-api-resource.php | 1 - includes/api/legacy/v2/class-wc-api-server.php | 1 - includes/api/legacy/v3/class-wc-api-authentication.php | 1 - includes/api/legacy/v3/class-wc-api-customers.php | 1 - includes/api/legacy/v3/class-wc-api-resource.php | 1 - includes/api/legacy/v3/class-wc-api-server.php | 1 - includes/class-wc-emails.php | 5 ++--- includes/class-wc-tax.php | 4 ---- includes/data-stores/class-wc-data-store-wp.php | 1 - .../class-wc-addons-gateway-simplify-commerce.php | 1 - includes/import/abstract-wc-product-importer.php | 1 - includes/import/class-wc-product-csv-importer.php | 6 ------ .../class-wc-shipping-legacy-free-shipping.php | 1 - includes/wc-cart-functions.php | 1 - includes/wc-update-functions.php | 2 -- 34 files changed, 6 insertions(+), 56 deletions(-) diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index fd4605b04fc..f1ec28fabe4 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -200,8 +200,8 @@ abstract class WC_Data { } else { $this->data_store->create( $this ); } - return $this->get_id(); } + return $this->get_id(); } /** diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index eefd9527897..cdec7844dcf 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -1280,6 +1280,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { * Save data (either create or update depending on if we are working on an existing product). * * @since 3.0.0 + * @return int */ public function save() { $this->validate_props(); @@ -1296,8 +1297,8 @@ class WC_Product extends WC_Abstract_Legacy_Product { if ( $this->get_parent_id() ) { wc_deferred_product_sync( $this->get_parent_id() ); } - return $this->get_id(); } + return $this->get_id(); } /* diff --git a/includes/abstracts/abstract-wc-settings-api.php b/includes/abstracts/abstract-wc-settings-api.php index ef4767fda4d..d96513a16bc 100644 --- a/includes/abstracts/abstract-wc-settings-api.php +++ b/includes/abstracts/abstract-wc-settings-api.php @@ -89,7 +89,6 @@ abstract class WC_Settings_API { * on the gateway's settings screen. * * @since 1.0.0 - * @return string */ public function init_form_fields() {} diff --git a/includes/abstracts/abstract-wc-widget.php b/includes/abstracts/abstract-wc-widget.php index 68b047c9b8c..8247a4d851b 100644 --- a/includes/abstracts/abstract-wc-widget.php +++ b/includes/abstracts/abstract-wc-widget.php @@ -118,10 +118,8 @@ abstract class WC_Widget extends WP_Widget { /** * Output the html at the start of a widget. * - * @param array $args + * @param array $args * @param array $instance - * - * @return string */ public function widget_start( $args, $instance ) { echo $args['before_widget']; @@ -135,7 +133,6 @@ abstract class WC_Widget extends WP_Widget { * Output the html at the end of a widget. * * @param array $args - * @return string */ public function widget_end( $args ) { echo $args['after_widget']; diff --git a/includes/admin/class-wc-admin-settings.php b/includes/admin/class-wc-admin-settings.php index 73bb749f5c1..e8fb57e0204 100644 --- a/includes/admin/class-wc-admin-settings.php +++ b/includes/admin/class-wc-admin-settings.php @@ -110,7 +110,6 @@ class WC_Admin_Settings { /** * Output messages + errors. - * @return string */ public static function show_messages() { if ( sizeof( self::$errors ) > 0 ) { diff --git a/includes/admin/class-wc-admin.php b/includes/admin/class-wc-admin.php index 1ca5210d75a..334590ea6f6 100644 --- a/includes/admin/class-wc-admin.php +++ b/includes/admin/class-wc-admin.php @@ -176,8 +176,6 @@ class WC_Admin { /** * Preview email template. - * - * @return string */ public function preview_emails() { diff --git a/includes/admin/reports/class-wc-admin-report.php b/includes/admin/reports/class-wc-admin-report.php index 9499cc832f4..b634b8c7a0e 100644 --- a/includes/admin/reports/class-wc-admin-report.php +++ b/includes/admin/reports/class-wc-admin-report.php @@ -616,8 +616,6 @@ class WC_Admin_Report { /** * Get the main chart. - * - * @return string */ public function get_main_chart() {} diff --git a/includes/admin/reports/class-wc-report-coupon-usage.php b/includes/admin/reports/class-wc-report-coupon-usage.php index 2df70e25fb8..7e8a72ec7a0 100644 --- a/includes/admin/reports/class-wc-report-coupon-usage.php +++ b/includes/admin/reports/class-wc-report-coupon-usage.php @@ -359,8 +359,6 @@ class WC_Report_Coupon_Usage extends WC_Admin_Report { /** * Get the main chart. - * - * @return string */ public function get_main_chart() { global $wp_locale; diff --git a/includes/admin/reports/class-wc-report-sales-by-category.php b/includes/admin/reports/class-wc-report-sales-by-category.php index 390a6b22210..85f7d876806 100644 --- a/includes/admin/reports/class-wc-report-sales-by-category.php +++ b/includes/admin/reports/class-wc-report-sales-by-category.php @@ -272,8 +272,6 @@ class WC_Report_Sales_By_Category extends WC_Admin_Report { /** * Get the main chart. - * - * @return string */ public function get_main_chart() { global $wp_locale; diff --git a/includes/admin/reports/class-wc-report-sales-by-date.php b/includes/admin/reports/class-wc-report-sales-by-date.php index af72e510f12..7921dc61d26 100644 --- a/includes/admin/reports/class-wc-report-sales-by-date.php +++ b/includes/admin/reports/class-wc-report-sales-by-date.php @@ -607,8 +607,6 @@ class WC_Report_Sales_By_Date extends WC_Admin_Report { /** * Get the main chart. - * - * @return string */ public function get_main_chart() { global $wp_locale; diff --git a/includes/admin/reports/class-wc-report-sales-by-product.php b/includes/admin/reports/class-wc-report-sales-by-product.php index 95858057158..e5edd1a0769 100644 --- a/includes/admin/reports/class-wc-report-sales-by-product.php +++ b/includes/admin/reports/class-wc-report-sales-by-product.php @@ -387,8 +387,6 @@ class WC_Report_Sales_By_Product extends WC_Admin_Report { /** * Get the main chart. - * - * @return string */ public function get_main_chart() { global $wp_locale; diff --git a/includes/admin/reports/class-wc-report-taxes-by-code.php b/includes/admin/reports/class-wc-report-taxes-by-code.php index 7950e3bb002..8f488ea5c29 100644 --- a/includes/admin/reports/class-wc-report-taxes-by-code.php +++ b/includes/admin/reports/class-wc-report-taxes-by-code.php @@ -67,8 +67,6 @@ class WC_Report_Taxes_By_Code extends WC_Admin_Report { /** * Get the main chart. - * - * @return string */ public function get_main_chart() { global $wpdb; diff --git a/includes/admin/reports/class-wc-report-taxes-by-date.php b/includes/admin/reports/class-wc-report-taxes-by-date.php index 2ffa0ea864e..6ac7424d092 100644 --- a/includes/admin/reports/class-wc-report-taxes-by-date.php +++ b/includes/admin/reports/class-wc-report-taxes-by-date.php @@ -67,8 +67,6 @@ class WC_Report_Taxes_By_Date extends WC_Admin_Report { /** * Get the main chart. - * - * @return string */ public function get_main_chart() { $query_data = array( diff --git a/includes/api/legacy/v1/class-wc-api-authentication.php b/includes/api/legacy/v1/class-wc-api-authentication.php index 87e79f3de92..1d26ae80ff6 100644 --- a/includes/api/legacy/v1/class-wc-api-authentication.php +++ b/includes/api/legacy/v1/class-wc-api-authentication.php @@ -19,7 +19,6 @@ class WC_API_Authentication { * Setup class * * @since 2.1 - * @return WC_API_Authentication */ public function __construct() { diff --git a/includes/api/legacy/v1/class-wc-api-customers.php b/includes/api/legacy/v1/class-wc-api-customers.php index d1b8fac6272..d5edb1503a2 100644 --- a/includes/api/legacy/v1/class-wc-api-customers.php +++ b/includes/api/legacy/v1/class-wc-api-customers.php @@ -31,7 +31,6 @@ class WC_API_Customers extends WC_API_Resource { * * @since 2.1 * @param WC_API_Server $server - * @return WC_API_Customers */ public function __construct( WC_API_Server $server ) { diff --git a/includes/api/legacy/v1/class-wc-api-resource.php b/includes/api/legacy/v1/class-wc-api-resource.php index c298e47a078..d419f8346e9 100644 --- a/includes/api/legacy/v1/class-wc-api-resource.php +++ b/includes/api/legacy/v1/class-wc-api-resource.php @@ -28,7 +28,6 @@ class WC_API_Resource { * * @since 2.1 * @param WC_API_Server $server - * @return WC_API_Resource */ public function __construct( WC_API_Server $server ) { diff --git a/includes/api/legacy/v1/class-wc-api-server.php b/includes/api/legacy/v1/class-wc-api-server.php index 6c85fcf4ade..530c6324def 100644 --- a/includes/api/legacy/v1/class-wc-api-server.php +++ b/includes/api/legacy/v1/class-wc-api-server.php @@ -112,7 +112,6 @@ class WC_API_Server { * * @since 2.1 * @param $path - * @return WC_API_Server */ public function __construct( $path ) { diff --git a/includes/api/legacy/v2/class-wc-api-authentication.php b/includes/api/legacy/v2/class-wc-api-authentication.php index 52b619a59c7..2c75a75b605 100644 --- a/includes/api/legacy/v2/class-wc-api-authentication.php +++ b/includes/api/legacy/v2/class-wc-api-authentication.php @@ -19,7 +19,6 @@ class WC_API_Authentication { * Setup class * * @since 2.1 - * @return WC_API_Authentication */ public function __construct() { diff --git a/includes/api/legacy/v2/class-wc-api-customers.php b/includes/api/legacy/v2/class-wc-api-customers.php index df92232aea6..d88865b1921 100644 --- a/includes/api/legacy/v2/class-wc-api-customers.php +++ b/includes/api/legacy/v2/class-wc-api-customers.php @@ -30,7 +30,6 @@ class WC_API_Customers extends WC_API_Resource { * * @since 2.1 * @param WC_API_Server $server - * @return WC_API_Customers */ public function __construct( WC_API_Server $server ) { diff --git a/includes/api/legacy/v2/class-wc-api-resource.php b/includes/api/legacy/v2/class-wc-api-resource.php index 89c3feef0fd..cd2a9ecde6f 100644 --- a/includes/api/legacy/v2/class-wc-api-resource.php +++ b/includes/api/legacy/v2/class-wc-api-resource.php @@ -27,7 +27,6 @@ class WC_API_Resource { * * @since 2.1 * @param WC_API_Server $server - * @return WC_API_Resource */ public function __construct( WC_API_Server $server ) { diff --git a/includes/api/legacy/v2/class-wc-api-server.php b/includes/api/legacy/v2/class-wc-api-server.php index e6413dca5ce..e5d43928193 100644 --- a/includes/api/legacy/v2/class-wc-api-server.php +++ b/includes/api/legacy/v2/class-wc-api-server.php @@ -111,7 +111,6 @@ class WC_API_Server { * * @since 2.1 * @param $path - * @return WC_API_Server */ public function __construct( $path ) { diff --git a/includes/api/legacy/v3/class-wc-api-authentication.php b/includes/api/legacy/v3/class-wc-api-authentication.php index 74c456309fd..88f0a711644 100644 --- a/includes/api/legacy/v3/class-wc-api-authentication.php +++ b/includes/api/legacy/v3/class-wc-api-authentication.php @@ -19,7 +19,6 @@ class WC_API_Authentication { * Setup class * * @since 2.1 - * @return WC_API_Authentication */ public function __construct() { diff --git a/includes/api/legacy/v3/class-wc-api-customers.php b/includes/api/legacy/v3/class-wc-api-customers.php index 4beca31c239..3ae5025530b 100644 --- a/includes/api/legacy/v3/class-wc-api-customers.php +++ b/includes/api/legacy/v3/class-wc-api-customers.php @@ -30,7 +30,6 @@ class WC_API_Customers extends WC_API_Resource { * * @since 2.1 * @param WC_API_Server $server - * @return WC_API_Customers */ public function __construct( WC_API_Server $server ) { diff --git a/includes/api/legacy/v3/class-wc-api-resource.php b/includes/api/legacy/v3/class-wc-api-resource.php index 4fefab1d0fb..4aface69c0c 100644 --- a/includes/api/legacy/v3/class-wc-api-resource.php +++ b/includes/api/legacy/v3/class-wc-api-resource.php @@ -27,7 +27,6 @@ class WC_API_Resource { * * @since 2.1 * @param WC_API_Server $server - * @return WC_API_Resource */ public function __construct( WC_API_Server $server ) { diff --git a/includes/api/legacy/v3/class-wc-api-server.php b/includes/api/legacy/v3/class-wc-api-server.php index 2e8aaaa6435..76cff49addf 100644 --- a/includes/api/legacy/v3/class-wc-api-server.php +++ b/includes/api/legacy/v3/class-wc-api-server.php @@ -111,7 +111,6 @@ class WC_API_Server { * * @since 2.1 * @param $path - * @return WC_API_Server */ public function __construct( $path ) { diff --git a/includes/class-wc-emails.php b/includes/class-wc-emails.php index b11928cce9e..8edc2d02660 100644 --- a/includes/class-wc-emails.php +++ b/includes/class-wc-emails.php @@ -375,9 +375,8 @@ class WC_Emails { * Add order meta to email templates. * * @param mixed $order - * @param bool $sent_to_admin (default: false) - * @param bool $plain_text (default: false) - * @return string + * @param bool $sent_to_admin (default: false) + * @param bool $plain_text (default: false) */ public function order_meta( $order, $sent_to_admin = false, $plain_text = false ) { $fields = apply_filters( 'woocommerce_email_order_meta_fields', array(), $sent_to_admin, $order ); diff --git a/includes/class-wc-tax.php b/includes/class-wc-tax.php index 7132f694eb4..a0c72452db3 100644 --- a/includes/class-wc-tax.php +++ b/includes/class-wc-tax.php @@ -916,7 +916,6 @@ class WC_Tax { * * @param int $tax_rate_id * @param string $postcodes String of postcodes separated by ; characters - * @return string */ public static function _update_tax_rate_postcodes( $tax_rate_id, $postcodes ) { if ( ! is_array( $postcodes ) ) { @@ -939,7 +938,6 @@ class WC_Tax { * * @param int $tax_rate_id * @param string $cities - * @return string */ public static function _update_tax_rate_cities( $tax_rate_id, $cities ) { if ( ! is_array( $cities ) ) { @@ -961,8 +959,6 @@ class WC_Tax { * @param int $tax_rate_id * @param array $values * @param string $type - * - * @return string */ private static function _update_tax_rate_locations( $tax_rate_id, $values, $type ) { global $wpdb; diff --git a/includes/data-stores/class-wc-data-store-wp.php b/includes/data-stores/class-wc-data-store-wp.php index 241275bc7c6..41e427c857d 100644 --- a/includes/data-stores/class-wc-data-store-wp.php +++ b/includes/data-stores/class-wc-data-store-wp.php @@ -86,7 +86,6 @@ class WC_Data_Store_WP { * @since 3.0.0 * @param WC_Data * @param stdClass (containing at least ->id) - * @return array */ public function delete_meta( &$object, $meta ) { delete_metadata_by_mid( $this->meta_type, $meta->id ); diff --git a/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php b/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php index 396bfd9d530..ea3b58d0bec 100644 --- a/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php +++ b/includes/gateways/simplify-commerce/class-wc-addons-gateway-simplify-commerce.php @@ -393,7 +393,6 @@ class WC_Addons_Gateway_Simplify_Commerce extends WC_Gateway_Simplify_Commerce { * @since 2.4 * @param string $payment_method_id The ID of the payment method to validate * @param array $payment_meta associative array of meta data required for automatic payments - * @return array * @throws Exception */ public function validate_subscription_payment_meta( $payment_method_id, $payment_meta ) { diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php index b1684c41dbc..251139d29fa 100644 --- a/includes/import/abstract-wc-product-importer.php +++ b/includes/import/abstract-wc-product-importer.php @@ -293,7 +293,6 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { * @param WC_Product $product Product instance. * @param array $data Item data. * - * @return WC_Product|WP_Error * @throws Exception */ protected function set_product_data( &$product, $data ) { diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php index 8ac995e10c0..aa69148a32e 100644 --- a/includes/import/class-wc-product-csv-importer.php +++ b/includes/import/class-wc-product-csv-importer.php @@ -54,8 +54,6 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { /** * Read file. - * - * @return array */ protected function read_file() { if ( false !== ( $handle = fopen( $this->file, 'r' ) ) ) { @@ -107,8 +105,6 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { /** * Set file mapped keys. - * - * @return array */ protected function set_mapped_keys() { $mapping = $this->params['mapping']; @@ -668,8 +664,6 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { /** * Map and format raw data to known fields. - * - * @return array */ protected function set_parsed_data() { $parse_functions = $this->get_formating_callback(); diff --git a/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php b/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php index dd2170f2d91..be23051a1fa 100644 --- a/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php +++ b/includes/shipping/legacy-free-shipping/class-wc-shipping-legacy-free-shipping.php @@ -216,7 +216,6 @@ class WC_Shipping_Legacy_Free_Shipping extends WC_Shipping_Method { /** * calculate_shipping function. - * @return array */ public function calculate_shipping( $package = array() ) { $args = array( diff --git a/includes/wc-cart-functions.php b/includes/wc-cart-functions.php index 2f69aa152b9..8eb336c7717 100644 --- a/includes/wc-cart-functions.php +++ b/includes/wc-cart-functions.php @@ -191,7 +191,6 @@ add_action( 'get_header', 'wc_clear_cart_after_payment' ); * Get the subtotal. * * @access public - * @return string */ function wc_cart_totals_subtotal_html() { echo WC()->cart->get_cart_subtotal(); diff --git a/includes/wc-update-functions.php b/includes/wc-update-functions.php index 6cdef46a8b3..250c07c4c70 100644 --- a/includes/wc-update-functions.php +++ b/includes/wc-update-functions.php @@ -995,8 +995,6 @@ function wc_update_300_webhooks() { /** * Add an index to the field comment_type to improve the response time of the query * used by WC_Comments::wp_count_comments() to get the number of comments by type. - * - * @return null */ function wc_update_300_comment_type_index() { global $wpdb; From 383c7eb8895f74c89969aaf3aea41e661d90e859 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 15:48:56 -0300 Subject: [PATCH 085/224] Fixed excluded paths for Code Climate --- .codeclimate.yml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 87f5413334f..707dca03ece 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -15,24 +15,24 @@ engines: - javascript ratings: paths: - - "includes/*" + - "includes/*" exclude_paths: - - tests/* - - apigen/* - - dummy-data/* - - i18n/* - - includes/api/legacy/* - - includes/libraries/* - - includes/updates/* - - includes/gateways/simplify-commerce/* - - includes/shipping/legacy-* - - includes/wc-deprecated-functions.php - - includes/class-wc-legacy-api.php - - assets/js/accounting/** - - assets/js/jquery-* - - assets/js/prettyPhoto/* - - assets/js/round/* - - assets/js/select2/* - - assets/js/selectWoo/* - - assets/js/stupidtable/* - - assets/js/zeroclipboard/* +- "tests/" +- "apigen/" +- "dummy-data/" +- "i18n/" +- "includes/api/legacy/" +- "includes/libraries/" +- "includes/updates/" +- "includes/gateways/simplify-commerce/" +- "includes/shipping/legacy-*" +- "includes/wc-deprecated-functions.php" +- "includes/class-wc-legacy-api.php" +- "assets/js/accounting/" +- "assets/js/jquery-*" +- "assets/js/prettyPhoto/" +- "assets/js/round/" +- "assets/js/select2/" +- "assets/js/selectWoo/" +- "assets/js/stupidtable/" +- "assets/js/zeroclipboard/" From e5ec0e66e309931b31892c833824ae3f10273807 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 20:33:27 +0100 Subject: [PATCH 086/224] fixed discount --- includes/class-wc-discounts.php | 69 ++++++++++++------------ tests/unit-tests/discounts/discounts.php | 19 +++++++ 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index f9990d94c07..ff6e934ba63 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -162,42 +162,43 @@ class WC_Discounts { * @return int discounted amount in cents. */ public function apply_discount( $raw_discount ) { - if ( strstr( $raw_discount, '%' ) ) { - $discount = absint( rtrim( $raw_discount, '%' ) ); - $total_to_discount = 0; + // Get total item cost after any extra discounts. + $total_to_discount = 0; - // Get total item cost right now. - foreach ( $this->items as $item ) { - $total_to_discount += $this->get_discounted_price_in_cents( $item ); - } - - // @todo sum other manual discounts too - foreach ( $this->discounts as $key => $value ) { - if ( strstr( $key, 'discount-' ) ) { - $total_to_discount = $total_to_discount - $value; - } - } - - $discount_total = $discount * ( $total_to_discount / 100 ); - $discount_id = ''; - $index = 1; - - while ( ! $discount_id ) { - $discount_id = 'discount-' . $raw_discount; - - if ( 1 < $index ) { - $discount_id .= '-' . $index; - } - - if ( isset( $this->discounts[ $discount_id ] ) ) { - $index ++; - $discount_id = ''; - } - } - - return $this->discounts[ $discount_id ] = $discount_total; + foreach ( $this->items as $item ) { + $total_to_discount += $this->get_discounted_price_in_cents( $item ); } - // @todo fixed discounts + + foreach ( $this->discounts as $key => $value ) { + if ( strstr( $key, 'discount-' ) ) { + $total_to_discount = $total_to_discount - $value; + } + } + + if ( strstr( $raw_discount, '%' ) ) { + $discount = absint( rtrim( $raw_discount, '%' ) ); + $discount_total = $discount * ( $total_to_discount / 100 ); + } else { + $discount_total = min( absint( $raw_discount * $this->precision ), $total_to_discount ); + } + + $discount_id = ''; + $index = 1; + + while ( ! $discount_id ) { + $discount_id = 'discount-' . $raw_discount; + + if ( 1 < $index ) { + $discount_id .= '-' . $index; + } + + if ( isset( $this->discounts[ $discount_id ] ) ) { + $index ++; + $discount_id = ''; + } + } + + return $this->discounts[ $discount_id ] = $discount_total; } /** diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index f5d185e4ab5..5941aa91ad4 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -101,6 +101,25 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $this->assertEquals( $all_discounts['discount-50%'], 10, print_r( $all_discounts, true ) ); $this->assertEquals( $all_discounts['discount-50%-2'], 5, print_r( $all_discounts, true ) ); + // Test fixed discounts. + $discounts = new WC_Discounts(); + $discounts->set_items( WC()->cart->get_cart() ); + + $discounts->apply_discount( '5' ); + $all_discounts = $discounts->get_discounts(); + $this->assertEquals( $all_discounts['discount-5'], 5 ); + + $discounts->apply_discount( '5' ); + $all_discounts = $discounts->get_discounts(); + $this->assertEquals( $all_discounts['discount-5'], 5, print_r( $all_discounts, true ) ); + $this->assertEquals( $all_discounts['discount-5-2'], 5, print_r( $all_discounts, true ) ); + + $discounts->apply_discount( '15' ); + $all_discounts = $discounts->get_discounts(); + $this->assertEquals( $all_discounts['discount-5'], 5, print_r( $all_discounts, true ) ); + $this->assertEquals( $all_discounts['discount-5-2'], 5, print_r( $all_discounts, true ) ); + $this->assertEquals( $all_discounts['discount-15'], 10, print_r( $all_discounts, true ) ); + // Cleanup. WC()->cart->empty_cart(); $product->delete( true ); From 499519eaac50af9ef0b2fe6aa9b1af073791fdc8 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Thu, 20 Jul 2017 20:50:09 +0100 Subject: [PATCH 087/224] WC_Discount class --- includes/class-wc-discount.php | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 includes/class-wc-discount.php diff --git a/includes/class-wc-discount.php b/includes/class-wc-discount.php new file mode 100644 index 00000000000..49b238af53e --- /dev/null +++ b/includes/class-wc-discount.php @@ -0,0 +1,77 @@ + '', + 'discount' => 0, + ); + + /** + * Coupon ID. + * + * @param string $id + */ + public function set_id( $id ) { + + } + + /** + * Discount amount - either fixed or percentage. + * + * @param string $amount + */ + public function set_amount( $amount ) { + + } + + /** + * Amount of discount this has given in total. + */ + public function set_discount_total() { + + } + + /** + * Array of negative taxes. + */ + public function set_taxes() { + + } + + /** + * Calculates the amount of negative tax to apply for this discount, since + * discounts are applied before tax. + * + * For percent discounts this is simply a percent of each cart item's tax. + * + * For fixed discounts, the taxes are calculated proportionally so the + * discount is fairly split between items. + * + * @return [type] [description] + */ + public function calculate_negative_taxes() { + + } + +} From 4251bd56143ca1188aeb8dca3c4ff8c471bb99da Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 16:50:12 -0300 Subject: [PATCH 088/224] Move code coverage to Scrutinizer --- .coveralls.yml | 3 --- .scrutinizer.yml | 23 +++++++++++++++++++++-- .travis.yml | 2 +- tests/bin/phpunit.sh | 5 ----- tests/bin/travis.sh | 9 --------- 5 files changed, 22 insertions(+), 20 deletions(-) delete mode 100644 .coveralls.yml delete mode 100755 tests/bin/phpunit.sh diff --git a/.coveralls.yml b/.coveralls.yml deleted file mode 100644 index c00a1ca9322..00000000000 --- a/.coveralls.yml +++ /dev/null @@ -1,3 +0,0 @@ -src_dir: . -coverage_clover: ./tmp/clover.xml -json_path: ./tmp/coveralls-upload.json diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 543a40cf4ed..9e2c1c000a3 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -30,5 +30,24 @@ checks: tools: sensiolabs_security_checker: true - external_code_coverage: - timeout: 600 # Wait 10 minutes for Travis send coverage file. + +build: + environment: + php: '7.1' + mysql: true + apache2: + modules: ['rewrite'] + variables: + WP_VERSION: 'latest' + dependencies: + before: + - bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION + - curl -sS https://getcomposer.org/installer | php + - php composer.phar install + tests: + override: + - + command: 'vendor/bin/phpunit --coverage-clover=results' + coverage: + file: 'results' + format: 'clover' diff --git a/.travis.yml b/.travis.yml index bc49cf91d6e..3733acf8f38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_script: - bash tests/bin/travis.sh before script: - - bash tests/bin/phpunit.sh + - phpunit -c phpunit.xml.dist after_script: - bash tests/bin/travis.sh after diff --git a/tests/bin/phpunit.sh b/tests/bin/phpunit.sh deleted file mode 100755 index bb7a731ab40..00000000000 --- a/tests/bin/phpunit.sh +++ /dev/null @@ -1,5 +0,0 @@ -if [[ ${TRAVIS_BRANCH} == 'master' ]] && [[ ${TRAVIS_EVENT_TYPE} != 'pull_request' ]] && [[ ${TRAVIS_PHP_VERSION} == ${PHP_LATEST_STABLE} ]]; then - phpunit -c phpunit.xml.dist --coverage-clover ./tmp/clover.xml -else - phpunit -c phpunit.xml.dist -fi diff --git a/tests/bin/travis.sh b/tests/bin/travis.sh index b548987e982..b04b7973d34 100755 --- a/tests/bin/travis.sh +++ b/tests/bin/travis.sh @@ -15,13 +15,4 @@ if [ $1 == 'before' ]; then composer global require "phpunit/phpunit=6.2.*" fi -elif [ $1 == 'after' ]; then - - # Only run on master, not pull requests, latest stable PHP box (defined in .travis.yml). - if [[ ${TRAVIS_BRANCH} == 'master' ]] && [[ ${TRAVIS_EVENT_TYPE} != 'pull_request' ]] && [[ ${TRAVIS_PHP_VERSION} == ${PHP_LATEST_STABLE} ]]; then - wget https://scrutinizer-ci.com/ocular.phar - chmod +x ocular.phar - php ocular.phar code-coverage:upload --format=php-clover ./tmp/clover.xml - fi - fi From 814f1b228bb57a31a4728f46eacdff2b78bdc2f8 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 17:24:52 -0300 Subject: [PATCH 089/224] Allow WP CodeSniffer on Scrutinizer and improved environment settings --- .scrutinizer.yml | 78 +++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 9e2c1c000a3..0f8d23732e5 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,17 +1,30 @@ -filter: - excluded_paths: - - tests/* - - apigen/* - - dummy-data/* - - i18n/* - - includes/api/legacy/* - - includes/legacy/* - - includes/libraries/* - - includes/updates/* - - includes/gateways/simplify-commerce/* - - includes/shipping/legacy-* - - includes/wc-deprecated-functions.php - - includes/class-wc-legacy-api.php +build: + environment: + php: '7.1' + mysql: true + postgresql: false + redis: false + node: false + apache2: + modules: ['rewrite'] + variables: + WP_VERSION: 'latest' + dependencies: + before: + - bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION + tests: + override: + - + command: 'vendor/bin/phpunit --coverage-clover=results' + coverage: + file: 'results' + format: 'clover' + +tools: + sensiolabs_security_checker: true + php_code_sniffer: + config: + standard: "WordPress" checks: php: @@ -28,26 +41,17 @@ checks: avoid_superglobals: false avoid_closing_tag: false -tools: - sensiolabs_security_checker: true - -build: - environment: - php: '7.1' - mysql: true - apache2: - modules: ['rewrite'] - variables: - WP_VERSION: 'latest' - dependencies: - before: - - bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION - - curl -sS https://getcomposer.org/installer | php - - php composer.phar install - tests: - override: - - - command: 'vendor/bin/phpunit --coverage-clover=results' - coverage: - file: 'results' - format: 'clover' +filter: + excluded_paths: + - tests/* + - apigen/* + - dummy-data/* + - i18n/* + - includes/api/legacy/* + - includes/legacy/* + - includes/libraries/* + - includes/updates/* + - includes/gateways/simplify-commerce/* + - includes/shipping/legacy-* + - includes/wc-deprecated-functions.php + - includes/class-wc-legacy-api.php From dcbe09376feaa777fc69a2a23c1a4bafa79cea67 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 18:05:36 -0300 Subject: [PATCH 090/224] Enable WordPress standard on Scrutinizer --- .scrutinizer.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 0f8d23732e5..122bcea2155 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -4,7 +4,6 @@ build: mysql: true postgresql: false redis: false - node: false apache2: modules: ['rewrite'] variables: @@ -24,7 +23,7 @@ tools: sensiolabs_security_checker: true php_code_sniffer: config: - standard: "WordPress" + standard: WordPress checks: php: @@ -40,6 +39,8 @@ checks: no_exit: false avoid_superglobals: false avoid_closing_tag: false + coding_standard: + name: WordPress filter: excluded_paths: From cb3e1de0bec17bf340840960c0945c655aa5c9a8 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 20 Jul 2017 18:14:53 -0300 Subject: [PATCH 091/224] Fixed coding standards --- includes/emails/class-wc-email.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/emails/class-wc-email.php b/includes/emails/class-wc-email.php index 62795af8a17..8bcf0660e8f 100644 --- a/includes/emails/class-wc-email.php +++ b/includes/emails/class-wc-email.php @@ -213,7 +213,7 @@ class WC_Email extends WC_Settings_API { // Find/replace if ( empty( $this->placeholders ) ) { $this->placeholders = array( - '{site_title}' => $this->get_blogname() + '{site_title}' => $this->get_blogname(), ); } From 50dbef7e8e5ff3bfefef2cb22668fde602953af5 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Fri, 21 Jul 2017 00:28:49 -0300 Subject: [PATCH 092/224] Set phpunit.xml.dist file for tests in Scrutinizer --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 122bcea2155..f3ec5117a1b 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -14,7 +14,7 @@ build: tests: override: - - command: 'vendor/bin/phpunit --coverage-clover=results' + command: 'vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover=results' coverage: file: 'results' format: 'clover' From e44450a40c2dc5cbdc52634bc59530dba47e69af Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 21 Jul 2017 10:39:24 +0100 Subject: [PATCH 093/224] Remove duplicate inherited method Closes #16206 --- includes/class-wc-product-variation.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/includes/class-wc-product-variation.php b/includes/class-wc-product-variation.php index f0c58de6f0a..79296d27b7d 100644 --- a/includes/class-wc-product-variation.php +++ b/includes/class-wc-product-variation.php @@ -426,16 +426,6 @@ class WC_Product_Variation extends WC_Product_Simple { $this->set_prop( 'attributes', $attributes ); } - /** - * Returns array of attribute name value pairs. Keys are prefixed with attribute_, as stored. - * - * @param string $context - * @return array - */ - public function get_attributes( $context = 'view' ) { - return $this->get_prop( 'attributes', $context ); - } - /** * Returns whether or not the product has any visible attributes. * From fd12c344f8b4bb4ace808bd6b94ca90d395c436c Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 21 Jul 2017 11:11:17 +0100 Subject: [PATCH 094/224] Ensure we have a http URL for file path replacements Closes #16179 --- includes/class-wc-download-handler.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-download-handler.php b/includes/class-wc-download-handler.php index 3f109521d7f..dfd9c8d99c2 100644 --- a/includes/class-wc-download-handler.php +++ b/includes/class-wc-download-handler.php @@ -189,13 +189,17 @@ class WC_Download_Handler { $wp_uploads_dir = $wp_uploads['basedir']; $wp_uploads_url = $wp_uploads['baseurl']; - // Replace uploads dir, site url etc with absolute counterparts if we can + /** + * Replace uploads dir, site url etc with absolute counterparts if we can. + * Note the str_replace on site_url is on purpose, so if https is forced + * via filters we can still do the string replacement on a HTTP file. + */ $replacements = array( - $wp_uploads_url => $wp_uploads_dir, - network_site_url( '/', 'https' ) => ABSPATH, - network_site_url( '/', 'http' ) => ABSPATH, - site_url( '/', 'https' ) => ABSPATH, - site_url( '/', 'http' ) => ABSPATH, + $wp_uploads_url => $wp_uploads_dir, + network_site_url( '/', 'https' ) => ABSPATH, + str_replace( 'https:', 'http:', network_site_url( '/', 'http' ) ) => ABSPATH, + site_url( '/', 'https' ) => ABSPATH, + str_replace( 'https:', 'http:', site_url( '/', 'http' ) ) => ABSPATH, ); $file_path = str_replace( array_keys( $replacements ), array_values( $replacements ), $file_path ); From 83b60573fa0e5643d858eff6891c69262b0a619a Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 21 Jul 2017 14:22:40 +0100 Subject: [PATCH 095/224] Framework for discount class --- includes/class-wc-discount.php | 103 +++++++++++++++++++----- tests/unit-tests/discounts/discount.php | 43 ++++++++++ 2 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 tests/unit-tests/discounts/discount.php diff --git a/includes/class-wc-discount.php b/includes/class-wc-discount.php index 49b238af53e..2c5248163a7 100644 --- a/includes/class-wc-discount.php +++ b/includes/class-wc-discount.php @@ -1,4 +1,8 @@ '', - 'discount' => 0, + 'amount' => 0, + 'discount' => 0, + 'discount_type' => 'fixed_cart', ); /** - * Coupon ID. + * Checks the coupon type. + * @param string $type Array or string of types + * @return bool + */ + public function is_type( $type ) { + return ( $this->get_discount_type() === $type || ( is_array( $type ) && in_array( $this->get_discount_type(), $type ) ) ); + } + + /** + * Prefix for action and filter hooks on data. + * + * @return string + */ + protected function get_hook_prefix() { + return 'woocommerce_discount_get_'; + } + + /** + * Returns the ID of this dicount. + * + * @return string + */ + public function get_id() { + return $this->id; + } + + /** + * Discount ID. * * @param string $id */ public function set_id( $id ) { + $this->id = $id; + } + /** + * Get discount amount. + * + * @param string $context + * @return float + */ + public function get_amount( $context = 'view' ) { + return $this->get_prop( 'amount', $context ); } /** * Discount amount - either fixed or percentage. * - * @param string $amount + * @param string $raw_amount */ - public function set_amount( $amount ) { - + public function set_amount( $raw_amount ) { + if ( strstr( $raw_amount, '%' ) ) { + $amount = absint( rtrim( $raw_amount, '%' ) ); + $this->set_prop( 'amount', $amount ); + $this->set_discount_type( 'percent' ); + } else { + $this->set_prop( 'amount', wc_format_decimal( $amount ) ); + } } /** - * Amount of discount this has given in total. + * Get discount type. + * + * @param string $context + * @return string */ - public function set_discount_total() { - + public function get_discount_type( $context = 'view' ) { + return $this->get_prop( 'discount_type', $context ); } /** - * Array of negative taxes. + * Set discount type. + * + * @param string $discount_type + * @throws WC_Data_Exception */ - public function set_taxes() { - + public function set_discount_type( $discount_type ) { + if ( ! in_array( $discount_type, array( 'percent', 'fixed_cart' ) ) { + $this->error( 'coupon_invalid_discount_type', __( 'Invalid discount type', 'woocommerce' ) ); + } + $this->set_prop( 'discount_type', $discount_type ); } + /** + * Amount of discount this has given in total. @todo should this be here? + */ + public function set_discount_total() {} + + /** + * Array of negative taxes. @todo should this be here? + */ + public function set_taxes() {} + /** * Calculates the amount of negative tax to apply for this discount, since * discounts are applied before tax. @@ -68,10 +131,8 @@ class WC_Discount { * For fixed discounts, the taxes are calculated proportionally so the * discount is fairly split between items. * - * @return [type] [description] + * @todo Should this bere here? */ - public function calculate_negative_taxes() { - - } + public function calculate_negative_taxes() {} } diff --git a/tests/unit-tests/discounts/discount.php b/tests/unit-tests/discounts/discount.php new file mode 100644 index 00000000000..6ce0f5140b4 --- /dev/null +++ b/tests/unit-tests/discounts/discount.php @@ -0,0 +1,43 @@ + Date: Fri, 21 Jul 2017 14:23:21 +0100 Subject: [PATCH 096/224] Add custom attributes into radio fields for woocommerce_form_field --- .gitattributes | 1 + includes/wc-template-functions.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 5e8686b5f3a..522d014b522 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,4 @@ phpcs.ruleset.xml export-ignore phpunit.* export-ignore README.md export-ignore tests export-ignore +* text eol=lf diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index 51d8ed192d1..3eb519a97b5 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -2102,7 +2102,7 @@ if ( ! function_exists( 'woocommerce_form_field' ) ) { if ( ! empty( $args['options'] ) ) { foreach ( $args['options'] as $option_key => $option_text ) { - $field .= ''; + $field .= ''; $field .= ''; } } From a82181f435a9b9f25fda618d4602e21ee2036bed Mon Sep 17 00:00:00 2001 From: Paul Robinson Date: Fri, 21 Jul 2017 14:24:53 +0100 Subject: [PATCH 097/224] reset gitattributes file --- .gitattributes | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 522d014b522..5e8686b5f3a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,4 +8,3 @@ phpcs.ruleset.xml export-ignore phpunit.* export-ignore README.md export-ignore tests export-ignore -* text eol=lf From 45258a7e021416c38fb831df12624f4173f676fc Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 21 Jul 2017 16:49:52 +0100 Subject: [PATCH 098/224] Started adding totals class from https://github.com/woocommerce/woocommerce/pull/11889/files --- includes/class-wc-totals.php | 663 +++++++++++++++++++++++++++++++++++ 1 file changed, 663 insertions(+) create mode 100644 includes/class-wc-totals.php diff --git a/includes/class-wc-totals.php b/includes/class-wc-totals.php new file mode 100644 index 00000000000..076a7706e61 --- /dev/null +++ b/includes/class-wc-totals.php @@ -0,0 +1,663 @@ +precision = pow( 10, wc_get_price_decimals() ); + $this->set_totals( $this->get_default_totals() ); + } + + /** + * Remove precision from a price. + * + * @param int $value + * @return float + */ + protected function remove_precision( $value ) { + return wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); + } + + /** + * Get default totals. + * @return array + */ + protected function get_default_totals() { + return array( + 'fees_total' => 0, + 'fees_total_tax' => 0, + 'items_subtotal' => 0, + 'items_subtotal_tax' => 0, + 'items_total' => 0, + 'items_total_tax' => 0, + 'item_totals' => array(), + 'total' => 0, + 'taxes' => array(), + 'tax_total' => 0, + 'shipping_total' => 0, + 'shipping_tax_total' => 0, + ); + } + + /** + * Get default blank set of props used per item. + * @return array + */ + protected function get_default_item_props() { + return (object) array( + 'price_includes_tax' => wc_prices_include_tax(), + 'subtotal' => 0, + 'subtotal_tax' => 0, + 'subtotal_taxes' => array(), + 'total' => 0, + 'total_tax' => 0, + 'taxes' => array(), + 'discounted_price' => 0, + ); + } + + /** + * Get default blank set of props used per coupon. + * @return array + */ + protected function get_default_coupon_props() { + return (object) array( + 'count' => 0, + 'total' => 0, + 'total_tax' => 0, + ); + } + + /** + * Get default blank set of props used per fee. + * @return array + */ + protected function get_default_fee_props() { + return (object) array( + 'total_tax' => 0, + 'taxes' => array(), + ); + } + + /** + * Get default blank set of props used per shipping row. + * @return array + */ + protected function get_default_shipping_props() { + return (object) array( + 'total' => 0, + 'total_tax' => 0, + 'taxes' => array(), + ); + } + + /** + * Sort items by the subtotal. + * + * @param object $a + * @param object $b + * @return int + */ + protected function sort_items_callback( $a, $b ) { + $b->subtotal = isset( $a->subtotal ) ? $a->subtotal : 0; + $b->subtotal = isset( $b->subtotal ) ? $b->subtotal : 0; + return $b->subtotal === $b->subtotal ? 0 : ( $b->subtotal < $b->subtotal ? 1 : -1 ); + } + + /** + * Only ran if woocommerce_adjust_non_base_location_prices is true. + * + * If the customer is outside of the base location, this removes the base + * taxes. This is off by default unless the filter is used. + */ + protected function adjust_non_base_location_price( $item ) { + $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->tax_class ); + $item_tax_rates = $this->get_item_tax_rates( $item ); + + if ( $item_tax_rates !== $base_tax_rates ) { + // Work out a new base price without the shop's base tax + $taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true, true ); + + // Now we have a new item price (excluding TAX) + $item->price = $item->price - array_sum( $taxes ); + $item->price_includes_tax = false; + } + return $item; + } + + /* + |-------------------------------------------------------------------------- + | Calculation methods. + |-------------------------------------------------------------------------- + */ + + /** + * Run all calculations methods on the given items. + */ + public function calculate() { + $this->calculate_item_subtotals(); + $this->calculate_item_totals(); + $this->calculate_fee_totals(); + $this->calculate_shipping_totals(); + $this->calculate_totals(); + } + + /** + * Subtotals are costs before discounts. + * + * To prevent rounding issues we need to work with the inclusive price where possible. + * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would. + * be 8.325 leading to totals being 1p off. + * + * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated. + * afterwards. + * + * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that. + */ + protected function calculate_item_subtotals() { + foreach ( $this->items as $item ) { + $item->subtotal = $item->price * $item->quantity; + $item->subtotal_tax = 0; + + if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { + $item = $this->adjust_non_base_location_price( $item ); + $item->subtotal = $item->price * $item->quantity; + } + + if ( $this->wc_tax_enabled() && $item->product->is_taxable() ) { + $item->subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $this->get_item_tax_rates( $item ), $item->price_includes_tax ); + $item->subtotal_tax = array_sum( $item->subtotal_taxes ); + + if ( $item->price_includes_tax ) { + $item->subtotal = $item->subtotal - $item->subtotal_tax; + } + } + } + uasort( $this->items, array( $this, 'sort_items_callback' ) ); + $this->set_items_subtotal( array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) ); + $this->set_items_subtotal_tax( array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) ); + } + + /** + * Totals are costs after discounts. + */ + public function calculate_item_totals() { + foreach ( $this->items as $item ) { + + + + // ! @todo + //$item->discounted_price = $this->get_discounted_price( $item ); + + + + + + + $item->total = $item->discounted_price * $item->quantity; + $item->total_tax = 0; + + if ( $this->wc_tax_enabled() && $item->product->is_taxable() ) { + $item->taxes = WC_Tax::calc_tax( $item->total, $this->get_item_tax_rates( $item ), $item->price_includes_tax ); + $item->total_tax = array_sum( $item->taxes ); + + if ( $item->price_includes_tax ) { + $item->total = $item->total - $item->total_tax; + } else { + $item->total = $item->total; + } + } + } + $this->set_items_total( array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) ); + $this->set_items_total_tax( array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) ); + //$this->set_coupon_totals( wp_list_pluck( $this->coupons, 'total' ) ); + //$this->set_coupon_tax_totals( wp_list_pluck( $this->coupons, 'total_tax' ) ); + //$this->set_coupon_counts( wp_list_pluck( $this->coupons, 'count' ) ); + //$this->set_item_totals( $this->items ); + } + + /** + * Calculate any fees taxes. + */ + protected function calculate_fee_totals() { + if ( ! empty( $this->fees ) ) { + foreach ( $this->fees as $fee_key => $fee ) { + if ( $this->wc_tax_enabled() && $fee->taxable ) { + $fee->taxes = WC_Tax::calc_tax( $fee->total, $tax_rates, false ); + $fee->total_tax = array_sum( $fee->taxes ); + } + } + } + $this->set_fees_total( array_sum( array_values( wp_list_pluck( $this->fees, 'total' ) ) ) ); + $this->set_fees_total_tax( array_sum( array_values( wp_list_pluck( $this->fees, 'total_tax' ) ) ) ); + } + + /** + * Calculate any shipping taxes. + */ + protected function calculate_shipping_totals() { + + } + + /** + * Main cart totals. + */ + public function calculate_totals() { + + $this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping_lines, 'total' ) ) ) ); + $this->set_taxes( $this->get_merged_taxes() ); + + // Total up/round taxes and shipping taxes + if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) { + $this->set_tax_total( WC_Tax::get_tax_total( wc_list_pluck( $this->get_taxes(), 'get_tax_total' ) ) ); + $this->set_shipping_tax_total( WC_Tax::get_tax_total( wc_list_pluck( $this->get_taxes(), 'get_shipping_tax_total' ) ) ); + } else { + $this->set_tax_total( array_sum( wc_list_pluck( $this->get_taxes(), 'get_tax_total' ) ) ); + $this->set_shipping_tax_total( array_sum( wc_list_pluck( $this->get_taxes(), 'get_shipping_tax_total' ) ) ); + } + + // Allow plugins to hook and alter totals before final total is calculated + do_action( 'woocommerce_calculate_totals', WC()->cart ); + + // Grand Total - Discounted product prices, discounted tax, shipping cost + tax + $this->set_total( apply_filters( 'woocommerce_calculated_total', round( $this->get_items_total() + $this->get_fees_total() + $this->get_shipping_total() + $this->get_tax_total() + $this->get_shipping_tax_total(), wc_get_price_decimals() ), WC()->cart ) ); + } + + /* + |-------------------------------------------------------------------------- + | Setters. + |-------------------------------------------------------------------------- + */ + + /** + * Set all totals. + * @param array $value + */ + public function set_totals( $value ) { + $value = wp_parse_args( $value, $this->get_default_totals() ); + $this->totals = $value; + } + + /** + * Set cart/order items which will be discounted. + * + * @since 3.2.0 + * @param array $raw_items List of raw cart or order items. + */ + public function set_items( $raw_items ) { + $this->items = array(); + + if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { + foreach ( $raw_items as $raw_item ) { + $item = (object) array( + 'price' => 0, // Line price without discounts, in cents. + 'quantity' => 0, // Line qty. + 'product' => false, + ); + if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { + //$item->quantity = $raw_item->get_quantity(); + //$item->price = $raw_item->get_price() * $raw_item->get_quantity(); + //$item->is_taxable = $raw_item->is_taxable(); + //$item->tax_class = $raw_item->get_tax_class(); + // @todo + } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { + $item->key = $raw_item->get_id(); + $item->quantity = $raw_item->get_quantity(); + $item->price = $raw_item->get_subtotal() * $this->precision; + $item->product = $raw_item->get_product(); + } else { + $item->key = $raw_item['key']; + $item->quantity = $raw_item['quantity']; + $item->price = $raw_item['data']->get_price() * $this->precision * $raw_item['quantity']; + $item->product = $raw_item['data']; + } + $this->items[ $item->key ] = $item; + } + } + } + + /** + * Set coupons. + * @param array $coupons + */ + public function set_coupons( $coupons ) { + foreach ( $coupons as $code => $coupon_object ) { + $coupon = $this->get_default_coupon_props(); + $coupon->coupon = $coupon_object; + $this->coupons[ $code ] = $coupon; + } + } + + /** + * Set fees. + * @param array $fees + */ + public function set_fees( $fees ) { + foreach ( $fees as $fee_key => $fee_object ) { + $fee = $this->get_default_fee_props(); + $fee->total = $fee_object->amount; + $fee->taxable = $fee_object->taxable; + $fee->tax_class = $fee_object->tax_class; + $this->fees[ $fee_key ] = $fee; + } + } + + /** + * Set shipping lines. + * @param array + */ + public function set_shipping( $shipping_objects ) { + $this->shipping_lines = array(); + + if ( is_array( $shipping_objects ) ) { + foreach ( $shipping_objects as $key => $shipping_object ) { + $shipping = $this->get_default_shipping_props(); + $shipping->total = $shipping_object->cost; + $shipping->taxes = $shipping_object->taxes; + $shipping->total_tax = array_sum( $shipping_object->taxes ); + $this->shipping_lines[ $key ] = $shipping; + } + } + } + + /** + * Set taxes. + * @param array $value + */ + protected function set_taxes( $value ) { + $this->totals['taxes'] = $value; + } + + /** + * Set tax total. + * @param float $value + */ + protected function set_tax_total( $value ) { + $this->totals['tax_total'] = $value; + } + + /** + * Set shipping total. + * @param float $value + */ + protected function set_shipping_total( $value ) { + $this->totals['shipping_total'] = $value; + } + + /** + * Set shipping tax total. + * @param float $value + */ + protected function set_shipping_tax_total( $value ) { + $this->totals['shipping_tax_total'] = $value; + } + + /** + * Set item totals. + * @param array $value + */ + protected function set_item_totals( $value ) { + $this->totals['item_totals'] = $value; + } + + /** + * Set items subtotal. + * @param float $value + */ + protected function set_items_subtotal( $value ) { + $this->totals['items_subtotal'] = $value; + } + + /** + * Set items subtotal tax. + * @param float $value + */ + protected function set_items_subtotal_tax( $value ) { + $this->totals['items_subtotal_tax'] = $value; + } + + /** + * Set items total. + * @param float $value + */ + protected function set_items_total( $value ) { + $this->totals['items_total'] = $value; + } + + /** + * Set items total tax. + * @param float $value + */ + protected function set_items_total_tax( $value ) { + $this->totals['items_total_tax'] = $value; + } + + /** + * Set fees total. + * @param float $value + */ + protected function set_fees_total( $value ) { + $this->totals['fees_total'] = $value; + } + + /** + * Set fees total tax. + * @param float $value + */ + protected function set_fees_total_tax( $value ) { + $this->totals['fees_total_tax'] = $value; + } + + /** + * Set total. + * @param float $value + */ + protected function set_total( $value ) { + $this->totals['total'] = max( 0, $value ); + } + + /* + |-------------------------------------------------------------------------- + | Getters. + |-------------------------------------------------------------------------- + */ + + /** + * Get all totals. + * @return array. + */ + public function get_totals() { + return $this->totals; + } + + /** + * Get shipping and item taxes. + * @return array + */ + public function get_taxes() { + return $this->totals['taxes']; + } + + /** + * Get tax total. + * @return float + */ + public function get_tax_total() { + return $this->totals['tax_total']; + } + + /** + * Get shipping total. + * @return float + */ + public function get_shipping_total() { + return $this->totals['shipping_total']; + } + + /** + * Get shipping tax total. + * @return float + */ + public function get_shipping_tax_total() { + return $this->totals['shipping_tax_total']; + } + + /** + * Get the items subtotal. + * @return float + */ + public function get_items_subtotal() { + return $this->totals['items_subtotal']; + } + + /** + * Get the items subtotal tax. + * @return float + */ + public function get_items_subtotal_tax() { + return $this->totals['items_subtotal_tax']; + } + + /** + * Get the items total. + * @return float + */ + public function get_items_total() { + return $this->totals['items_total']; + } + + /** + * Get the items total tax. + * @return float + */ + public function get_items_total_tax() { + return $this->totals['items_total_tax']; + } + + /** + * Get the total fees amount. + * @return float + */ + public function get_fees_total() { + return $this->totals['fees_total']; + } + + /** + * Get the total fee tax amount. + * @return float + */ + public function get_fees_total_tax() { + return $this->totals['fees_total_tax']; + } + + /** + * Get the total. + * @return float + */ + public function get_total() { + return $this->totals['total']; + } + + /** + * Returns an array of item totals. + * @return array + */ + public function get_item_totals() { + return $this->totals['item_totals']; + } + + /** + * Get tax rates for an item. Caches rates in class to avoid multiple look ups. + * @param object $item + * @return array of taxes + */ + protected function get_item_tax_rates( $item ) { + $tax_class = $item->product->get_tax_class(); + return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class() ); + } + + /** + * Get all tax rows for a set of items and shipping methods. + * @return array + */ + protected function get_merged_taxes() { + $taxes = array(); + + foreach ( $this->items as $item ) { + foreach ( $item->taxes as $rate_id => $rate ) { + if ( ! isset( $taxes[ $rate_id ] ) ) { + $taxes[ $rate_id ] = new WC_Item_Tax(); + } + $taxes[ $rate_id ]->set_rate( $rate_id ); + $taxes[ $rate_id ]->set_tax_total( $taxes[ $rate_id ]->get_tax_total() + $rate ); + } + } + + foreach ( $this->shipping_lines as $item ) { + foreach ( $item->taxes as $rate_id => $rate ) { + if ( ! isset( $taxes[ $rate_id ] ) ) { + $taxes[ $rate_id ] = new WC_Item_Tax(); + } + $taxes[ $rate_id ]->set_rate( $rate_id ); + $taxes[ $rate_id ]->set_shipping_tax_total( $taxes[ $rate_id ]->get_shipping_tax_total() + $rate ); + } + } + + return $taxes; + } +} From c5055ed2f76a103d9bdbf1b0d46e1909db3bf0b5 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Fri, 21 Jul 2017 14:25:27 -0300 Subject: [PATCH 099/224] Tidy up Scrutinizer, PHPUnit and Travis config --- .scrutinizer.yml | 70 ++++++++++++++++++++++++------------------------ .travis.yml | 2 +- phpunit.xml | 7 +++-- phpunit.xml.dist | 40 --------------------------- 4 files changed, 39 insertions(+), 80 deletions(-) delete mode 100644 phpunit.xml.dist diff --git a/.scrutinizer.yml b/.scrutinizer.yml index f3ec5117a1b..0cf3c219ad0 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,58 +1,58 @@ build: environment: - php: '7.1' + php: "7.1" mysql: true postgresql: false redis: false apache2: - modules: ['rewrite'] + modules: + - rewrite variables: - WP_VERSION: 'latest' + WP_VERSION: latest dependencies: before: - - bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION + - "bash tests/bin/install.sh woocommerce_test root '' localhost $WP_VERSION" tests: override: - - - command: 'vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover=results' - coverage: - file: 'results' - format: 'clover' - + - + command: "vendor/bin/phpunit -c phpunit.xml --coverage-clover=results" + coverage: + file: results + format: clover tools: - sensiolabs_security_checker: true php_code_sniffer: config: standard: WordPress - + sensiolabs_security_checker: true checks: php: + avoid_closing_tag: false + avoid_superglobals: false + coding_standard: + name: WordPress + no_exit: false + no_global_keyword: false + one_class_per_file: false + psr2_class_declaration: false + psr2_control_structure_declaration: false + psr2_switch_declaration: false variable_existence: false verify_access_scope_valid: false verify_argument_usable_as_reference: false verify_property_names: false - no_global_keyword: false - psr2_switch_declaration: false - psr2_control_structure_declaration: false - psr2_class_declaration: false - one_class_per_file: false - no_exit: false - avoid_superglobals: false - avoid_closing_tag: false - coding_standard: - name: WordPress - filter: excluded_paths: - - tests/* - - apigen/* - - dummy-data/* - - i18n/* - - includes/api/legacy/* - - includes/legacy/* - - includes/libraries/* - - includes/updates/* - - includes/gateways/simplify-commerce/* - - includes/shipping/legacy-* - - includes/wc-deprecated-functions.php - - includes/class-wc-legacy-api.php + - apigen/ + - dummy-data/ + - i18n/ + - includes/api/legacy/ + - includes/class-wc-legacy-api.php + - includes/gateways/simplify-commerce-deprecated/ + - includes/gateways/simplify-commerce/includes/ + - includes/legacy/ + - includes/libraries/ + - includes/shipping/legacy-* + - includes/updates/ + - includes/vendor/ + - includes/wc-deprecated-functions.php + - tests/ diff --git a/.travis.yml b/.travis.yml index 3733acf8f38..387f2e67ba1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_script: - bash tests/bin/travis.sh before script: - - phpunit -c phpunit.xml.dist + - phpunit -c phpunit.xml after_script: - bash tests/bin/travis.sh after diff --git a/phpunit.xml b/phpunit.xml index 0a829bc9242..f7c911553a9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -30,14 +30,13 @@ ./includes/shipping/legacy-local-delivery/ ./includes/shipping/legacy-local-pickup/ ./includes/updates/ + ./includes/vendor/ ./includes/widgets/ ./templates/ - ./tests/ + ./tests/ ./tmp/ + ./vendor/ - - - diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 0ae53994749..00000000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - ./tests/unit-tests - - - - - . - - ./apigen/ - ./i18n/ - ./includes/api/legacy/ - ./includes/gateways/simplify-commerce-deprecated/ - ./includes/gateways/simplify-commerce/includes/ - ./includes/libraries/ - ./includes/shipping/legacy-flat-rate/ - ./includes/shipping/legacy-free-shipping/ - ./includes/shipping/legacy-international-delivery/ - ./includes/shipping/legacy-local-delivery/ - ./includes/shipping/legacy-local-pickup/ - ./includes/updates/ - ./includes/widgets/ - ./templates/ - ./tests/ - ./tmp/ - - - - From 45a9826a804967655e0c0d4481c6ba2a22b210c2 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Sun, 23 Jul 2017 12:05:11 +0100 Subject: [PATCH 100/224] Totals class and tests files --- includes/class-wc-totals.php | 105 +++++++++++++++-------------- includes/class-woocommerce.php | 1 + tests/unit-tests/totals/totals.php | 102 ++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 49 deletions(-) create mode 100644 tests/unit-tests/totals/totals.php diff --git a/includes/class-wc-totals.php b/includes/class-wc-totals.php index 076a7706e61..909dcf361f7 100644 --- a/includes/class-wc-totals.php +++ b/includes/class-wc-totals.php @@ -18,35 +18,55 @@ class WC_Totals { * * @var array */ - protected $items = array(); + protected $items = array(); /** * Coupons to calculate. * * @var array */ - protected $coupons = array(); + protected $coupons = array(); + + /** + * Discounts to calculate. + * + * @var array + */ + protected $discounts = array(); /** * Fees to calculate. * * @var array */ - protected $fees = array(); + protected $fees = array(); /** * Shipping to calculate. * * @var array */ - protected $shipping_lines = array(); + protected $shipping = array(); /** * Stores totals. * * @var array */ - protected $totals = null; + protected $totals = array( + 'fees_total' => 0, + 'fees_total_tax' => 0, + 'items_subtotal' => 0, + 'items_subtotal_tax' => 0, + 'items_total' => 0, + 'items_total_tax' => 0, + 'item_totals' => array(), + 'total' => 0, + 'taxes' => array(), + 'tax_total' => 0, + 'shipping_total' => 0, + 'shipping_tax_total' => 0, + ); /** * Precision so we can work in cents. @@ -56,11 +76,21 @@ class WC_Totals { protected $precision = 1; /** - * Constructor. + * Sets up the items provided, and calculate totals. */ - public function __construct() { + public function __construct( $cart = null ) { $this->precision = pow( 10, wc_get_price_decimals() ); - $this->set_totals( $this->get_default_totals() ); + $this->set_cart( $cart ); + $this->calculate(); + } + + /** + * Hadnles a cart or order object passed in for calculation. Normalises data. + */ + protected function set_cart( $cart ) { + if ( is_a( $cart, 'WC_Cart' ) ) { + + } } /** @@ -73,27 +103,6 @@ class WC_Totals { return wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); } - /** - * Get default totals. - * @return array - */ - protected function get_default_totals() { - return array( - 'fees_total' => 0, - 'fees_total_tax' => 0, - 'items_subtotal' => 0, - 'items_subtotal_tax' => 0, - 'items_total' => 0, - 'items_total_tax' => 0, - 'item_totals' => array(), - 'total' => 0, - 'taxes' => array(), - 'tax_total' => 0, - 'shipping_total' => 0, - 'shipping_tax_total' => 0, - ); - } - /** * Get default blank set of props used per item. * @return array @@ -120,6 +129,7 @@ class WC_Totals { 'count' => 0, 'total' => 0, 'total_tax' => 0, + 'object' => false, ); } @@ -188,8 +198,14 @@ class WC_Totals { /** * Run all calculations methods on the given items. + * + * @uses WC_Totals::calculate_item_subtotals + * @uses WC_Totals::calculate_item_totals + * @uses WC_Totals::calculate_fee_totals + * @uses WC_Totals::calculate_shipping_totals + * @uses WC_Totals::calculate_totals */ - public function calculate() { + protected function calculate() { $this->calculate_item_subtotals(); $this->calculate_item_totals(); $this->calculate_fee_totals(); @@ -236,7 +252,7 @@ class WC_Totals { /** * Totals are costs after discounts. */ - public function calculate_item_totals() { + protected function calculate_item_totals() { foreach ( $this->items as $item ) { @@ -297,9 +313,9 @@ class WC_Totals { /** * Main cart totals. */ - public function calculate_totals() { + protected function calculate_totals() { - $this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping_lines, 'total' ) ) ) ); + $this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping, 'total' ) ) ) ); $this->set_taxes( $this->get_merged_taxes() ); // Total up/round taxes and shipping taxes @@ -324,22 +340,13 @@ class WC_Totals { |-------------------------------------------------------------------------- */ - /** - * Set all totals. - * @param array $value - */ - public function set_totals( $value ) { - $value = wp_parse_args( $value, $this->get_default_totals() ); - $this->totals = $value; - } - /** * Set cart/order items which will be discounted. * * @since 3.2.0 * @param array $raw_items List of raw cart or order items. */ - public function set_items( $raw_items ) { + protected function set_items( $raw_items ) { $this->items = array(); if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { @@ -375,7 +382,7 @@ class WC_Totals { * Set coupons. * @param array $coupons */ - public function set_coupons( $coupons ) { + protected function set_coupons( $coupons ) { foreach ( $coupons as $code => $coupon_object ) { $coupon = $this->get_default_coupon_props(); $coupon->coupon = $coupon_object; @@ -387,7 +394,7 @@ class WC_Totals { * Set fees. * @param array $fees */ - public function set_fees( $fees ) { + protected function set_fees( $fees ) { foreach ( $fees as $fee_key => $fee_object ) { $fee = $this->get_default_fee_props(); $fee->total = $fee_object->amount; @@ -401,8 +408,8 @@ class WC_Totals { * Set shipping lines. * @param array */ - public function set_shipping( $shipping_objects ) { - $this->shipping_lines = array(); + protected function set_shipping( $shipping_objects ) { + $this->shipping = array(); if ( is_array( $shipping_objects ) ) { foreach ( $shipping_objects as $key => $shipping_object ) { @@ -410,7 +417,7 @@ class WC_Totals { $shipping->total = $shipping_object->cost; $shipping->taxes = $shipping_object->taxes; $shipping->total_tax = array_sum( $shipping_object->taxes ); - $this->shipping_lines[ $key ] = $shipping; + $this->shipping[ $key ] = $shipping; } } } @@ -648,7 +655,7 @@ class WC_Totals { } } - foreach ( $this->shipping_lines as $item ) { + foreach ( $this->shipping as $item ) { foreach ( $item->taxes as $rate_id => $rate ) { if ( ! isset( $taxes[ $rate_id ] ) ) { $taxes[ $rate_id ] = new WC_Item_Tax(); diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 58876dd9f54..3667641f93e 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -334,6 +334,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-deprecated-filter-hooks.php' ); include_once( WC_ABSPATH . 'includes/class-wc-background-emailer.php' ); include_once( WC_ABSPATH . 'includes/class-wc-discounts.php' ); + include_once( WC_ABSPATH . 'includes/class-wc-totals.php' ); /** * Data stores - used to store and retrieve CRUD object data from the database. diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php new file mode 100644 index 00000000000..5b724f9ca70 --- /dev/null +++ b/tests/unit-tests/totals/totals.php @@ -0,0 +1,102 @@ + '', + 'tax_rate_state' => '', + 'tax_rate' => '20.0000', + 'tax_rate_name' => 'VAT', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => '', + ); + $tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate ); + update_option( 'woocommerce_calc_taxes', 'yes' ); + + $product = WC_Helper_Product::create_simple_product(); + $product2 = WC_Helper_Product::create_simple_product(); + + $coupon = new WC_Coupon; + $coupon->set_code( 'test-coupon-10' ); + $coupon->set_amount( 10 ); + $coupon->set_discount_type( 'percent' ); + $coupon->save(); + + $this->tax_rate_ids[] = $tax_rate_id; + $this->products[] = $product; + $this->products[] = $product2; + $this->coupons[] = $coupon; + + WC()->cart->add_to_cart( $product->get_id(), 1 ); + WC()->cart->add_to_cart( $product2->get_id(), 2 ); + WC()->cart->add_fee( "test fee", 10, true ); + WC()->cart->add_fee( "test fee 2", 20, true ); + WC()->cart->add_fee( "test fee non-taxable", 10, false ); + WC()->cart->add_discount( 'test-coupon-10' ); + + // @todo manual discounts + $this->totals = new WC_Totals( WC()->cart ); + } + + /** + * Clean up after test. + */ + public function tearDown() { + WC()->cart->empty_cart(); + update_option( 'woocommerce_calc_taxes', 'no' ); + + foreach ( $this->products as $product ) { + $product->delete( true ); + } + + foreach ( $this->coupons as $coupon ) { + $coupon->delete( true ); + } + + foreach ( $this->tax_rate_ids as $tax_rate_id ) { + WC_Tax::_delete_tax_rate( $tax_rate_id ); + } + } + + /** + * Test get and set items. + */ + public function test_get_totals() { + $this->assertEquals( array(), $this->totals->get_totals() ); + } + + /** + * Test get and set items. + */ + public function test_get_taxes() { + + } + + /** + * Test get and set items. + */ + public function test_get_tax_total() { + + } + + //... @todo test all public methods +} From 504a2165828c0e54ee6264a320e6d88671013a27 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 24 Jul 2017 10:43:34 +0100 Subject: [PATCH 101/224] Check data is an array in set_variation method Closes #16232 --- includes/class-wc-order-item-product.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/includes/class-wc-order-item-product.php b/includes/class-wc-order-item-product.php index 2cf90dfb801..6a08d298170 100644 --- a/includes/class-wc-order-item-product.php +++ b/includes/class-wc-order-item-product.php @@ -165,9 +165,11 @@ class WC_Order_Item_Product extends WC_Order_Item { * * @param array $data Key/Value pairs */ - public function set_variation( $data ) { - foreach ( $data as $key => $value ) { - $this->add_meta_data( str_replace( 'attribute_', '', $key ), $value, true ); + public function set_variation( $data = array() ) { + if ( is_array( $data ) ) { + foreach ( $data as $key => $value ) { + $this->add_meta_data( str_replace( 'attribute_', '', $key ), $value, true ); + } } } From a9339f60d3b964c2b172a1371849cfa8ccbdd31c Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 24 Jul 2017 13:23:38 +0100 Subject: [PATCH 102/224] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 82fafab64d4..691915e6086 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,35 +1,46 @@ + + + + + + + + ## Prerequisites - + - [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate -- [ ] The issue still exists against the latest `master` branch of WooCommerce -- [ ] This is not a usage question (Those should be directed to the [community](https://wordpress.org/support/plugin/woocommerce), unless this is a question about a premium plugin in which you should [use the helpdesk](https://woocommerce.com/my-account/tickets/) for official extensions or contact the author of 3rd party extensions) +- [ ] The issue still exists against the latest `master` branch of WooCommerce on Github - [ ] I have attempted to find the simplest possible steps to reproduce the issue - [ ] I have included a failing test as a pull request (Optional) ## Steps to reproduce the issue + + 1. 2. 3. -## Expected behavior and actual behavior +## Expected/actual behavior When I follow those steps, I see... -I was expecting... - -## Environment - -
    -``` -Grab the system status report from WooCommerce > System Status and paste it here. -``` -
    +I was expecting to see... ## Isolating the problem + + - [ ] This bug happens with only WooCommerce plugin active - [ ] This bug happens with a default WordPress theme active, or [Storefront](https://woocommerce.com/storefront/) -- [ ] I can reproduce this bug consistently +- [ ] I can reproduce this bug consistently using the steps above + +## WordPress Environment + +
    +``` +Copy and paste the system status report from **WooCommerce > System Status** in WordPress admin here. +``` +
    From b376b548b9c74750bf11f0d36ee199dd507e57f8 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 24 Jul 2017 13:25:23 +0100 Subject: [PATCH 103/224] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 691915e6086..518852e36a4 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -6,6 +6,8 @@ + + ## Prerequisites @@ -15,6 +17,8 @@ - [ ] I have attempted to find the simplest possible steps to reproduce the issue - [ ] I have included a failing test as a pull request (Optional) + + ## Steps to reproduce the issue @@ -23,12 +27,16 @@ 2. 3. + + ## Expected/actual behavior When I follow those steps, I see... I was expecting to see... + + ## Isolating the problem @@ -37,6 +45,8 @@ I was expecting to see... - [ ] This bug happens with a default WordPress theme active, or [Storefront](https://woocommerce.com/storefront/) - [ ] I can reproduce this bug consistently using the steps above + + ## WordPress Environment
    From 0b9b178522ceb94f64ca8b5d52a55c1d4fc5f04f Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 24 Jul 2017 13:32:45 +0100 Subject: [PATCH 104/224] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 518852e36a4..1232b9d801f 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,8 +1,8 @@ - + - + From c9d500f4db6edcb5de7bfa4799823ff8a091a53a Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 24 Jul 2017 13:34:04 +0100 Subject: [PATCH 105/224] Update ISSUE_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 1232b9d801f..673795380b0 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,6 @@ - + From ee545e77933640b6401945ed4f4bcac23f39e9f8 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 24 Jul 2017 17:21:08 +0100 Subject: [PATCH 106/224] Totals progress --- includes/class-wc-discounts.php | 44 +- includes/class-wc-totals.php | 697 ++++++++++------------------- tests/unit-tests/totals/totals.php | 34 +- 3 files changed, 256 insertions(+), 519 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 26b9b56b6d1..c08dc00ed67 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -46,8 +46,9 @@ class WC_Discounts { /** * Constructor. */ - public function __construct() { + public function __construct( $items ) { $this->precision = pow( 10, wc_get_price_decimals() ); + $this->set_items( $items ); } /** @@ -71,13 +72,13 @@ class WC_Discounts { } /** - * Get all discount totals without precision. + * Get all discount totals with precision. * * @since 3.2.0 * @return array */ public function get_discounts() { - return array_map( array( $this, 'remove_precision' ), $this->discounts ); + return $this->discounts; } /** @@ -117,42 +118,19 @@ class WC_Discounts { * Set cart/order items which will be discounted. * * @since 3.2.0 - * @param array $raw_items List of raw cart or order items. + * @param array $items List items, normailised, by WC_Totals. */ - public function set_items( $raw_items ) { + public function set_items( $items ) { $this->items = array(); $this->discounts = array(); $this->applied_coupons = array(); - if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { - foreach ( $raw_items as $raw_item ) { - $item = (object) array( - 'price' => 0, // Line price without discounts, in cents. - 'quantity' => 0, // Line qty. - 'product' => false, - ); - if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { - //$item->quantity = $raw_item->get_quantity(); - //$item->price = $raw_item->get_price() * $raw_item->get_quantity(); - //$item->is_taxable = $raw_item->is_taxable(); - //$item->tax_class = $raw_item->get_tax_class(); - // @todo - } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { - $item->key = $raw_item->get_id(); - $item->quantity = $raw_item->get_quantity(); - $item->price = $raw_item->get_subtotal() * $this->precision; - $item->product = $raw_item->get_product(); - } else { - $item->key = $raw_item['key']; - $item->quantity = $raw_item['quantity']; - $item->price = $raw_item['data']->get_price() * $this->precision * $raw_item['quantity']; - $item->product = $raw_item['data']; - } - $this->items[ $item->key ] = $item; - $this->discounts[ $item->key ] = 0; - } - uasort( $this->items, array( $this, 'sort_by_price' ) ); + if ( ! empty( $items ) && is_array( $items ) ) { + $this->items = $items; + $this->discounts = array_fill_keys( array_keys( $items ), 0 ); } + + uasort( $this->items, array( $this, 'sort_by_price' ) ); } /** diff --git a/includes/class-wc-totals.php b/includes/class-wc-totals.php index 909dcf361f7..7debc5e970e 100644 --- a/includes/class-wc-totals.php +++ b/includes/class-wc-totals.php @@ -13,44 +13,34 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Totals { + /** + * Reference to cart or order object. + * + * @since 3.2.0 + * @var array + */ + protected $object; + /** * Line items to calculate. * + * @since 3.2.0 * @var array */ - protected $items = array(); + protected $items = array(); // @todo ? /** - * Coupons to calculate. - * - * @var array - */ - protected $coupons = array(); - - /** - * Discounts to calculate. + * Discount amounts in cents after calculation. * + * @since 3.2.0 * @var array */ protected $discounts = array(); - /** - * Fees to calculate. - * - * @var array - */ - protected $fees = array(); - - /** - * Shipping to calculate. - * - * @var array - */ - protected $shipping = array(); - /** * Stores totals. * + * @since 3.2.0 * @var array */ protected $totals = array( @@ -60,51 +50,75 @@ class WC_Totals { 'items_subtotal_tax' => 0, 'items_total' => 0, 'items_total_tax' => 0, - 'item_totals' => array(), 'total' => 0, 'taxes' => array(), 'tax_total' => 0, 'shipping_total' => 0, 'shipping_tax_total' => 0, + 'discounts_total' => 0, + 'discounts_tax_total' => 0, ); /** * Precision so we can work in cents. * + * @since 3.2.0 * @var int */ protected $precision = 1; /** * Sets up the items provided, and calculate totals. + * + * @since 3.2.0 */ - public function __construct( $cart = null ) { + public function __construct( &$cart = null ) { $this->precision = pow( 10, wc_get_price_decimals() ); - $this->set_cart( $cart ); + $this->object = $cart; + $this->set_items(); $this->calculate(); } /** - * Hadnles a cart or order object passed in for calculation. Normalises data. + * Handles a cart or order object passed in for calculation. Normalises data. + * + * @since 3.2.0 */ - protected function set_cart( $cart ) { - if ( is_a( $cart, 'WC_Cart' ) ) { - + protected function set_items() { + if ( is_a( $this->object, 'WC_Cart' ) ) { + foreach ( $this->object->get_cart() as $cart_item_key => $cart_item ) { + $item = $this->get_default_item_props(); + $item->key = $cart_item_key; + $item->quantity = $cart_item['quantity']; + $item->price = $cart_item['data']->get_price() * $this->precision * $cart_item['quantity']; + $item->product = $cart_item['data']; + $this->items[ $cart_item_key ] = $item; + } } } /** * Remove precision from a price. * + * @since 3.2.0 * @param int $value * @return float */ protected function remove_precision( $value ) { - return wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); + if ( is_array( $value ) ) { + foreach ( $value as $key => $subvalue ) { + $value[ $key ] = $this->remove_precision( $subvalue ); + } + } else { + $value = wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); + } + return $value; } /** * Get default blank set of props used per item. + * + * @since 3.2.0 * @return array */ protected function get_default_item_props() { @@ -120,60 +134,13 @@ class WC_Totals { ); } - /** - * Get default blank set of props used per coupon. - * @return array - */ - protected function get_default_coupon_props() { - return (object) array( - 'count' => 0, - 'total' => 0, - 'total_tax' => 0, - 'object' => false, - ); - } - - /** - * Get default blank set of props used per fee. - * @return array - */ - protected function get_default_fee_props() { - return (object) array( - 'total_tax' => 0, - 'taxes' => array(), - ); - } - - /** - * Get default blank set of props used per shipping row. - * @return array - */ - protected function get_default_shipping_props() { - return (object) array( - 'total' => 0, - 'total_tax' => 0, - 'taxes' => array(), - ); - } - - /** - * Sort items by the subtotal. - * - * @param object $a - * @param object $b - * @return int - */ - protected function sort_items_callback( $a, $b ) { - $b->subtotal = isset( $a->subtotal ) ? $a->subtotal : 0; - $b->subtotal = isset( $b->subtotal ) ? $b->subtotal : 0; - return $b->subtotal === $b->subtotal ? 0 : ( $b->subtotal < $b->subtotal ? 1 : -1 ); - } - /** * Only ran if woocommerce_adjust_non_base_location_prices is true. * * If the customer is outside of the base location, this removes the base * taxes. This is off by default unless the filter is used. + * + * @since 3.2.0 */ protected function adjust_non_base_location_price( $item ) { $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->tax_class ); @@ -190,6 +157,126 @@ class WC_Totals { return $item; } + /** + * Get discounted price of an item with precision (in cents). + * + * @since 3.2.0 + * @param object $item + * @return int + */ + protected function get_discounted_price_in_cents( $item ) { + return $item->price - $this->totals['discounts'][ $item->key ]; + } + + /** + * Get tax rates for an item. Caches rates in class to avoid multiple look ups. + * + * @param object $item + * @return array of taxes + */ + protected function get_item_tax_rates( $item ) { + $tax_class = $item->product->get_tax_class(); + return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class() ); + } + + /** + * Return array of coupon objects from the cart or an order. + * + * @since 3.2.0 + * @return array + */ + protected function get_coupons() { + if ( is_a( $this->object, 'WC_Cart' ) ) { + return $this->object->get_coupons(); + } + } + + /** + * Return array of shipping costs. + * + * @since 3.2.0 + * @return array + */ + protected function get_shipping() { + // @todo get this somehow. Where does calc occur? + return array(); + } + + protected function get_discounts() { + // @todo fee style API for discounts in cart/checkout. + return array(); + } + + protected function get_fees() { + // @todo where should fee api be located? New class? + return array(); + } + + /** + * Get a single total with or without precision (in cents). + * + * @since 3.2.0 + * @param string $key Total to get. + * @param bool $in_cents + * @return int|float + */ + public function get_total( $key = 'total', $in_cents = false ) { + $totals = $this->get_totals( $in_cents ); + return isset( $totals[ $key ] ) ? $totals[ $key ] : 0; + } + + /** + * Set a single total. + * + * @since 3.2.0 + * @param string $key + * @param int $total + */ + protected function set_total( $key = 'total', $total ) { + $this->totals[ $key ] = $total; + } + + /** + * Get all totals with or without precision (in cents). + * + * @since 3.2.0 + * @param $in_cents bool + * @return array. + */ + public function get_totals( $in_cents = false ) { + return $in_cents ? $this->totals : array_map( array( $this, 'remove_precision' ), $this->totals ); + } + + /** + * Get all tax rows from items (including shipping and product line items). + * + * @todo consider an item object instead of array here + * + * @since 3.2.0 + * @return array + */ + protected function get_merged_taxes() { + $taxes = array(); + foreach ( $this->items as $item ) { + foreach ( $item->taxes as $rate_id => $rate ) { + if ( ! isset( $taxes[ $rate_id ] ) ) { + $taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 ); + } + $taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate; + } + } + + foreach ( $this->get_shipping() as $item ) { + foreach ( $item->taxes as $rate_id => $rate ) { + if ( ! isset( $taxes[ $rate_id ] ) ) { + $taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 ); + } + $taxes[ $rate_id ]['shipping_tax_total'] = $taxes[ $rate_id ]['shipping_tax_total'] + $rate; + } + } + return $taxes; + } + /* |-------------------------------------------------------------------------- | Calculation methods. @@ -199,14 +286,11 @@ class WC_Totals { /** * Run all calculations methods on the given items. * - * @uses WC_Totals::calculate_item_subtotals - * @uses WC_Totals::calculate_item_totals - * @uses WC_Totals::calculate_fee_totals - * @uses WC_Totals::calculate_shipping_totals - * @uses WC_Totals::calculate_totals + * @since 3.2.0 */ protected function calculate() { $this->calculate_item_subtotals(); + $this->calculate_discounts(); $this->calculate_item_totals(); $this->calculate_fee_totals(); $this->calculate_shipping_totals(); @@ -224,18 +308,19 @@ class WC_Totals { * afterwards. * * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that. + * + * @since 3.2.0 */ protected function calculate_item_subtotals() { foreach ( $this->items as $item ) { - $item->subtotal = $item->price * $item->quantity; - $item->subtotal_tax = 0; - if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { $item = $this->adjust_non_base_location_price( $item ); - $item->subtotal = $item->price * $item->quantity; } - if ( $this->wc_tax_enabled() && $item->product->is_taxable() ) { + $item->subtotal = $item->price; + $item->subtotal_tax = 0; + + if ( wc_tax_enabled() && $item->product->is_taxable() ) { $item->subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $this->get_item_tax_rates( $item ), $item->price_includes_tax ); $item->subtotal_tax = array_sum( $item->subtotal_taxes ); @@ -244,31 +329,47 @@ class WC_Totals { } } } - uasort( $this->items, array( $this, 'sort_items_callback' ) ); - $this->set_items_subtotal( array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) ); - $this->set_items_subtotal_tax( array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) ); + $this->set_total( 'items_subtotal', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) ); + $this->set_total( 'items_subtotal_tax', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) ); + } + + /** + * Calculate all discount and coupon amounts. + * + * @since 3.2.0 + * @uses WC_Discounts class. + */ + protected function calculate_discounts() { + $discounts = new WC_Discounts( $this->items ); + + foreach ( $this->get_coupons() as $coupon ) { + $discounts->apply_coupon( $coupon ); + } + + foreach ( $this->get_discounts() as $discount ) { + //$discounts->apply_discount( $coupon ); @todo + } + + $this->totals['discounts'] = $discounts->get_discounts(); + $this->totals['discounts_total'] = array_sum( $this->totals['discounts'] ); + // $this->totals['discounts_tax_total'] = $value; + + /*$this->set_coupon_totals( wp_list_pluck( $this->coupons, 'total' ) ); + //$this->set_coupon_tax_totals( wp_list_pluck( $this->coupons, 'total_tax' ) ); + //$this->set_coupon_counts( wp_list_pluck( $this->coupons, 'count' ) );*/ } /** * Totals are costs after discounts. + * + * @since 3.2.0 */ protected function calculate_item_totals() { foreach ( $this->items as $item ) { + $item->total = $this->get_discounted_price_in_cents( $item ); + $item->total_tax = 0; - - - // ! @todo - //$item->discounted_price = $this->get_discounted_price( $item ); - - - - - - - $item->total = $item->discounted_price * $item->quantity; - $item->total_tax = 0; - - if ( $this->wc_tax_enabled() && $item->product->is_taxable() ) { + if ( wc_tax_enabled() && $item->product->is_taxable() ) { $item->taxes = WC_Tax::calc_tax( $item->total, $this->get_item_tax_rates( $item ), $item->price_includes_tax ); $item->total_tax = array_sum( $item->taxes ); @@ -279,392 +380,52 @@ class WC_Totals { } } } - $this->set_items_total( array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) ); - $this->set_items_total_tax( array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) ); - //$this->set_coupon_totals( wp_list_pluck( $this->coupons, 'total' ) ); - //$this->set_coupon_tax_totals( wp_list_pluck( $this->coupons, 'total_tax' ) ); - //$this->set_coupon_counts( wp_list_pluck( $this->coupons, 'count' ) ); - //$this->set_item_totals( $this->items ); + $this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) ); + $this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) ); } /** * Calculate any fees taxes. + * + * @since 3.2.0 */ protected function calculate_fee_totals() { - if ( ! empty( $this->fees ) ) { - foreach ( $this->fees as $fee_key => $fee ) { - if ( $this->wc_tax_enabled() && $fee->taxable ) { - $fee->taxes = WC_Tax::calc_tax( $fee->total, $tax_rates, false ); - $fee->total_tax = array_sum( $fee->taxes ); - } + foreach ( $this->get_fees() as $fee_key => $fee ) { + if ( wc_tax_enabled() && $fee->taxable ) { + $fee->taxes = WC_Tax::calc_tax( $fee->total, $tax_rates, false ); + $fee->total_tax = array_sum( $fee->taxes ); } } - $this->set_fees_total( array_sum( array_values( wp_list_pluck( $this->fees, 'total' ) ) ) ); - $this->set_fees_total_tax( array_sum( array_values( wp_list_pluck( $this->fees, 'total_tax' ) ) ) ); } /** * Calculate any shipping taxes. + * + * @since 3.2.0 */ protected function calculate_shipping_totals() { - + //$this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping, 'total' ) ) ) ); } /** * Main cart totals. - */ - protected function calculate_totals() { - - $this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping, 'total' ) ) ) ); - $this->set_taxes( $this->get_merged_taxes() ); - - // Total up/round taxes and shipping taxes - if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) { - $this->set_tax_total( WC_Tax::get_tax_total( wc_list_pluck( $this->get_taxes(), 'get_tax_total' ) ) ); - $this->set_shipping_tax_total( WC_Tax::get_tax_total( wc_list_pluck( $this->get_taxes(), 'get_shipping_tax_total' ) ) ); - } else { - $this->set_tax_total( array_sum( wc_list_pluck( $this->get_taxes(), 'get_tax_total' ) ) ); - $this->set_shipping_tax_total( array_sum( wc_list_pluck( $this->get_taxes(), 'get_shipping_tax_total' ) ) ); - } - - // Allow plugins to hook and alter totals before final total is calculated - do_action( 'woocommerce_calculate_totals', WC()->cart ); - - // Grand Total - Discounted product prices, discounted tax, shipping cost + tax - $this->set_total( apply_filters( 'woocommerce_calculated_total', round( $this->get_items_total() + $this->get_fees_total() + $this->get_shipping_total() + $this->get_tax_total() + $this->get_shipping_tax_total(), wc_get_price_decimals() ), WC()->cart ) ); - } - - /* - |-------------------------------------------------------------------------- - | Setters. - |-------------------------------------------------------------------------- - */ - - /** - * Set cart/order items which will be discounted. * * @since 3.2.0 - * @param array $raw_items List of raw cart or order items. */ - protected function set_items( $raw_items ) { - $this->items = array(); + protected function calculate_totals() { + $this->set_total( 'taxes', $this->get_merged_taxes() ); + $this->set_total( 'tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'tax_total' ) ) ); + $this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'shipping_tax_total' ) ) ); - if ( ! empty( $raw_items ) && is_array( $raw_items ) ) { - foreach ( $raw_items as $raw_item ) { - $item = (object) array( - 'price' => 0, // Line price without discounts, in cents. - 'quantity' => 0, // Line qty. - 'product' => false, - ); - if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { - //$item->quantity = $raw_item->get_quantity(); - //$item->price = $raw_item->get_price() * $raw_item->get_quantity(); - //$item->is_taxable = $raw_item->is_taxable(); - //$item->tax_class = $raw_item->get_tax_class(); - // @todo - } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { - $item->key = $raw_item->get_id(); - $item->quantity = $raw_item->get_quantity(); - $item->price = $raw_item->get_subtotal() * $this->precision; - $item->product = $raw_item->get_product(); - } else { - $item->key = $raw_item['key']; - $item->quantity = $raw_item['quantity']; - $item->price = $raw_item['data']->get_price() * $this->precision * $raw_item['quantity']; - $item->product = $raw_item['data']; - } - $this->items[ $item->key ] = $item; - } - } - } + //$this->set_fees_total( array_sum( array_values( wp_list_pluck( $this->fees, 'total' ) ) ) ); + //$this->set_fees_total_tax( array_sum( array_values( wp_list_pluck( $this->fees, 'total_tax' ) ) ) ); - /** - * Set coupons. - * @param array $coupons - */ - protected function set_coupons( $coupons ) { - foreach ( $coupons as $code => $coupon_object ) { - $coupon = $this->get_default_coupon_props(); - $coupon->coupon = $coupon_object; - $this->coupons[ $code ] = $coupon; - } - } - /** - * Set fees. - * @param array $fees - */ - protected function set_fees( $fees ) { - foreach ( $fees as $fee_key => $fee_object ) { - $fee = $this->get_default_fee_props(); - $fee->total = $fee_object->amount; - $fee->taxable = $fee_object->taxable; - $fee->tax_class = $fee_object->tax_class; - $this->fees[ $fee_key ] = $fee; - } - } + $this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + $this->get_total( 'tax_total', true ) + $this->get_total( 'shipping_tax_total', true ) ) ); - /** - * Set shipping lines. - * @param array - */ - protected function set_shipping( $shipping_objects ) { - $this->shipping = array(); - if ( is_array( $shipping_objects ) ) { - foreach ( $shipping_objects as $key => $shipping_object ) { - $shipping = $this->get_default_shipping_props(); - $shipping->total = $shipping_object->cost; - $shipping->taxes = $shipping_object->taxes; - $shipping->total_tax = array_sum( $shipping_object->taxes ); - $this->shipping[ $key ] = $shipping; - } - } - } - - /** - * Set taxes. - * @param array $value - */ - protected function set_taxes( $value ) { - $this->totals['taxes'] = $value; - } - - /** - * Set tax total. - * @param float $value - */ - protected function set_tax_total( $value ) { - $this->totals['tax_total'] = $value; - } - - /** - * Set shipping total. - * @param float $value - */ - protected function set_shipping_total( $value ) { - $this->totals['shipping_total'] = $value; - } - - /** - * Set shipping tax total. - * @param float $value - */ - protected function set_shipping_tax_total( $value ) { - $this->totals['shipping_tax_total'] = $value; - } - - /** - * Set item totals. - * @param array $value - */ - protected function set_item_totals( $value ) { - $this->totals['item_totals'] = $value; - } - - /** - * Set items subtotal. - * @param float $value - */ - protected function set_items_subtotal( $value ) { - $this->totals['items_subtotal'] = $value; - } - - /** - * Set items subtotal tax. - * @param float $value - */ - protected function set_items_subtotal_tax( $value ) { - $this->totals['items_subtotal_tax'] = $value; - } - - /** - * Set items total. - * @param float $value - */ - protected function set_items_total( $value ) { - $this->totals['items_total'] = $value; - } - - /** - * Set items total tax. - * @param float $value - */ - protected function set_items_total_tax( $value ) { - $this->totals['items_total_tax'] = $value; - } - - /** - * Set fees total. - * @param float $value - */ - protected function set_fees_total( $value ) { - $this->totals['fees_total'] = $value; - } - - /** - * Set fees total tax. - * @param float $value - */ - protected function set_fees_total_tax( $value ) { - $this->totals['fees_total_tax'] = $value; - } - - /** - * Set total. - * @param float $value - */ - protected function set_total( $value ) { - $this->totals['total'] = max( 0, $value ); - } - - /* - |-------------------------------------------------------------------------- - | Getters. - |-------------------------------------------------------------------------- - */ - - /** - * Get all totals. - * @return array. - */ - public function get_totals() { - return $this->totals; - } - - /** - * Get shipping and item taxes. - * @return array - */ - public function get_taxes() { - return $this->totals['taxes']; - } - - /** - * Get tax total. - * @return float - */ - public function get_tax_total() { - return $this->totals['tax_total']; - } - - /** - * Get shipping total. - * @return float - */ - public function get_shipping_total() { - return $this->totals['shipping_total']; - } - - /** - * Get shipping tax total. - * @return float - */ - public function get_shipping_tax_total() { - return $this->totals['shipping_tax_total']; - } - - /** - * Get the items subtotal. - * @return float - */ - public function get_items_subtotal() { - return $this->totals['items_subtotal']; - } - - /** - * Get the items subtotal tax. - * @return float - */ - public function get_items_subtotal_tax() { - return $this->totals['items_subtotal_tax']; - } - - /** - * Get the items total. - * @return float - */ - public function get_items_total() { - return $this->totals['items_total']; - } - - /** - * Get the items total tax. - * @return float - */ - public function get_items_total_tax() { - return $this->totals['items_total_tax']; - } - - /** - * Get the total fees amount. - * @return float - */ - public function get_fees_total() { - return $this->totals['fees_total']; - } - - /** - * Get the total fee tax amount. - * @return float - */ - public function get_fees_total_tax() { - return $this->totals['fees_total_tax']; - } - - /** - * Get the total. - * @return float - */ - public function get_total() { - return $this->totals['total']; - } - - /** - * Returns an array of item totals. - * @return array - */ - public function get_item_totals() { - return $this->totals['item_totals']; - } - - /** - * Get tax rates for an item. Caches rates in class to avoid multiple look ups. - * @param object $item - * @return array of taxes - */ - protected function get_item_tax_rates( $item ) { - $tax_class = $item->product->get_tax_class(); - return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class() ); - } - - /** - * Get all tax rows for a set of items and shipping methods. - * @return array - */ - protected function get_merged_taxes() { - $taxes = array(); - - foreach ( $this->items as $item ) { - foreach ( $item->taxes as $rate_id => $rate ) { - if ( ! isset( $taxes[ $rate_id ] ) ) { - $taxes[ $rate_id ] = new WC_Item_Tax(); - } - $taxes[ $rate_id ]->set_rate( $rate_id ); - $taxes[ $rate_id ]->set_tax_total( $taxes[ $rate_id ]->get_tax_total() + $rate ); - } - } - - foreach ( $this->shipping as $item ) { - foreach ( $item->taxes as $rate_id => $rate ) { - if ( ! isset( $taxes[ $rate_id ] ) ) { - $taxes[ $rate_id ] = new WC_Item_Tax(); - } - $taxes[ $rate_id ]->set_rate( $rate_id ); - $taxes[ $rate_id ]->set_shipping_tax_total( $taxes[ $rate_id ]->get_shipping_tax_total() + $rate ); - } - } - - return $taxes; + // @todo woocommerce_tax_round_at_subtotal option - how should we handle this with precision? + // @todo woocommerce_calculate_totals action for carts. + // @todo woocommerce_calculated_total filter for carts. } } diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index 5b724f9ca70..e691a54ec2d 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -51,7 +51,7 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { WC()->cart->add_fee( "test fee", 10, true ); WC()->cart->add_fee( "test fee 2", 20, true ); WC()->cart->add_fee( "test fee non-taxable", 10, false ); - WC()->cart->add_discount( 'test-coupon-10' ); + WC()->cart->add_discount( $coupon->get_code() ); // @todo manual discounts $this->totals = new WC_Totals( WC()->cart ); @@ -81,22 +81,20 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { * Test get and set items. */ public function test_get_totals() { - $this->assertEquals( array(), $this->totals->get_totals() ); + $this->assertEquals( array( + 'fees_total' => 40.00, + 'fees_total_tax' => 6.00, + 'items_subtotal' => 30.00, + 'items_subtotal_tax' => 6.00, + 'items_total' => 27.00, + 'items_total_tax' => 5.40, + 'total' => 72.40, + 'taxes' => array(), // @todo ? + 'tax_total' => 11.40, + 'shipping_total' => 0, // @todo ? + 'shipping_tax_total' => 0, // @todo ? + 'discounts_total' => 3.00, + 'discounts_tax_total' => 0, // @todo ? + ), $this->totals->get_totals() ); } - - /** - * Test get and set items. - */ - public function test_get_taxes() { - - } - - /** - * Test get and set items. - */ - public function test_get_tax_total() { - - } - - //... @todo test all public methods } From cdbead8961930ea9984ff256bcea631100c125d6 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Mon, 24 Jul 2017 15:05:23 -0700 Subject: [PATCH 107/224] Clean up and tests --- includes/class-wc-product-query.php | 13 +- .../class-wc-product-data-store-cpt.php | 34 ++- tests/unit-tests/product/functions.php | 221 +++++++++++++++++- 3 files changed, 245 insertions(+), 23 deletions(-) diff --git a/includes/class-wc-product-query.php b/includes/class-wc-product-query.php index 935e962bb4d..613cd3b51f5 100644 --- a/includes/class-wc-product-query.php +++ b/includes/class-wc-product-query.php @@ -26,13 +26,10 @@ class WC_Product_Query extends WC_Object_Query { 'type' => array_merge( array_keys( wc_get_product_types() ) ), 'limit' => get_option( 'posts_per_page' ), 'include' => array(), - 'slug' => '', 'date_created' => '', 'date_modified' => '', 'featured' => '', - 'catalog_visibility' => '', - 'description' => '', - 'short_description' => '', + 'visibility' => '', 'sku' => '', 'price' => '', 'regular_price' => '', @@ -51,22 +48,14 @@ class WC_Product_Query extends WC_Object_Query { 'length' => '', 'width' => '', 'height' => '', - 'upsell_ids' => array(), - 'cross_sell_ids' => array(), 'reviews_allowed' => '', - 'purchase_note' => '', - 'attributes' => array(), - 'default_attributes' => array(), - 'menu_order' => '', 'virtual' => '', 'downloadable' => '', 'category' => array(), 'tag' => array(), 'shipping_class' => array(), - 'image_id' => '', 'download_limit' => '', 'download_expiry' => '', - 'rating_counts' => array(), 'average_rating' => '', 'review_count' => '', ) diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 872e7c2614f..359ccca713d 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1273,7 +1273,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da 'page' => 'paged', 'include' => 'post__in', ); - foreach ( $key_mapping as $query_key => $db_key ) { if ( isset( $query_vars[ $query_key ] ) ) { $query_vars[ $db_key ] = $query_vars[ $query_key ]; @@ -1281,6 +1280,22 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da } } + // Map boolean queries that are stored as 'yes'/'no' in the DB. + $boolean_queries = array( + 'virtual', + 'downloadable', + 'featured', + 'sold_individually', + 'manage_stock', + 'reviews_allowed', + ); + foreach ( $boolean_queries as $boolean_query ) { + if ( isset( $query_vars[ $boolean_query ] ) && is_bool( $query_vars[ $boolean_query ] ) ) { + $query_vars[ $boolean_query ] = $query_vars[ $boolean_query ] ? 'yes' : 'no'; + } + } + + // SKU needs special handling because it works with partial matches. // Don't auto-generate meta query args for it. $sku_query = false; @@ -1289,6 +1304,14 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da unset( $query_vars['sku'] ); } + // total_sales needs special handline because the meta key doesn't have the underscore prefix. + // Don't auto generate meta query args for it. + $total_sales_query = false; + if ( isset( $query_vars['total_sales'] ) ) { + $total_sales_query = $query_vars['total_sales']; + unset( $query_vars['total_sales'] ); + } + $wp_query_args = parent::get_wp_query_args( $query_vars ); if ( ! isset( $wp_query_args['date_query'] ) ) { @@ -1307,6 +1330,15 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da ); } + // Manually build the total_sales query if needed. + if ( $total_sales_query ) { + $wp_query_args['meta_query'][] = array( + 'key' => 'total_sales', + 'value' => $total_sales_query, + 'compare' => '=', + ); + } + // Handle product types. if ( 'variation' === $query_vars['type'] ) { $wp_query_args['post_type'] = 'product_variation'; diff --git a/tests/unit-tests/product/functions.php b/tests/unit-tests/product/functions.php index 6fb6e38071b..9980f7c8dea 100644 --- a/tests/unit-tests/product/functions.php +++ b/tests/unit-tests/product/functions.php @@ -51,29 +51,33 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { $this->assertEquals( 9, count( wc_get_products( array( 'return' => 'ids' ) ) ) ); - // test status + // Test status. $products = wc_get_products( array( 'return' => 'ids', 'status' => 'draft' ) ); $this->assertEquals( array( $draft->get_id() ), $products ); - // test type + // Test type. $products = wc_get_products( array( 'return' => 'ids', 'type' => 'variation' ) ); $this->assertEquals( 2, count( $products ) ); - // test parent + // Test parent. $products = wc_get_products( array( 'return' => 'ids', 'type' => 'variation', 'parent' => $variation->get_id() ) ); $this->assertEquals( 2, count( $products ) ); - // test skus + // Test parent_exclude. + $products = wc_get_products( array( 'return' => 'ids', 'type' => 'variation', 'parent_exclude' => array( $variation->get_id() ) ) ); + $this->assertEquals( 0, count( $products ) ); + + // Test skus. $products = wc_get_products( array( 'return' => 'ids', 'sku' => 'GET TEST SKU' ) ); $this->assertEquals( 2, count( $products ) ); $this->assertContains( $product->get_id(), $products ); $this->assertContains( $external->get_id(), $products ); - // test categories + // Test categories. $products = wc_get_products( array( 'return' => 'ids', 'category' => array( $term_cat_1->slug ) ) ); $this->assertEquals( 3, count( $products ) ); - // test tags + // Test tags. $products = wc_get_products( array( 'return' => 'ids', 'tag' => array( $term_tag_1->slug ) ) ); $this->assertEquals( 2, count( $products ) ); @@ -83,31 +87,228 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { $products = wc_get_products( array( 'return' => 'ids', 'tag' => array( $term_tag_1->slug, $term_tag_2->slug ) ) ); $this->assertEquals( 3, count( $products ) ); - // test limit + // Test limit. $products = wc_get_products( array( 'return' => 'ids', 'limit' => 5 ) ); $this->assertEquals( 5, count( $products ) ); - // test offset + // Test offset. $products = wc_get_products( array( 'return' => 'ids', 'limit' => 2 ) ); $products_offset = wc_get_products( array( 'return' => 'ids', 'limit' => 2, 'offset' => 2 ) ); $this->assertEquals( 2, count( $products ) ); $this->assertEquals( 2, count( $products_offset ) ); $this->assertNotEquals( $products, $products_offset ); - // test page + // Test page. $products_page_1 = wc_get_products( array( 'return' => 'ids', 'limit' => 2 ) ); $products_page_2 = wc_get_products( array( 'return' => 'ids', 'limit' => 2, 'page' => 2 ) ); $this->assertEquals( 2, count( $products_page_1 ) ); $this->assertEquals( 2, count( $products_page_2 ) ); $this->assertNotEquals( $products_page_1, $products_page_2 ); - // test exclude + // Test exclude. $products = wc_get_products( array( 'return' => 'ids', 'limit' => 200, 'exclude' => array( $product->get_id() ) ) ); $this->assertNotContains( $product->get_id(), $products ); + // Test include. + $products = wc_get_products( array( 'return' => 'ids', 'include' => array( $product->get_id() ) ) ); + $this->assertContains( $product->get_id(), $products ); + + // Test order and orderby. + $products = wc_get_products( array( 'return' => 'ids', 'order' => 'ASC', 'orderby' => 'ID', 'limit' => 2 ) ); + $this->assertEquals( array( $product->get_id(), $product_2->get_id() ), $products ); + + // Test paginate. + $products = wc_get_products( array( 'paginate' => true ) ); + $this->assertGreaterThan( 0, $products->total ); + $this->assertGreaterThan( 0, $products->max_num_pages ); + $this->assertNotEmpty( $products->products ); + $variation->delete( true ); } + /** + * Tests wc_get_products() with dimension parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_dimensions() { + $product_1 = new WC_Product_Simple; + $product_1->set_width( '12.5' ); + $product_1->set_height( '5' ); + $product_1->set_weight( '11.4' ); + $product_1->save(); + + $product_2 = new WC_Product_Simple; + $product_2->set_width( '10' ); + $product_2->set_height( '5' ); + $product_2->set_weight( '15' ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'width' => 12.5 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'height' => 5.0 ) ); + $this->assertEquals( array( $product_1->get_id(), $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'weight' => 15 ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + } + + /** + * Tests wc_get_products() with price parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_price() { + $product_1 = new WC_Product_Simple; + $product_1->set_regular_price( '12.5' ); + $product_1->set_price( '12.5' ); + $product_1->save(); + + $product_2 = new WC_Product_Simple; + $product_2->set_regular_price( '14' ); + $product_2->set_sale_price( '12.5' ); + $product_2->set_price( '12.5' ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'regular_price' => 12.5 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'sale_price' => 12.5 ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'price' => 12.5 ) ); + $this->assertEquals( array( $product_1->get_id(), $product_2->get_id() ), $products ); + } + + /** + * Tests wc_get_products() with total_sales parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_total_sales() { + $product_1 = new WC_Product_Simple; + $product_1->set_total_sales( 4 ); + $product_1->save(); + + $product_2 = new WC_Product_Simple; + $product_2->set_total_sales( 2 ); + $product_2->save(); + + $product_3 = new WC_Product_Simple; + $product_3->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'total_sales' => 4 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + } + + /** + * Tests wc_get_products() with boolean parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_booleans() { + $product_1 = new WC_Product_Simple; + $product_1->set_virtual( true ); + $product_1->set_downloadable( true ); + $product_1->set_featured( true ); + $product_1->set_sold_individually( true ); + $product_1->set_backorders( 'no' ); + $product_1->set_manage_stock( false ); + $product_1->set_reviews_allowed( true ); + $product_1->save(); + + $product_2 = new WC_Product_Simple; + $product_2->set_virtual( false ); + $product_2->set_downloadable( false ); + $product_2->set_featured( false ); + $product_2->set_sold_individually( false ); + $product_2->set_backorders( 'notify' ); + $product_2->set_manage_stock( true ); + $product_2->set_reviews_allowed( false ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'virtual' => true ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'virtual' => false ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'downloadable' => false ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + // TODO: Fix featured query. + //$products = wc_get_products( array( 'return' => 'ids', 'featured' => true ) ); + //$this->assertEquals( array( $product_1->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'sold_individually' => true ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'backorders' => 'notify' ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'manage_stock' => true ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + // TODO: Fix this to use comment status in query args + //$products = wc_get_products( array( 'return' => 'ids', 'reviews_allowed' => true ) ); + //$this->assertEquals( array( $product_1->get_id() ), $products ); + } + + /** + * Tests wc_get_products() with visibility parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_visibility() { + + } + + /** + * Tests wc_get_products() with stock parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_stock() { + + } + + /** + * Tests wc_get_products() with tax parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_tax() { + + } + + /** + * Tests wc_get_products() with shipping parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_shipping_class() { + + } + + /** + * Tests wc_get_products() with download parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_download() { + + } + + /** + * Tests wc_get_products() with reviews parameters. + * + * @since 3.2.0 + */ + public function test_wc_get_products_reviews() { + + } + /** * Test wc_get_product(). * From 7781b2ea4db8c37e7092fb61a09974e2fbe3619b Mon Sep 17 00:00:00 2001 From: Fulvio Notarstefano Date: Tue, 25 Jul 2017 11:54:03 +0800 Subject: [PATCH 108/224] [#16242] Add context to woocommerce_backordered_item_meta_name --- includes/class-wc-order-item-product.php | 2 +- includes/legacy/abstract-wc-legacy-order.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-order-item-product.php b/includes/class-wc-order-item-product.php index 2cf90dfb801..0e15e277c46 100644 --- a/includes/class-wc-order-item-product.php +++ b/includes/class-wc-order-item-product.php @@ -198,7 +198,7 @@ class WC_Order_Item_Product extends WC_Order_Item { public function set_backorder_meta() { $product = $this->get_product(); if ( $product && $product->backorders_require_notification() && $product->is_on_backorder( $this->get_quantity() ) ) { - $this->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $this->get_quantity() - max( 0, $product->get_stock_quantity() ), true ); + $this->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ), $this ), $this->get_quantity() - max( 0, $product->get_stock_quantity() ), true ); } } diff --git a/includes/legacy/abstract-wc-legacy-order.php b/includes/legacy/abstract-wc-legacy-order.php index 976013e120e..30faea7c8af 100644 --- a/includes/legacy/abstract-wc-legacy-order.php +++ b/includes/legacy/abstract-wc-legacy-order.php @@ -161,7 +161,7 @@ abstract class WC_Abstract_Legacy_Order extends WC_Data { // Handle qty if set. if ( isset( $args['qty'] ) ) { if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) { - $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_stock_quantity() ), true ); + $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ), $item ), $args['qty'] - max( 0, $product->get_stock_quantity() ), true ); } $args['subtotal'] = $args['subtotal'] ? $args['subtotal'] : wc_get_price_excluding_tax( $product, array( 'qty' => $args['qty'] ) ); $args['total'] = $args['total'] ? $args['total'] : wc_get_price_excluding_tax( $product, array( 'qty' => $args['qty'] ) ); From e8e200195f5e76f4349b3b2da01b63477c820fb7 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 25 Jul 2017 14:05:49 +0100 Subject: [PATCH 109/224] fees pass --- includes/class-wc-totals.php | 239 ++++++++++++++++++----------- tests/unit-tests/totals/totals.php | 29 +++- 2 files changed, 174 insertions(+), 94 deletions(-) diff --git a/includes/class-wc-totals.php b/includes/class-wc-totals.php index 7debc5e970e..ec01bb44296 100644 --- a/includes/class-wc-totals.php +++ b/includes/class-wc-totals.php @@ -1,14 +1,26 @@ 0, - 'fees_total_tax' => 0, - 'items_subtotal' => 0, - 'items_subtotal_tax' => 0, - 'items_total' => 0, - 'items_total_tax' => 0, - 'total' => 0, - 'taxes' => array(), - 'tax_total' => 0, - 'shipping_total' => 0, - 'shipping_tax_total' => 0, - 'discounts_total' => 0, - 'discounts_tax_total' => 0, - ); + private $discount_totals = array(); /** * Precision so we can work in cents. @@ -65,12 +63,35 @@ class WC_Totals { * @since 3.2.0 * @var int */ - protected $precision = 1; + private $precision = 1; + + /** + * Stores totals. + * + * @since 3.2.0 + * @var array + */ + private $totals = array( + 'fees_total' => 0, + 'fees_total_tax' => 0, + 'items_subtotal' => 0, + 'items_subtotal_tax' => 0, + 'items_total' => 0, + 'items_total_tax' => 0, + 'total' => 0, + 'taxes' => array(), + 'tax_total' => 0, + 'shipping_total' => 0, + 'shipping_tax_total' => 0, + 'discounts_total' => 0, + 'discounts_tax_total' => 0, + ); /** * Sets up the items provided, and calculate totals. * * @since 3.2.0 + * @param object $cart Cart or order object to calculate totals for. */ public function __construct( &$cart = null ) { $this->precision = pow( 10, wc_get_price_decimals() ); @@ -80,11 +101,12 @@ class WC_Totals { } /** - * Handles a cart or order object passed in for calculation. Normalises data. + * Handles a cart or order object passed in for calculation. Normalises data + * into the same format for use by this class. * * @since 3.2.0 */ - protected function set_items() { + private function set_items() { if ( is_a( $this->object, 'WC_Cart' ) ) { foreach ( $this->object->get_cart() as $cart_item_key => $cart_item ) { $item = $this->get_default_item_props(); @@ -98,13 +120,13 @@ class WC_Totals { } /** - * Remove precision from a price. + * Remove precision (deep) from a price. * * @since 3.2.0 - * @param int $value + * @param int|array $value Value to remove precision from. * @return float */ - protected function remove_precision( $value ) { + private function remove_precision( $value ) { if ( is_array( $value ) ) { foreach ( $value as $key => $subvalue ) { $value[ $key ] = $this->remove_precision( $subvalue ); @@ -121,8 +143,12 @@ class WC_Totals { * @since 3.2.0 * @return array */ - protected function get_default_item_props() { + private function get_default_item_props() { return (object) array( + 'key' => '', + 'quantity' => 0, + 'price' => 0, + 'product' => false, 'price_includes_tax' => wc_prices_include_tax(), 'subtotal' => 0, 'subtotal_tax' => 0, @@ -134,6 +160,19 @@ class WC_Totals { ); } + /** + * Get default blank set of props used per fee. + * + * @since 3.2.0 + * @return array + */ + private function get_default_fee_props() { + return (object) array( + 'total_tax' => 0, + 'taxes' => array(), + ); + } + /** * Only ran if woocommerce_adjust_non_base_location_prices is true. * @@ -141,16 +180,18 @@ class WC_Totals { * taxes. This is off by default unless the filter is used. * * @since 3.2.0 + * @param object $item Item to adjust the prices of. + * @return object */ - protected function adjust_non_base_location_price( $item ) { + private function adjust_non_base_location_price( $item ) { $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->tax_class ); $item_tax_rates = $this->get_item_tax_rates( $item ); if ( $item_tax_rates !== $base_tax_rates ) { - // Work out a new base price without the shop's base tax + // Work out a new base price without the shop's base tax. $taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true, true ); - // Now we have a new item price (excluding TAX) + // Now we have a new item price (excluding TAX). $item->price = $item->price - array_sum( $taxes ); $item->price_includes_tax = false; } @@ -161,20 +202,20 @@ class WC_Totals { * Get discounted price of an item with precision (in cents). * * @since 3.2.0 - * @param object $item + * @param object $item Item to get the price of. * @return int */ - protected function get_discounted_price_in_cents( $item ) { - return $item->price - $this->totals['discounts'][ $item->key ]; + private function get_discounted_price_in_cents( $item ) { + return $item->price - $this->discount_totals[ $item->key ]; } /** * Get tax rates for an item. Caches rates in class to avoid multiple look ups. * - * @param object $item + * @param object $item Item to get tax rates for. * @return array of taxes */ - protected function get_item_tax_rates( $item ) { + private function get_item_tax_rates( $item ) { $tax_class = $item->product->get_tax_class(); return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class() ); } @@ -185,7 +226,7 @@ class WC_Totals { * @since 3.2.0 * @return array */ - protected function get_coupons() { + private function get_coupons() { if ( is_a( $this->object, 'WC_Cart' ) ) { return $this->object->get_coupons(); } @@ -197,27 +238,27 @@ class WC_Totals { * @since 3.2.0 * @return array */ - protected function get_shipping() { + private function get_shipping() { // @todo get this somehow. Where does calc occur? return array(); } - protected function get_discounts() { + /** + * Get discounts. + * + * @return array + */ + private function get_discounts() { // @todo fee style API for discounts in cart/checkout. return array(); } - protected function get_fees() { - // @todo where should fee api be located? New class? - return array(); - } - /** * Get a single total with or without precision (in cents). * * @since 3.2.0 * @param string $key Total to get. - * @param bool $in_cents + * @param bool $in_cents Should the totals be returned in cents, or without precision. * @return int|float */ public function get_total( $key = 'total', $in_cents = false ) { @@ -229,10 +270,10 @@ class WC_Totals { * Set a single total. * * @since 3.2.0 - * @param string $key - * @param int $total + * @param string $key Total name you want to set. + * @param int $total Total to set. */ - protected function set_total( $key = 'total', $total ) { + private function set_total( $key = 'total', $total ) { $this->totals[ $key ] = $total; } @@ -240,7 +281,7 @@ class WC_Totals { * Get all totals with or without precision (in cents). * * @since 3.2.0 - * @param $in_cents bool + * @param bool $in_cents Should the totals be returned in cents, or without precision. * @return array. */ public function get_totals( $in_cents = false ) { @@ -255,22 +296,29 @@ class WC_Totals { * @since 3.2.0 * @return array */ - protected function get_merged_taxes() { + private function get_merged_taxes() { $taxes = array(); + + foreach ( array_merge( $this->items, $this->fees, $this->get_shipping() ) as $item ) { + foreach ( $item->taxes as $rate_id => $rate ) { + $taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 ); + } + } + foreach ( $this->items as $item ) { foreach ( $item->taxes as $rate_id => $rate ) { - if ( ! isset( $taxes[ $rate_id ] ) ) { - $taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 ); - } + $taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate; + } + } + + foreach ( $this->fees as $fee ) { + foreach ( $fee->taxes as $rate_id => $rate ) { $taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate; } } foreach ( $this->get_shipping() as $item ) { foreach ( $item->taxes as $rate_id => $rate ) { - if ( ! isset( $taxes[ $rate_id ] ) ) { - $taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 ); - } $taxes[ $rate_id ]['shipping_tax_total'] = $taxes[ $rate_id ]['shipping_tax_total'] + $rate; } } @@ -284,11 +332,11 @@ class WC_Totals { */ /** - * Run all calculations methods on the given items. + * Run all calculations methods on the given items in sequence. * * @since 3.2.0 */ - protected function calculate() { + private function calculate() { $this->calculate_item_subtotals(); $this->calculate_discounts(); $this->calculate_item_totals(); @@ -311,7 +359,7 @@ class WC_Totals { * * @since 3.2.0 */ - protected function calculate_item_subtotals() { + private function calculate_item_subtotals() { foreach ( $this->items as $item ) { if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { $item = $this->adjust_non_base_location_price( $item ); @@ -336,23 +384,21 @@ class WC_Totals { /** * Calculate all discount and coupon amounts. * + * @todo Manual discounts. + * * @since 3.2.0 * @uses WC_Discounts class. */ - protected function calculate_discounts() { + private function calculate_discounts() { $discounts = new WC_Discounts( $this->items ); foreach ( $this->get_coupons() as $coupon ) { $discounts->apply_coupon( $coupon ); } - foreach ( $this->get_discounts() as $discount ) { - //$discounts->apply_discount( $coupon ); @todo - } - - $this->totals['discounts'] = $discounts->get_discounts(); - $this->totals['discounts_total'] = array_sum( $this->totals['discounts'] ); - // $this->totals['discounts_tax_total'] = $value; + $this->discount_totals = $discounts->get_discounts(); + $this->totals['discounts_total'] = array_sum( $this->discount_totals ); + // @todo $this->totals['discounts_tax_total'] = $value; /*$this->set_coupon_totals( wp_list_pluck( $this->coupons, 'total' ) ); //$this->set_coupon_tax_totals( wp_list_pluck( $this->coupons, 'total_tax' ) ); @@ -360,11 +406,11 @@ class WC_Totals { } /** - * Totals are costs after discounts. + * Totals are costs after discounts. @todo move cart specific setters to subclass? * * @since 3.2.0 */ - protected function calculate_item_totals() { + private function calculate_item_totals() { foreach ( $this->items as $item ) { $item->total = $this->get_discounted_price_in_cents( $item ); $item->total_tax = 0; @@ -382,20 +428,45 @@ class WC_Totals { } $this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) ); $this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) ); + + $this->object->subtotal = array_sum( wp_list_pluck( $this->items, 'total' ) ) + array_sum( wp_list_pluck( $this->items, 'total_tax' ) ); + $this->object->subtotal_ex_tax = array_sum( wp_list_pluck( $this->items, 'total' ) ); } /** - * Calculate any fees taxes. + * Triggers the cart fees API, grabs the list of fees, and calculates taxes. + * + * Note: This class sets the totals for the 'object' as they are calculated. This is so that APIs like the fees API can see these totals if needed. * * @since 3.2.0 + * @todo logic is unqiue to carts. */ - protected function calculate_fee_totals() { - foreach ( $this->get_fees() as $fee_key => $fee ) { - if ( wc_tax_enabled() && $fee->taxable ) { - $fee->taxes = WC_Tax::calc_tax( $fee->total, $tax_rates, false ); + private function calculate_fee_totals() { + $this->object->calculate_fees(); + + foreach ( $this->object->get_fees() as $fee_key => $fee_object ) { + $fee = $this->get_default_fee_props(); + $fee->object = $fee_object; + $fee->total = $fee->object->amount * $this->precision; + + if ( wc_tax_enabled() && $fee->object->taxable ) { + $fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class ), false ); $fee->total_tax = array_sum( $fee->taxes ); } + + $this->fees[ $fee_key ] = $fee; } + + // Store totals to self. + $this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) ); + $this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) ); + + // Transfer totals to the cart. + foreach ( $this->fees as $fee_key => $fee ) { + $this->object->fees[ $fee_key ]->tax = $this->remove_precision( $fee->total_tax ); + $this->object->fees[ $fee_key ]->tax_data = $this->remove_precision( $fee->taxes ); + } + $this->object->fee_total = $this->remove_precision( array_sum( wp_list_pluck( $this->fees, 'total' ) ) ); } /** @@ -403,7 +474,7 @@ class WC_Totals { * * @since 3.2.0 */ - protected function calculate_shipping_totals() { + private function calculate_shipping_totals() { //$this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping, 'total' ) ) ) ); } @@ -412,20 +483,10 @@ class WC_Totals { * * @since 3.2.0 */ - protected function calculate_totals() { + private function calculate_totals() { $this->set_total( 'taxes', $this->get_merged_taxes() ); $this->set_total( 'tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'tax_total' ) ) ); $this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'shipping_tax_total' ) ) ); - - //$this->set_fees_total( array_sum( array_values( wp_list_pluck( $this->fees, 'total' ) ) ) ); - //$this->set_fees_total_tax( array_sum( array_values( wp_list_pluck( $this->fees, 'total_tax' ) ) ) ); - - $this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + $this->get_total( 'tax_total', true ) + $this->get_total( 'shipping_tax_total', true ) ) ); - - - // @todo woocommerce_tax_round_at_subtotal option - how should we handle this with precision? - // @todo woocommerce_calculate_totals action for carts. - // @todo woocommerce_calculated_total filter for carts. } } diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index e691a54ec2d..f170762bcc6 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -35,6 +35,9 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { $product = WC_Helper_Product::create_simple_product(); $product2 = WC_Helper_Product::create_simple_product(); + WC_Helper_Shipping::create_simple_flat_rate(); + WC()->session->set( 'chosen_shipping_methods', array( 'flat_rate' ) ); + $coupon = new WC_Coupon; $coupon->set_code( 'test-coupon-10' ); $coupon->set_amount( 10 ); @@ -48,21 +51,32 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { WC()->cart->add_to_cart( $product->get_id(), 1 ); WC()->cart->add_to_cart( $product2->get_id(), 2 ); - WC()->cart->add_fee( "test fee", 10, true ); - WC()->cart->add_fee( "test fee 2", 20, true ); - WC()->cart->add_fee( "test fee non-taxable", 10, false ); WC()->cart->add_discount( $coupon->get_code() ); + add_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) ); + // @todo manual discounts $this->totals = new WC_Totals( WC()->cart ); } + /** + * Add fees when the fees API is called. + */ + public function add_cart_fees_callback() { + WC()->cart->add_fee( "test fee", 10, true ); + WC()->cart->add_fee( "test fee 2", 20, true ); + WC()->cart->add_fee( "test fee non-taxable", 10, false ); + } + /** * Clean up after test. */ public function tearDown() { WC()->cart->empty_cart(); + WC()->session->set( 'chosen_shipping_methods', array() ); + WC_Helper_Shipping::delete_simple_flat_rate(); update_option( 'woocommerce_calc_taxes', 'no' ); + remove_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) ); foreach ( $this->products as $product ) { $product->delete( true ); @@ -88,8 +102,13 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { 'items_subtotal_tax' => 6.00, 'items_total' => 27.00, 'items_total_tax' => 5.40, - 'total' => 72.40, - 'taxes' => array(), // @todo ? + 'total' => 78.40, + 'taxes' => array( + 1 => array( + 'tax_total' => 11.40, + 'shipping_tax_total' => 0.00, + ) + ), 'tax_total' => 11.40, 'shipping_total' => 0, // @todo ? 'shipping_tax_total' => 0, // @todo ? From 4c4f26ff4111b1a6eb7d42104a0b1c633829e7c0 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 25 Jul 2017 15:11:32 +0100 Subject: [PATCH 110/224] Changes to allow shipping to be calculated from totals class Moved some items from https://github.com/woocommerce/woocommerce/pull/11889/files to support this. --- includes/class-wc-cart.php | 40 ++++++++--- includes/class-wc-discounts.php | 2 +- includes/class-wc-shipping.php | 72 ++----------------- includes/class-wc-totals.php | 109 ++++++++++++++++++----------- includes/wc-cart-functions.php | 66 +++++++++++++++++ tests/unit-tests/totals/totals.php | 54 ++++++++------ 6 files changed, 208 insertions(+), 135 deletions(-) diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 23e17377e94..3db24df3ed9 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -18,6 +18,12 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Cart { + /** + * This stores the chosen shipping methods for the cart item packages. + * @var array + */ + protected $shipping_methods; + /** @var array Contains an array of cart items. */ public $cart_contents = array(); @@ -295,6 +301,7 @@ class WC_Cart { */ public function empty_cart( $clear_persistent_cart = true ) { $this->cart_contents = array(); + $this->shipping_methods = null; $this->reset( true ); unset( WC()->session->order_awaiting_payment, WC()->session->applied_coupons, WC()->session->coupon_discount_amounts, WC()->session->coupon_discount_tax_amounts, WC()->session->cart ); @@ -1452,15 +1459,32 @@ class WC_Cart { * Uses the shipping class to calculate shipping then gets the totals when its finished. */ public function calculate_shipping() { - if ( $this->needs_shipping() && $this->show_shipping() ) { - WC()->shipping->calculate_shipping( $this->get_shipping_packages() ); - } else { - WC()->shipping->reset_shipping(); - } + $this->shipping_methods = $this->needs_shipping() ? $this->get_chosen_shipping_methods( WC()->shipping->calculate_shipping( $this->get_shipping_packages() ) ) : array(); - // Get totals for the chosen shipping method - $this->shipping_total = WC()->shipping->shipping_total; // Shipping Total - $this->shipping_taxes = WC()->shipping->shipping_taxes; // Shipping Taxes + // Set legacy totals for backwards compatibility with versions prior to 3.2. + $this->shipping_total = WC()->shipping->shipping_total = array_sum( wp_list_pluck( $this->shipping_methods, 'cost' ) ); + $this->shipping_taxes = WC()->shipping->shipping_taxes = wp_list_pluck( $this->shipping_methods, 'taxes' ); + + return $this->shipping_methods; + } + + /** + * Given a set of packages with rates, get the chosen ones only. + * + * @since 3.2.0 + * @param array $calculated_shipping_packages + * @return array + */ + protected function get_chosen_shipping_methods( $calculated_shipping_packages = array() ) { + $chosen_methods = array(); + // Get chosen methods for each package to get our totals. + foreach ( $calculated_shipping_packages as $key => $package ) { + $chosen_method = wc_get_chosen_shipping_method_for_package( $key, $package ); + if ( $chosen_method ) { + $chosen_methods[ $key ] = $package['rates'][ $chosen_method ]; + } + } + return $chosen_methods; } /** diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index c08dc00ed67..3945933a585 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -140,7 +140,7 @@ class WC_Discounts { * @todo is_valid_for_product accepts values - how can we deal with that? * * @since 3.2.0 - * @param WC_Coupon $coupon + * @param WC_Coupon $coupon Coupon object being applied to the items. * @return bool True if applied. */ public function apply_coupon( $coupon ) { diff --git a/includes/class-wc-shipping.php b/includes/class-wc-shipping.php index 0a1e931f8a2..ece466afd5e 100644 --- a/includes/class-wc-shipping.php +++ b/includes/class-wc-shipping.php @@ -26,12 +26,6 @@ class WC_Shipping { /** @var array|null Stores methods loaded into woocommerce. */ public $shipping_methods = null; - /** @var float Stores the cost of shipping */ - public $shipping_total = 0; - - /** @var array Stores an array of shipping taxes. */ - public $shipping_taxes = array(); - /** @var array Stores the shipping classes. */ public $shipping_classes = array(); @@ -237,18 +231,17 @@ class WC_Shipping { /** * Calculate shipping for (multiple) packages of cart items. * - * @param array $packages multi-dimensional array of cart items to calc shipping for + * @param array $packages multi-dimensional array of cart items to calc shipping for. + * @return array Array of calculated packages. */ public function calculate_shipping( $packages = array() ) { - $this->shipping_total = 0; - $this->shipping_taxes = array(); - $this->packages = array(); + $this->packages = array(); if ( ! $this->enabled || empty( $packages ) ) { - return; + return array(); } - // Calculate costs for passed packages + // Calculate costs for passed packages. foreach ( $packages as $package_key => $package ) { $this->packages[ $package_key ] = $this->calculate_shipping_for_package( $package, $package_key ); } @@ -264,58 +257,9 @@ class WC_Shipping { * * @param array $packages The array of packages after shipping costs are calculated. */ - $this->packages = apply_filters( 'woocommerce_shipping_packages', $this->packages ); + $this->packages = array_filter( (array) apply_filters( 'woocommerce_shipping_packages', $this->packages ) ); - if ( ! is_array( $this->packages ) || empty( $this->packages ) ) { - return; - } - - // Get all chosen methods - $chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); - $method_counts = WC()->session->get( 'shipping_method_counts' ); - - // Get chosen methods for each package - foreach ( $this->packages as $i => $package ) { - $chosen_method = false; - $method_count = false; - - if ( ! empty( $chosen_methods[ $i ] ) ) { - $chosen_method = $chosen_methods[ $i ]; - } - - if ( ! empty( $method_counts[ $i ] ) ) { - $method_count = absint( $method_counts[ $i ] ); - } - - if ( sizeof( $package['rates'] ) > 0 ) { - - // If not set, not available, or available methods have changed, set to the DEFAULT option - if ( empty( $chosen_method ) || ! isset( $package['rates'][ $chosen_method ] ) || sizeof( $package['rates'] ) !== $method_count ) { - $chosen_method = apply_filters( 'woocommerce_shipping_chosen_method', $this->get_default_method( $package['rates'], false ), $package['rates'], $chosen_method ); - $chosen_methods[ $i ] = $chosen_method; - $method_counts[ $i ] = sizeof( $package['rates'] ); - do_action( 'woocommerce_shipping_method_chosen', $chosen_method ); - } - - // Store total costs - if ( $chosen_method && isset( $package['rates'][ $chosen_method ] ) ) { - $rate = $package['rates'][ $chosen_method ]; - - // Merge cost and taxes - label and ID will be the same - $this->shipping_total += $rate->cost; - - if ( ! empty( $rate->taxes ) && is_array( $rate->taxes ) ) { - foreach ( array_keys( $this->shipping_taxes + $rate->taxes ) as $key ) { - $this->shipping_taxes[ $key ] = ( isset( $rate->taxes[ $key ] ) ? $rate->taxes[ $key ] : 0 ) + ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ); - } - } - } - } - } - - // Save all chosen methods (array) - WC()->session->set( 'chosen_shipping_methods', $chosen_methods ); - WC()->session->set( 'shipping_method_counts', $method_counts ); + return $this->packages; } /** @@ -396,8 +340,6 @@ class WC_Shipping { */ public function reset_shipping() { unset( WC()->session->chosen_shipping_methods ); - $this->shipping_total = 0; - $this->shipping_taxes = array(); $this->packages = array(); } diff --git a/includes/class-wc-totals.php b/includes/class-wc-totals.php index ec01bb44296..4a8ac0f1031 100644 --- a/includes/class-wc-totals.php +++ b/includes/class-wc-totals.php @@ -49,6 +49,14 @@ class WC_Totals { */ private $fees = array(); + /** + * Shipping costs. + * + * @since 3.2.0 + * @var array + */ + private $shipping = array(); + /** * Discount amounts in cents after calculation for the cart. * @@ -112,13 +120,31 @@ class WC_Totals { $item = $this->get_default_item_props(); $item->key = $cart_item_key; $item->quantity = $cart_item['quantity']; - $item->price = $cart_item['data']->get_price() * $this->precision * $cart_item['quantity']; + $item->price = $this->add_precision( $cart_item['data']->get_price() ) * $cart_item['quantity']; $item->product = $cart_item['data']; $this->items[ $cart_item_key ] = $item; } } } + /** + * Add precision (deep) to a price. + * + * @since 3.2.0 + * @param int|array $value Value to remove precision from. + * @return float + */ + private function add_precision( $value ) { + if ( is_array( $value ) ) { + foreach ( $value as $key => $subvalue ) { + $value[ $key ] = $this->add_precision( $subvalue ); + } + } else { + $value = $value * $this->precision; + } + return $value; + } + /** * Remove precision (deep) from a price. * @@ -173,6 +199,20 @@ class WC_Totals { ); } + /** + * Get default blank set of props used per shipping row. + * + * @since 3.2.0 + * @return array + */ + private function get_default_shipping_props() { + return (object) array( + 'total' => 0, + 'total_tax' => 0, + 'taxes' => array(), + ); + } + /** * Only ran if woocommerce_adjust_non_base_location_prices is true. * @@ -232,27 +272,6 @@ class WC_Totals { } } - /** - * Return array of shipping costs. - * - * @since 3.2.0 - * @return array - */ - private function get_shipping() { - // @todo get this somehow. Where does calc occur? - return array(); - } - - /** - * Get discounts. - * - * @return array - */ - private function get_discounts() { - // @todo fee style API for discounts in cart/checkout. - return array(); - } - /** * Get a single total with or without precision (in cents). * @@ -291,33 +310,25 @@ class WC_Totals { /** * Get all tax rows from items (including shipping and product line items). * - * @todo consider an item object instead of array here - * * @since 3.2.0 * @return array */ private function get_merged_taxes() { $taxes = array(); - foreach ( array_merge( $this->items, $this->fees, $this->get_shipping() ) as $item ) { + foreach ( array_merge( $this->items, $this->fees, $this->shipping ) as $item ) { foreach ( $item->taxes as $rate_id => $rate ) { $taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 ); } } - foreach ( $this->items as $item ) { + foreach ( $this->items + $this->fees as $item ) { foreach ( $item->taxes as $rate_id => $rate ) { $taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate; } } - foreach ( $this->fees as $fee ) { - foreach ( $fee->taxes as $rate_id => $rate ) { - $taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate; - } - } - - foreach ( $this->get_shipping() as $item ) { + foreach ( $this->shipping as $item ) { foreach ( $item->taxes as $rate_id => $rate ) { $taxes[ $rate_id ]['shipping_tax_total'] = $taxes[ $rate_id ]['shipping_tax_total'] + $rate; } @@ -385,6 +396,7 @@ class WC_Totals { * Calculate all discount and coupon amounts. * * @todo Manual discounts. + * @todo record coupon totals and counts for cart. * * @since 3.2.0 * @uses WC_Discounts class. @@ -398,11 +410,18 @@ class WC_Totals { $this->discount_totals = $discounts->get_discounts(); $this->totals['discounts_total'] = array_sum( $this->discount_totals ); - // @todo $this->totals['discounts_tax_total'] = $value; - /*$this->set_coupon_totals( wp_list_pluck( $this->coupons, 'total' ) ); - //$this->set_coupon_tax_totals( wp_list_pluck( $this->coupons, 'total_tax' ) ); - //$this->set_coupon_counts( wp_list_pluck( $this->coupons, 'count' ) );*/ + // See how much tax was 'discounted'. + if ( wc_tax_enabled() ) { + foreach ( $this->discount_totals as $cart_item_key => $discount ) { + $item = $this->items[ $cart_item_key ]; + if ( $item->product->is_taxable() ) { + $tax_rates = $this->get_item_tax_rates( $item ); + $taxes = WC_Tax::calc_tax( $discount, $tax_rates, false ); + $this->totals['discounts_tax_total'] += array_sum( $taxes ); + } + } + } } /** @@ -442,12 +461,13 @@ class WC_Totals { * @todo logic is unqiue to carts. */ private function calculate_fee_totals() { + $this->fees = array(); $this->object->calculate_fees(); foreach ( $this->object->get_fees() as $fee_key => $fee_object ) { $fee = $this->get_default_fee_props(); $fee->object = $fee_object; - $fee->total = $fee->object->amount * $this->precision; + $fee->total = $this->add_precision( $fee->object->amount ); if ( wc_tax_enabled() && $fee->object->taxable ) { $fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class ), false ); @@ -475,7 +495,18 @@ class WC_Totals { * @since 3.2.0 */ private function calculate_shipping_totals() { - //$this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping, 'total' ) ) ) ); + $this->shipping = array(); + + foreach ( $this->object->calculate_shipping() as $key => $shipping_object ) { + $shipping_line = $this->get_default_shipping_props(); + $shipping_line->total = $this->add_precision( $shipping_object->cost ); + $shipping_line->taxes = array_map( array( $this, 'add_precision' ), $shipping_object->taxes ); + $shipping_line->total_tax = array_sum( $shipping_object->taxes ); + $this->shipping[ $key ] = $shipping_line; + } + + $this->set_total( 'shipping_total', array_sum( wp_list_pluck( $this->shipping, 'total' ) ) ); + $this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->shipping, 'total_tax' ) ) ); } /** diff --git a/includes/wc-cart-functions.php b/includes/wc-cart-functions.php index 2f69aa152b9..386424062ff 100644 --- a/includes/wc-cart-functions.php +++ b/includes/wc-cart-functions.php @@ -388,3 +388,69 @@ function wc_get_chosen_shipping_method_ids() { } return $method_ids; } + +/** + * Get chosen method for package from session. + * + * @since 3.2.0 + * @param int $key + * @param array $package + * @return string|bool + */ +function wc_get_chosen_shipping_method_for_package( $key, $package ) { + $chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); + $chosen_method = isset( $chosen_methods[ $key ] ) ? $chosen_methods[ $key ] : false; + $changed = wc_shipping_methods_have_changed( $key, $package ); + // If not set, not available, or available methods have changed, set to the DEFAULT option + if ( ! $chosen_method || $changed || ! isset( $package['rates'][ $chosen_method ] ) ) { + $chosen_method = wc_get_default_shipping_method_for_package( $key, $package, $chosen_method ); + $chosen_methods[ $key ] = $chosen_method; + WC()->session->set( 'chosen_shipping_methods', $chosen_methods ); + do_action( 'woocommerce_shipping_method_chosen', $chosen_method ); + } + return $chosen_method; +} +/** + * Choose the default method for a package. + * + * @since 3.2.0 + * @param string $key + * @param array $package + * @return string + */ +function wc_get_default_shipping_method_for_package( $key, $package, $chosen_method ) { + $rate_keys = array_keys( $package['rates'] ); + $default = current( $rate_keys ); + $coupons = WC()->cart->get_coupons(); + foreach ( $coupons as $coupon ) { + if ( $coupon->get_free_shipping() ) { + foreach ( $rate_keys as $rate_key ) { + if ( 0 === stripos( $rate_key, 'free_shipping' ) ) { + $default = $rate_key; + break; + } + } + break; + } + } + return apply_filters( 'woocommerce_shipping_chosen_method', $default, $package['rates'], $chosen_method ); +} +/** + * See if the methods have changed since the last request. + * + * @since 3.2.0 + * @param int $key + * @param array $package + * @return bool + */ +function wc_shipping_methods_have_changed( $key, $package ) { + // Lookup previous methods from session. + $previous_shipping_methods = WC()->session->get( 'previous_shipping_methods' ); + // Get new and old rates. + $new_rates = array_keys( $package['rates'] ); + $prev_rates = isset( $previous_shipping_methods[ $key ] ) ? $previous_shipping_methods[ $key ] : false; + // Update session. + $previous_shipping_methods[ $key ] = $new_rates; + WC()->session->set( 'previous_shipping_methods', $previous_shipping_methods ); + return $new_rates != $prev_rates; +} diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index f170762bcc6..435cf8af2bc 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -1,18 +1,28 @@ set_discount_type( 'percent' ); $coupon->save(); - $this->tax_rate_ids[] = $tax_rate_id; - $this->products[] = $product; - $this->products[] = $product2; - $this->coupons[] = $coupon; + $this->ids['tax_rate_ids'][] = $tax_rate_id; + $this->ids['products'][] = $product; + $this->ids['products'][] = $product2; + $this->ids['coupons'][] = $coupon; WC()->cart->add_to_cart( $product->get_id(), 1 ); WC()->cart->add_to_cart( $product2->get_id(), 2 ); @@ -63,9 +73,9 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { * Add fees when the fees API is called. */ public function add_cart_fees_callback() { - WC()->cart->add_fee( "test fee", 10, true ); - WC()->cart->add_fee( "test fee 2", 20, true ); - WC()->cart->add_fee( "test fee non-taxable", 10, false ); + WC()->cart->add_fee( 'test fee', 10, true ); + WC()->cart->add_fee( 'test fee 2', 20, true ); + WC()->cart->add_fee( 'test fee non-taxable', 10, false ); } /** @@ -78,15 +88,15 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { update_option( 'woocommerce_calc_taxes', 'no' ); remove_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) ); - foreach ( $this->products as $product ) { + foreach ( $this->ids['products'] as $product ) { $product->delete( true ); } - foreach ( $this->coupons as $coupon ) { + foreach ( $this->ids['coupons'] as $coupon ) { $coupon->delete( true ); } - foreach ( $this->tax_rate_ids as $tax_rate_id ) { + foreach ( $this->ids['tax_rate_ids'] as $tax_rate_id ) { WC_Tax::_delete_tax_rate( $tax_rate_id ); } } @@ -102,18 +112,18 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { 'items_subtotal_tax' => 6.00, 'items_total' => 27.00, 'items_total_tax' => 5.40, - 'total' => 78.40, + 'total' => 90.40, 'taxes' => array( 1 => array( 'tax_total' => 11.40, - 'shipping_tax_total' => 0.00, - ) + 'shipping_tax_total' => 2.00, + ), ), 'tax_total' => 11.40, - 'shipping_total' => 0, // @todo ? - 'shipping_tax_total' => 0, // @todo ? + 'shipping_total' => 10, + 'shipping_tax_total' => 2, 'discounts_total' => 3.00, - 'discounts_tax_total' => 0, // @todo ? + 'discounts_tax_total' => 0.60, ), $this->totals->get_totals() ); } } From 40cb2c9cb0dd83eb14ce747a1ffb7ec4fdb456dc Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 25 Jul 2017 15:24:00 +0100 Subject: [PATCH 111/224] phpcs --- includes/class-wc-discounts.php | 46 +++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 3945933a585..f38f100880a 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -45,6 +45,8 @@ class WC_Discounts { /** * Constructor. + * + * @param array $items Items to discount. */ public function __construct( $items ) { $this->precision = pow( 10, wc_get_price_decimals() ); @@ -65,6 +67,7 @@ class WC_Discounts { * Get discount by key without precision. * * @since 3.2.0 + * @param string $key name of discount row to return. * @return array */ public function get_discount( $key ) { @@ -85,7 +88,7 @@ class WC_Discounts { * Get discounted price of an item without precision. * * @since 3.2.0 - * @param object $item + * @param object $item Get data for this item. * @return float */ public function get_discounted_price( $item ) { @@ -96,7 +99,7 @@ class WC_Discounts { * Get discounted price of an item to precision (in cents). * * @since 3.2.0 - * @param object $item + * @param object $item Get data for this item. * @return float */ public function get_discounted_price_in_cents( $item ) { @@ -172,7 +175,7 @@ class WC_Discounts { /** * Remove precision from a price. * - * @param int $value + * @param int $value Value to remove precision from. * @return float */ protected function remove_precision( $value ) { @@ -182,13 +185,13 @@ class WC_Discounts { /** * Sort by price. * - * @param array $a - * @param array $b + * @param array $a First element. + * @param array $b Second element. * @return int */ protected function sort_by_price( $a, $b ) { $price_1 = $a->price * $a->quantity; - $price_2 = $b->price * $b->quantity;; + $price_2 = $b->price * $b->quantity; if ( $price_1 === $price_2 ) { return 0; } @@ -199,7 +202,7 @@ class WC_Discounts { * Filter out all products which have been fully discounted to 0. * Used as array_filter callback. * - * @param object $item + * @param object $item Get data for this item. * @return bool */ protected function filter_products_with_price( $item ) { @@ -209,7 +212,7 @@ class WC_Discounts { /** * Get items which the coupon should be applied to. * - * @param object $coupon + * @param object $coupon Coupon object. * @return array */ protected function get_items_to_apply_coupon( $coupon ) { @@ -249,9 +252,9 @@ class WC_Discounts { * Apply a discount amount to an item and ensure it does not go negative. * * @since 3.2.0 - * @param object $item - * @param int $discount - * @return int Amount discounted. + * @param object $item Get data for this item. + * @param int $discount Amount of discount. + * @return int Amount discounted. */ protected function add_item_discount( &$item, $discount ) { $discounted_price = $this->get_discounted_price_in_cents( $item ); @@ -264,9 +267,9 @@ class WC_Discounts { * Apply percent discount to items. * * @since 3.2.0 - * @param array $items_to_apply Array of items to apply the coupon to. - * @param int $amount - * @return int total discounted in cents + * @param array $items_to_apply Array of items to apply the coupon to. + * @param int $amount Amount of discount. + * @return int total discounted in cents */ protected function apply_percentage_discount( $items_to_apply, $amount ) { $total_discounted = 0; @@ -283,7 +286,7 @@ class WC_Discounts { * * @since 3.2.0 * @param array $items_to_apply Array of items to apply the coupon to. - * @param int $amount + * @param int $discount Amount of discout. * @return int total discounted in cents */ protected function apply_fixed_product_discount( $items_to_apply, $discount ) { @@ -301,7 +304,7 @@ class WC_Discounts { * * @since 3.2.0 * @param array $items_to_apply Array of items to apply the coupon to. - * @param int $cart_discount + * @param int $cart_discount Fixed discount amount to apply. * @return int total discounted in cents */ protected function apply_fixed_cart_discount( $items_to_apply, $cart_discount ) { @@ -325,12 +328,11 @@ class WC_Discounts { if ( $amount_discounted > 0 && $amount_discounted < $cart_discount ) { $amount_discounted += $this->apply_fixed_cart_discount( $items_to_apply, $cart_discount - $amount_discounted ); } - - /** - * Deal with remaining fractional discounts by splitting it over items - * until the amount is expired, discounting 1 cent at a time. - */ - } elseif ( $cart_discount > 0 ) { + } elseif ( $cart_discount > 0 ) { + /** + * Deal with remaining fractional discounts by splitting it over items + * until the amount is expired, discounting 1 cent at a time. + */ foreach ( $items_to_apply as $item ) { for ( $i = 0; $i < $item->quantity; $i ++ ) { $amount_discounted += $this->add_item_discount( $item, 1 ); From 474799889f682120eb738f2daa3b976aac92d065 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 25 Jul 2017 15:57:58 +0100 Subject: [PATCH 112/224] Disable discounts tests until rewritten --- includes/class-wc-discounts.php | 2 +- tests/unit-tests/discounts/discounts.php | 3 +++ tests/unit-tests/totals/totals.php | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index f38f100880a..6133500ce7b 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -48,7 +48,7 @@ class WC_Discounts { * * @param array $items Items to discount. */ - public function __construct( $items ) { + public function __construct( $items = array() ) { $this->precision = pow( 10, wc_get_price_decimals() ); $this->set_items( $items ); } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 247f5257441..f649e9865f4 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -3,7 +3,10 @@ /** * Test for the discounts class. * @package WooCommerce\Tests\Discounts + * @todo update tests for new 'items' stucture, or handle other data. */ +return; + class WC_Tests_Discounts extends WC_Unit_Test_Case { /** diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index 435cf8af2bc..41ab46be0a4 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -65,7 +65,6 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { add_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) ); - // @todo manual discounts $this->totals = new WC_Totals( WC()->cart ); } From 4960bf0ca4d76cf52ead02805235254835a44d99 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 25 Jul 2017 16:27:57 +0100 Subject: [PATCH 113/224] Update discount tests --- tests/unit-tests/discounts/discounts.php | 47 ++++++++++++++++++------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index f649e9865f4..7bf6bf56d95 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -3,12 +3,35 @@ /** * Test for the discounts class. * @package WooCommerce\Tests\Discounts - * @todo update tests for new 'items' stucture, or handle other data. */ -return; - class WC_Tests_Discounts extends WC_Unit_Test_Case { + protected function get_items_for_discounts_class( $items ) { + $items = array(); + foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { + $item = (object) array( + 'key' => '', + 'quantity' => 0, + 'price' => 0, + 'product' => false, + 'price_includes_tax' => wc_prices_include_tax(), + 'subtotal' => 0, + 'subtotal_tax' => 0, + 'subtotal_taxes' => array(), + 'total' => 0, + 'total_tax' => 0, + 'taxes' => array(), + 'discounted_price' => 0, + ); + $item->key = $cart_item_key; + $item->quantity = $cart_item['quantity']; + $item->price = $this->add_precision( $cart_item['data']->get_price() ) * $cart_item['quantity']; + $item->product = $cart_item['data']; + $items[ $cart_item_key ] = $item; + } + return $items; + } + /** * Test get and set items. */ @@ -27,12 +50,12 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Test setting items to the cart. $discounts = new WC_Discounts(); - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); $this->assertEquals( 1, count( $discounts->get_items() ) ); // Test setting items to an order. $discounts = new WC_Discounts(); - $discounts->set_items( $order->get_items() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); $this->assertEquals( 1, count( $discounts->get_items() ) ); // Empty array of items. @@ -58,7 +81,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $discounts = new WC_Discounts(); $product = WC_Helper_Product::create_simple_product(); WC()->cart->add_to_cart( $product->get_id(), 1 ); - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); // Test applying multiple coupons and getting totals. $coupon = new WC_Coupon; @@ -85,12 +108,12 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { WC()->cart->add_to_cart( $product->get_id(), 2 ); $coupon->set_discount_type( 'fixed_product' ); $coupon->set_amount( 2 ); - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); $discounts->apply_coupon( $coupon ); $this->assertEquals( array( 'test' => 4 ), $discounts->get_applied_coupons() ); $coupon->set_discount_type( 'fixed_cart' ); - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); $discounts->apply_coupon( $coupon ); $this->assertEquals( array( 'test' => 2 ), $discounts->get_applied_coupons() ); @@ -117,19 +140,19 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Apply a percent discount. $coupon->set_discount_type( 'percent' ); - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); $discounts->apply_coupon( $coupon ); $this->assertEquals( 9, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); // Apply a fixed cart coupon. $coupon->set_discount_type( 'fixed_cart' ); - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); $discounts->apply_coupon( $coupon ); $this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); // Apply a fixed product coupon. $coupon->set_discount_type( 'fixed_product' ); - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); $discounts->apply_coupon( $coupon ); $this->assertEquals( 0, $discounts->get_discounted_price( current( $discounts->get_items() ) ) ); @@ -399,7 +422,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $products[] = $product; } - $discounts->set_items( WC()->cart->get_cart() ); + $discounts->set_items( $this->get_items_for_discounts_class() ); foreach ( $test['coupons'] as $coupon_props ) { $coupon = new WC_Coupon; From 689c5e7006d22e0acabdb1225302067e21d0b9d2 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Tue, 25 Jul 2017 08:36:01 -0700 Subject: [PATCH 114/224] Better code --- .../class-wc-product-data-store-cpt.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 359ccca713d..1b5e34d798b 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -1295,7 +1295,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da } } - // SKU needs special handling because it works with partial matches. // Don't auto-generate meta query args for it. $sku_query = false; @@ -1304,14 +1303,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da unset( $query_vars['sku'] ); } - // total_sales needs special handline because the meta key doesn't have the underscore prefix. - // Don't auto generate meta query args for it. - $total_sales_query = false; - if ( isset( $query_vars['total_sales'] ) ) { - $total_sales_query = $query_vars['total_sales']; - unset( $query_vars['total_sales'] ); - } - $wp_query_args = parent::get_wp_query_args( $query_vars ); if ( ! isset( $wp_query_args['date_query'] ) ) { @@ -1331,10 +1322,11 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da } // Manually build the total_sales query if needed. - if ( $total_sales_query ) { + // This query doesn't get auto-generated since the meta key doesn't have the underscore prefix. + if ( isset( $query_vars['total_sales'] ) && '' !== $query_vars['total_sales'] ) { $wp_query_args['meta_query'][] = array( 'key' => 'total_sales', - 'value' => $total_sales_query, + 'value' => absint( $query_vars['total_sales'] ), 'compare' => '=', ); } From b68adf748b36cfa667187c609cbd6b814723a653 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 25 Jul 2017 17:25:06 +0100 Subject: [PATCH 115/224] Tests pass --- includes/class-wc-discounts.php | 37 ++++++++++++++++++++---- includes/class-wc-totals.php | 2 +- tests/unit-tests/discounts/discounts.php | 7 +++-- tests/unit-tests/totals/totals.php | 2 +- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 6133500ce7b..cdc16c5a64a 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -78,10 +78,11 @@ class WC_Discounts { * Get all discount totals with precision. * * @since 3.2.0 + * @param bool $in_cents Should the totals be returned in cents, or without precision. * @return array */ - public function get_discounts() { - return $this->discounts; + public function get_discounts( $in_cents = false ) { + return $in_cents ? $this->discounts : array_map( array( $this, 'remove_precision' ), $this->discounts ); } /** @@ -173,13 +174,39 @@ class WC_Discounts { } /** - * Remove precision from a price. + * Add precision (deep) to a price. * - * @param int $value Value to remove precision from. + * @since 3.2.0 + * @param int|array $value Value to remove precision from. + * @return float + */ + protected function add_precision( $value ) { + if ( is_array( $value ) ) { + foreach ( $value as $key => $subvalue ) { + $value[ $key ] = $this->add_precision( $subvalue ); + } + } else { + $value = $value * $this->precision; + } + return $value; + } + + /** + * Remove precision (deep) from a price. + * + * @since 3.2.0 + * @param int|array $value Value to remove precision from. * @return float */ protected function remove_precision( $value ) { - return wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); + if ( is_array( $value ) ) { + foreach ( $value as $key => $subvalue ) { + $value[ $key ] = $this->remove_precision( $subvalue ); + } + } else { + $value = wc_format_decimal( $value / $this->precision, wc_get_price_decimals() ); + } + return $value; } /** diff --git a/includes/class-wc-totals.php b/includes/class-wc-totals.php index 4a8ac0f1031..f79a176e2a2 100644 --- a/includes/class-wc-totals.php +++ b/includes/class-wc-totals.php @@ -408,7 +408,7 @@ class WC_Totals { $discounts->apply_coupon( $coupon ); } - $this->discount_totals = $discounts->get_discounts(); + $this->discount_totals = $discounts->get_discounts( true ); $this->totals['discounts_total'] = array_sum( $this->discount_totals ); // See how much tax was 'discounted'. diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 7bf6bf56d95..850235154fc 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -6,8 +6,9 @@ */ class WC_Tests_Discounts extends WC_Unit_Test_Case { - protected function get_items_for_discounts_class( $items ) { - $items = array(); + protected function get_items_for_discounts_class() { + $items = array(); + $precision = pow( 10, wc_get_price_decimals() ); foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { $item = (object) array( 'key' => '', @@ -25,7 +26,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { ); $item->key = $cart_item_key; $item->quantity = $cart_item['quantity']; - $item->price = $this->add_precision( $cart_item['data']->get_price() ) * $cart_item['quantity']; + $item->price = $cart_item['data']->get_price() * $precision * $cart_item['quantity']; $item->product = $cart_item['data']; $items[ $cart_item_key ] = $item; } diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index 41ab46be0a4..5789315110f 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -113,7 +113,7 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { 'items_total_tax' => 5.40, 'total' => 90.40, 'taxes' => array( - 1 => array( + $this->ids['tax_rate_ids'][0] => array( 'tax_total' => 11.40, 'shipping_tax_total' => 2.00, ), From ec6d11bd67cfff42c02895e4ddcfe0b205b2c355 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Tue, 25 Jul 2017 10:01:24 -0700 Subject: [PATCH 116/224] Just needs visibility support and cleanup --- .../class-wc-product-data-store-cpt.php | 99 ++++++++++++----- tests/unit-tests/product/functions.php | 100 ++++++++++++++++-- 2 files changed, 169 insertions(+), 30 deletions(-) diff --git a/includes/data-stores/class-wc-product-data-store-cpt.php b/includes/data-stores/class-wc-product-data-store-cpt.php index 1b5e34d798b..44c1d072997 100644 --- a/includes/data-stores/class-wc-product-data-store-cpt.php +++ b/includes/data-stores/class-wc-product-data-store-cpt.php @@ -45,6 +45,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da '_product_attributes', '_virtual', '_downloadable', + '_download_limit', + '_download_expiry', '_featured', '_downloadable_files', '_wc_rating_count', @@ -1257,6 +1259,19 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da } } + public function reviews_allowed_query_where( $where, $wp_query ) { + global $wpdb; + + if ( isset( $wp_query->query_vars['reviews_allowed'] ) && is_bool( $wp_query->query_vars['reviews_allowed'] ) ) { + if ( $wp_query->query_vars['reviews_allowed'] ) { + $where .= " AND $wpdb->posts.comment_status = 'open'"; + } else { + $where .= " AND $wpdb->posts.comment_status = 'closed'"; + } + } + + return $where; + } /** * Get valid WP_Query args from a WC_Product_Query's query variables. @@ -1269,9 +1284,12 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da // Map query vars to ones that get_wp_query_args or WP_Query recognize. $key_mapping = array( - 'status' => 'post_status', - 'page' => 'paged', - 'include' => 'post__in', + 'status' => 'post_status', + 'page' => 'paged', + 'include' => 'post__in', + 'stock_quantity' => 'stock', + 'average_rating' => 'wc_average_rating', + 'review_count' => 'wc_review_count', ); foreach ( $key_mapping as $query_key => $db_key ) { if ( isset( $query_vars[ $query_key ] ) ) { @@ -1284,10 +1302,8 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $boolean_queries = array( 'virtual', 'downloadable', - 'featured', 'sold_individually', 'manage_stock', - 'reviews_allowed', ); foreach ( $boolean_queries as $boolean_query ) { if ( isset( $query_vars[ $boolean_query ] ) && is_bool( $query_vars[ $boolean_query ] ) ) { @@ -1303,6 +1319,13 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da unset( $query_vars['sku'] ); } + // Featured needs special handling because it's stored in terms not meta. + $featured_query = ''; + if ( isset( $query_vars['featured'] ) ) { + $featured_query = $query_vars['featured']; + unset( $query_vars['featured'] ); + } + $wp_query_args = parent::get_wp_query_args( $query_vars ); if ( ! isset( $wp_query_args['date_query'] ) ) { @@ -1312,25 +1335,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $wp_query_args['meta_query'] = array(); } - // Add the special SKU query if needed. - if ( $sku_query ) { - $wp_query_args['meta_query'][] = array( - 'key' => '_sku', - 'value' => $sku_query, - 'compare' => 'LIKE', - ); - } - - // Manually build the total_sales query if needed. - // This query doesn't get auto-generated since the meta key doesn't have the underscore prefix. - if ( isset( $query_vars['total_sales'] ) && '' !== $query_vars['total_sales'] ) { - $wp_query_args['meta_query'][] = array( - 'key' => 'total_sales', - 'value' => absint( $query_vars['total_sales'] ), - 'compare' => '=', - ); - } - // Handle product types. if ( 'variation' === $query_vars['type'] ) { $wp_query_args['post_type'] = 'product_variation'; @@ -1375,6 +1379,25 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da ); } + // Add the special SKU query if needed. + if ( $sku_query ) { + $wp_query_args['meta_query'][] = array( + 'key' => '_sku', + 'value' => $sku_query, + 'compare' => 'LIKE', + ); + } + + // Manually build the total_sales query if needed. + // This query doesn't get auto-generated since the meta key doesn't have the underscore prefix. + if ( isset( $query_vars['total_sales'] ) && '' !== $query_vars['total_sales'] ) { + $wp_query_args['meta_query'][] = array( + 'key' => 'total_sales', + 'value' => absint( $query_vars['total_sales'] ), + 'compare' => '=', + ); + } + if ( ! empty( $query_vars['shipping_class'] ) ) { $wp_query_args['tax_query'][] = array( 'taxonomy' => 'product_shipping_class', @@ -1383,6 +1406,30 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da ); } + if ( '' !== $featured_query ) { + $product_visibility_term_ids = wc_get_product_visibility_term_ids(); + if ( $featured_query ) { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'product_visibility', + 'field' => 'term_taxonomy_id', + 'terms' => array( $product_visibility_term_ids['featured'] ), + ); + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'product_visibility', + 'field' => 'term_taxonomy_id', + 'terms' => array( $product_visibility_term_ids['exclude-from-catalog'] ), + 'operator' => 'NOT IN', + ); + } else { + $wp_query_args['tax_query'][] = array( + 'taxonomy' => 'product_visibility', + 'field' => 'term_taxonomy_id', + 'terms' => array( $product_visibility_term_ids['featured'] ), + 'operator' => 'NOT IN', + ); + } + } + $date_queries = array( 'date_created' => 'post_date', 'date_modified' => 'post_modified', @@ -1406,6 +1453,10 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da $wp_query_args['no_found_rows'] = true; } + if ( isset( $query_vars['reviews_allowed'] ) && is_bool( $query_vars['reviews_allowed'] ) ) { + add_filter( 'posts_where', array( $this, 'reviews_allowed_query_where' ), 10, 2 ); + } + return apply_filters( 'woocommerce_product_data_store_cpt_get_products_query', $wp_query_args, $query_vars, $this ); } diff --git a/tests/unit-tests/product/functions.php b/tests/unit-tests/product/functions.php index 9980f7c8dea..c9149921037 100644 --- a/tests/unit-tests/product/functions.php +++ b/tests/unit-tests/product/functions.php @@ -237,9 +237,10 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { $products = wc_get_products( array( 'return' => 'ids', 'downloadable' => false ) ); $this->assertEquals( array( $product_2->get_id() ), $products ); - // TODO: Fix featured query. - //$products = wc_get_products( array( 'return' => 'ids', 'featured' => true ) ); - //$this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'featured' => true ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'featured' => false ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); $products = wc_get_products( array( 'return' => 'ids', 'sold_individually' => true ) ); $this->assertEquals( array( $product_1->get_id() ), $products ); @@ -250,9 +251,10 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { $products = wc_get_products( array( 'return' => 'ids', 'manage_stock' => true ) ); $this->assertEquals( array( $product_2->get_id() ), $products ); - // TODO: Fix this to use comment status in query args - //$products = wc_get_products( array( 'return' => 'ids', 'reviews_allowed' => true ) ); - //$this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'reviews_allowed' => true ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'reviews_allowed' => false ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); } /** @@ -270,7 +272,25 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { * @since 3.2.0 */ public function test_wc_get_products_stock() { + $product_1 = new WC_Product_Simple; + $product_1->set_manage_stock( true ); + $product_1->set_stock_status( 'instock' ); + $product_1->set_stock_quantity( 5 ); + $product_1->save(); + $product_2 = new WC_Product_Simple; + $product_2->set_manage_stock( true ); + $product_2->set_stock_status( 'outofstock' ); + $product_2->set_stock_quantity( 0 ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'stock_quantity' => 5 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'stock_quantity' => 0 ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'stock_status' => 'outofstock' ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); } /** @@ -279,7 +299,23 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { * @since 3.2.0 */ public function test_wc_get_products_tax() { + $product_1 = new WC_Product_Simple; + $product_1->set_tax_status( 'taxable' ); + $product_1->set_tax_class( 'reduced-rate' ); + $product_1->save(); + $product_2 = new WC_Product_Simple; + $product_2->set_tax_status( 'none' ); + $product_2->set_tax_class( 'standard' ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'tax_status' => 'taxable' ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'tax_status' => 'none' ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'tax_class' => 'reduced-rate' ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); } /** @@ -288,7 +324,21 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { * @since 3.2.0 */ public function test_wc_get_products_shipping_class() { + $shipping_class_1 = wp_insert_term( 'Bulky', 'product_shipping_class' ); + $shipping_class_2 = wp_insert_term( 'Standard', 'product_shipping_class' ); + $product_1 = new WC_Product_Simple; + $product_1->set_shipping_class_id( $shipping_class_1['term_id'] ); + $product_1->save(); + + $product_2 = new WC_Product_Simple; + $product_2->set_shipping_class_id( $shipping_class_2['term_id'] ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'shipping_class' => 'bulky' ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'shipping_class' => 'standard' ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); } /** @@ -297,7 +347,27 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { * @since 3.2.0 */ public function test_wc_get_products_download() { + $product_1 = new WC_Product_Simple; + $product_1->set_downloadable( true ); + $product_1->set_download_limit( 5 ); + $product_1->set_download_expiry( 90 ); + $product_1->save(); + $product_2 = new WC_Product_Simple; + $product_2->set_downloadable( true ); + $product_2->set_download_limit( -1 ); + $product_2->set_download_expiry( -1 ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'download_limit' => 5 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'download_limit' => -1 ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'download_expiry' => 90 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'download_expiry' => -1 ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); } /** @@ -306,7 +376,25 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { * @since 3.2.0 */ public function test_wc_get_products_reviews() { + $product_1 = new WC_Product_Simple; + $product_1->set_average_rating( 5.0 ); + $product_1->set_review_count( 5 ); + $product_1->save(); + $product_2 = new WC_Product_Simple; + $product_2->set_average_rating( 3.0 ); + $product_2->set_review_count( 1 ); + $product_2->save(); + + $products = wc_get_products( array( 'return' => 'ids', 'average_rating' => 5.0 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'average_rating' => 3.0 ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); + + $products = wc_get_products( array( 'return' => 'ids', 'review_count' => 5 ) ); + $this->assertEquals( array( $product_1->get_id() ), $products ); + $products = wc_get_products( array( 'return' => 'ids', 'review_count' => 1 ) ); + $this->assertEquals( array( $product_2->get_id() ), $products ); } /** From fe0c190894aa3943f72c15a46e73c5858ced29f7 Mon Sep 17 00:00:00 2001 From: Kathy Darling Date: Tue, 25 Jul 2017 14:43:26 -0400 Subject: [PATCH 117/224] Move settings init/save to load-$settings hook. Closes #16221 --- includes/admin/class-wc-admin-menus.php | 23 ++++++++++++++++++++++ includes/admin/class-wc-admin-settings.php | 21 -------------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/includes/admin/class-wc-admin-menus.php b/includes/admin/class-wc-admin-menus.php index 09aee416b64..1513a6c261b 100644 --- a/includes/admin/class-wc-admin-menus.php +++ b/includes/admin/class-wc-admin-menus.php @@ -86,8 +86,31 @@ class WC_Admin_Menus { * Loads gateways and shipping methods into memory for use within settings. */ public function settings_page_init() { + global $current_tab, $current_section; + WC()->payment_gateways(); WC()->shipping(); + + // Include settings pages + WC_Admin_Settings::get_settings_pages(); + + // Get current tab/section + $current_tab = empty( $_GET['tab'] ) ? 'general' : sanitize_title( $_GET['tab'] ); + $current_section = empty( $_REQUEST['section'] ) ? '' : sanitize_title( $_REQUEST['section'] ); + + // Save settings if data has been posted + if ( ! empty( $_POST ) ) { + WC_Admin_Settings::save(); + } + + // Add any posted messages + if ( ! empty( $_GET['wc_error'] ) ) { + WC_Admin_Settings::add_error( stripslashes( $_GET['wc_error'] ) ); + } + + if ( ! empty( $_GET['wc_message'] ) ) { + WC_Admin_Settings::add_message( stripslashes( $_GET['wc_message'] ) ); + } } /** diff --git a/includes/admin/class-wc-admin-settings.php b/includes/admin/class-wc-admin-settings.php index e8fb57e0204..1828bcaac03 100644 --- a/includes/admin/class-wc-admin-settings.php +++ b/includes/admin/class-wc-admin-settings.php @@ -141,27 +141,6 @@ class WC_Admin_Settings { 'i18n_nav_warning' => __( 'The changes you made will be lost if you navigate away from this page.', 'woocommerce' ), ) ); - // Include settings pages - self::get_settings_pages(); - - // Get current tab/section - $current_tab = empty( $_GET['tab'] ) ? 'general' : sanitize_title( $_GET['tab'] ); - $current_section = empty( $_REQUEST['section'] ) ? '' : sanitize_title( $_REQUEST['section'] ); - - // Save settings if data has been posted - if ( ! empty( $_POST ) ) { - self::save(); - } - - // Add any posted messages - if ( ! empty( $_GET['wc_error'] ) ) { - self::add_error( stripslashes( $_GET['wc_error'] ) ); - } - - if ( ! empty( $_GET['wc_message'] ) ) { - self::add_message( stripslashes( $_GET['wc_message'] ) ); - } - // Get tabs for the settings page $tabs = apply_filters( 'woocommerce_settings_tabs_array', array() ); From 15c4910014669b0e6242c3bb612d619b3c2342f9 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 25 Jul 2017 21:03:37 +0100 Subject: [PATCH 118/224] Fix notices --- templates/order/form-tracking.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/order/form-tracking.php b/templates/order/form-tracking.php index 72c6e3b28fe..4851367f107 100644 --- a/templates/order/form-tracking.php +++ b/templates/order/form-tracking.php @@ -13,7 +13,7 @@ * @see https://docs.woocommerce.com/document/template-structure/ * @author WooThemes * @package WooCommerce/Templates - * @version 1.6.4 + * @version 3.2.0 */ if ( ! defined( 'ABSPATH' ) ) { @@ -28,8 +28,8 @@ global $post;

    -

    -

    +

    +

    From dfee2c0cbedc29511de25ba50afa82e3477716e0 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Tue, 25 Jul 2017 14:44:54 -0700 Subject: [PATCH 119/224] Maybe integrate cart and total classes --- includes/class-wc-cart-item.php | 4 ++-- includes/class-wc-totals.php | 9 +++++++-- tests/unit-tests/totals/totals.php | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/includes/class-wc-cart-item.php b/includes/class-wc-cart-item.php index 2ba4afe31bb..694a87cd652 100644 --- a/includes/class-wc-cart-item.php +++ b/includes/class-wc-cart-item.php @@ -51,7 +51,7 @@ class WC_Cart_Item implements ArrayAccess { } /** - * Gets price of the product. + * Gets weight of the product. * @return float */ public function get_weight() { @@ -59,7 +59,7 @@ class WC_Cart_Item implements ArrayAccess { } /** - * Gets price of the product. + * Gets tax class of the product. * @return float */ public function get_tax_class() { diff --git a/includes/class-wc-totals.php b/includes/class-wc-totals.php index f79a176e2a2..fff3c50a72b 100644 --- a/includes/class-wc-totals.php +++ b/includes/class-wc-totals.php @@ -448,8 +448,8 @@ class WC_Totals { $this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) ); $this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) ); - $this->object->subtotal = array_sum( wp_list_pluck( $this->items, 'total' ) ) + array_sum( wp_list_pluck( $this->items, 'total_tax' ) ); - $this->object->subtotal_ex_tax = array_sum( wp_list_pluck( $this->items, 'total' ) ); + $this->object->subtotal = $this->get_total( 'items_total' ) + $this->get_total( 'items_total_tax' ); + $this->object->subtotal_ex_tax = $this->get_total( 'items_total' ); } /** @@ -519,5 +519,10 @@ class WC_Totals { $this->set_total( 'tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'tax_total' ) ) ); $this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'shipping_tax_total' ) ) ); $this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + $this->get_total( 'tax_total', true ) + $this->get_total( 'shipping_tax_total', true ) ) ); + + $this->object->total = $this->get_total( 'total' ); + $this->object->tax_total = $this->get_total( 'tax_total' ); + $this->object->shipping_total = $this->get_total( 'shipping_total' ); + $this->object->shipping_tax_total = $this->get_total( 'shipping_tax_total' ); } } diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index 5789315110f..b226b61c441 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -28,6 +28,8 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { * Setup the cart for totals calculation. */ public function setUp() { + $this->ids = array(); + $tax_rate = array( 'tax_rate_country' => '', 'tax_rate_state' => '', @@ -93,6 +95,7 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { foreach ( $this->ids['coupons'] as $coupon ) { $coupon->delete( true ); + wp_cache_delete( WC_Cache_Helper::get_cache_prefix( 'coupons' ) . 'coupon_id_from_code_' . $coupon->get_code(), 'coupons' ); } foreach ( $this->ids['tax_rate_ids'] as $tax_rate_id ) { @@ -125,4 +128,23 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { 'discounts_tax_total' => 0.60, ), $this->totals->get_totals() ); } + + /** + * Test that cart totals get updated. + */ + public function test_cart_totals() { + $cart = WC()->cart; + + $this->assertEquals( 40.00, $cart->fee_total ); + $this->assertEquals( 27.00, $cart->cart_contents_total ); + $this->assertEquals( 90.40, $cart->total ); + $this->assertEquals( 32.40, $cart->subtotal ); + $this->assertEquals( 27.00, $cart->subtotal_ex_tax ); + $this->assertEquals( 11.40, $cart->tax_total ); + $this->assertEquals( 3.00, $cart->discount_cart ); + $this->assertEquals( 0.60, $cart->discount_cart_tax ); + $this->assertEquals( 40.00, $cart->fee_total ); + $this->assertEquals( 10, $cart->shipping_total ); + $this->assertEquals( 2, $cart->shipping_tax_total ); + } } From 2573adedd5210c8dd8053c7c6f282e8172740824 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 25 Jul 2017 22:36:41 -0300 Subject: [PATCH 120/224] Moved coupon validation logic to WC_Discounts --- includes/class-wc-discounts.php | 431 ++++++++++++++++++++++- tests/unit-tests/discounts/discounts.php | 51 +-- 2 files changed, 446 insertions(+), 36 deletions(-) diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 26b9b56b6d1..da074123b4e 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -132,10 +132,10 @@ class WC_Discounts { 'product' => false, ); if ( is_a( $raw_item, 'WC_Cart_Item' ) ) { - //$item->quantity = $raw_item->get_quantity(); - //$item->price = $raw_item->get_price() * $raw_item->get_quantity(); - //$item->is_taxable = $raw_item->is_taxable(); - //$item->tax_class = $raw_item->get_tax_class(); + // $item->quantity = $raw_item->get_quantity(); + // $item->price = $raw_item->get_price() * $raw_item->get_quantity(); + // $item->is_taxable = $raw_item->is_taxable(); + // $item->tax_class = $raw_item->get_tax_class(); // @todo } elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) { $item->key = $raw_item->get_id(); @@ -159,11 +159,10 @@ class WC_Discounts { * Apply a discount to all items using a coupon. * * @todo Coupon class has lots of WC()->cart calls and needs decoupling. This makes 'is valid' hard to use here. - * @todo is_valid_for_product accepts values - how can we deal with that? * * @since 3.2.0 * @param WC_Coupon $coupon - * @return bool True if applied. + * @return bool|WP_Error True if applied or WP_Error instance in failure. */ public function apply_coupon( $coupon ) { if ( ! is_a( $coupon, 'WC_Coupon' ) ) { @@ -174,8 +173,12 @@ class WC_Discounts { $this->applied_coupons[ $coupon->get_code() ] = 0; } + $is_coupon_valid = $this->is_coupon_valid( $coupon ); + if ( is_wp_error( $is_coupon_valid ) ) { + return $is_coupon_valid; + } + // @todo how can we support the old woocommerce_coupon_get_discount_amount filter? - // @todo is valid for product - filter items here and pass to function? $items_to_apply = $this->get_items_to_apply_coupon( $coupon ); switch ( $coupon->get_discount_type() ) { @@ -204,13 +207,13 @@ class WC_Discounts { /** * Sort by price. * - * @param array $a - * @param array $b + * @param array $a + * @param array $b * @return int */ protected function sort_by_price( $a, $b ) { $price_1 = $a->price * $a->quantity; - $price_2 = $b->price * $b->quantity;; + $price_2 = $b->price * $b->quantity; if ( $price_1 === $price_2 ) { return 0; } @@ -243,11 +246,13 @@ class WC_Discounts { $limit_usage_qty = $coupon->get_limit_usage_to_x_items(); } + $cart_items = $this->get_cart_items_backwards_compatibility(); + foreach ( $this->items as $item ) { if ( 0 === $this->get_discounted_price_in_cents( $item ) ) { continue; } - if ( ! $coupon->is_valid_for_product( $item->product ) && ! $coupon->is_valid_for_cart() ) { // @todo is this enough? + if ( ! $coupon->is_valid_for_product( $item->product, $cart_items[ $item->key ] ) && ! $coupon->is_valid_for_cart() ) { // @todo is this enough? continue; } if ( $limit_usage_qty && $applied_count > $limit_usage_qty ) { @@ -352,7 +357,7 @@ class WC_Discounts { * Deal with remaining fractional discounts by splitting it over items * until the amount is expired, discounting 1 cent at a time. */ - } elseif ( $cart_discount > 0 ) { + } elseif ( $cart_discount > 0 ) { foreach ( $items_to_apply as $item ) { for ( $i = 0; $i < $item->quantity; $i ++ ) { $amount_discounted += $this->add_item_discount( $item, 1 ); @@ -369,4 +374,406 @@ class WC_Discounts { return $amount_discounted; } + + /* + |-------------------------------------------------------------------------- + | Validation & Error Handling + |-------------------------------------------------------------------------- + */ + + /** + * Ensure coupon exists or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_exists( $coupon ) { + if ( ! $coupon->get_id() ) { + /* translators: %s: coupon code */ + throw new Exception( sprintf( __( 'Coupon "%s" does not exist!', 'woocommerce' ), $coupon->get_code() ), 105 ); + } + + return true; + } + + /** + * Ensure coupon usage limit is valid or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_usage_limit( $coupon ) { + if ( $coupon->get_usage_limit() > 0 && $coupon->get_usage_count() >= $coupon->get_usage_limit() ) { + throw new Exception( __( 'Coupon usage limit has been reached.', 'woocommerce' ), 106 ); + } + + return true; + } + + /** + * Ensure coupon user usage limit is valid or throw exception. + * + * Per user usage limit - check here if user is logged in (against user IDs). + * Checked again for emails later on in WC_Cart::check_customer_coupons(). + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @param int $user_id User ID. + * @return bool + */ + protected function validate_coupon_user_usage_limit( $coupon, $user_id = 0 ) { + if ( empty( $user_id ) ) { + $user_id = get_current_user_id(); + } + + if ( $coupon->get_usage_limit_per_user() > 0 && is_user_logged_in() && $coupon->get_id() && $coupon->get_data_store() ) { + $date_store = $coupon->get_data_store(); + $usage_count = $date_store->get_usage_by_user_id( $coupon, $user_id ); + if ( $usage_count >= $coupon->get_usage_limit_per_user() ) { + throw new Exception( __( 'Coupon usage limit has been reached.', 'woocommerce' ), 106 ); + } + } + + return true; + } + + /** + * Ensure coupon date is valid or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_expiry_date( $coupon ) { + if ( $coupon->get_date_expires() && current_time( 'timestamp', true ) > $coupon->get_date_expires()->getTimestamp() ) { + throw new Exception( __( 'This coupon has expired.', 'woocommerce' ), 107 ); + } + + return true; + } + + /** + * Ensure coupon amount is valid or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @param float $subtotal Items subtotal. + * @return bool + */ + protected function validate_coupon_minimum_amount( $coupon, $subtotal = 0 ) { + if ( $coupon->get_minimum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_minimum_amount', $coupon->get_minimum_amount() > $subtotal, $coupon, $subtotal ) ) { + /* translators: %s: coupon minimum amount */ + throw new Exception( sprintf( __( 'The minimum spend for this coupon is %s.', 'woocommerce' ), wc_price( $coupon->get_minimum_amount() ) ), 108 ); + } + + return true; + } + + /** + * Ensure coupon amount is valid or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @param float $subtotal Items subtotal. + * @return bool + */ + protected function validate_coupon_maximum_amount( $coupon, $subtotal = 0 ) { + if ( $coupon->get_maximum_amount() > 0 && apply_filters( 'woocommerce_coupon_validate_maximum_amount', $coupon->get_maximum_amount() < $subtotal, $coupon ) ) { + /* translators: %s: coupon maximum amount */ + throw new Exception( sprintf( __( 'The maximum spend for this coupon is %s.', 'woocommerce' ), wc_price( $coupon->get_maximum_amount() ) ), 112 ); + } + + return true; + } + + /** + * Ensure coupon is valid for products in the list is valid or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_product_ids( $coupon ) { + if ( count( $coupon->get_product_ids() ) > 0 ) { + $valid = false; + + foreach ( $this->items as $item ) { + if ( $item->product && in_array( $item->product->get_id(), $coupon->get_product_ids(), true ) || in_array( $item->product->get_parent_id(), $coupon->get_product_ids(), true ) ) { + $valid = true; + break; + } + } + + if ( ! $valid ) { + throw new Exception( __( 'Sorry, this coupon is not applicable to selected products.', 'woocommerce' ), 109 ); + } + } + + return true; + } + + /** + * Ensure coupon is valid for product categories in the list is valid or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_product_categories( $coupon ) { + if ( count( $coupon->get_product_categories() ) > 0 ) { + $valid = false; + + foreach ( $this->items as $item ) { + if ( $coupon->get_exclude_sale_items() && $item->product && $item->product->is_on_sale() ) { + continue; + } + + $product_cats = wc_get_product_cat_ids( $item->product->get_id() ); + + // If we find an item with a cat in our allowed cat list, the coupon is valid. + if ( count( array_intersect( $product_cats, $coupon->get_product_categories() ) ) > 0 ) { + $valid = true; + break; + } + } + + if ( ! $valid ) { + throw new Exception( __( 'Sorry, this coupon is not applicable to selected products.', 'woocommerce' ), 109 ); + } + } + + return true; + } + + /** + * Ensure coupon is valid for sale items in the list is valid or throw exception. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_sale_items( $coupon ) { + if ( $coupon->get_exclude_sale_items() ) { + $valid = false; + + foreach ( $this->items as $item ) { + if ( $item->product && ! $item->product->is_on_sale() ) { + $valid = true; + break; + } + } + + if ( ! $valid ) { + throw new Exception( __( 'Sorry, this coupon is not valid for sale items.', 'woocommerce' ), 110 ); + } + } + + return true; + } + + /** + * All exclusion rules must pass at the same time for a product coupon to be valid. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_excluded_items( $coupon ) { + if ( ! $this->items && $coupon->is_type( wc_get_product_coupon_types() ) ) { + $valid = false; + + $cart_items = $this->get_cart_items_backwards_compatibility(); + foreach ( $this->items as $item ) { + if ( $item->product && $coupon->is_valid_for_product( $item->product, $cart_items[ $item->key ] ) ) { + $valid = true; + break; + } + } + + if ( ! $valid ) { + throw new Exception( __( 'Sorry, this coupon is not applicable to selected products.', 'woocommerce' ), 109 ); + } + } + + return true; + } + + /** + * Cart discounts cannot be added if non-eligible product is found. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_eligible_items( $coupon ) { + if ( ! $coupon->is_type( wc_get_product_coupon_types() ) ) { + $this->validate_coupon_excluded_product_ids( $coupon ); + $this->validate_coupon_excluded_product_categories( $coupon ); + } + + return true; + } + + /** + * Exclude products. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_excluded_product_ids( $coupon ) { + // Exclude Products. + if ( count( $coupon->get_excluded_product_ids() ) > 0 ) { + $products = array(); + + foreach ( $this->items as $item ) { + if ( $item->product && in_array( $item->product->get_id(), $coupon->get_excluded_product_ids(), true ) || in_array( $item->product->get_parent_id(), $coupon->get_excluded_product_ids(), true ) ) { + $products[] = $item->product->get_name(); + } + } + + if ( ! empty( $products ) ) { + /* translators: %s: products list */ + throw new Exception( sprintf( __( 'Sorry, this coupon is not applicable to the products: %s.', 'woocommerce' ), implode( ', ', $products ) ), 113 ); + } + } + + return true; + } + + /** + * Exclude categories from product list. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool + */ + protected function validate_coupon_excluded_product_categories( $coupon ) { + if ( count( $coupon->get_excluded_product_categories() ) > 0 ) { + $categories = array(); + + foreach ( $this->items as $item ) { + if ( $coupon->get_exclude_sale_items() && $item->product && $item->product->is_on_sale() ) { + continue; + } + + $product_cats = wc_get_product_cat_ids( $item->product->get_id() ); + $cat_id_list = array_intersect( $product_cats, $coupon->get_excluded_product_categories() ); + if ( count( $cat_id_list ) > 0 ) { + foreach ( $cat_id_list as $cat_id ) { + $cat = get_term( $cat_id, 'product_cat' ); + $categories[] = $cat->name; + } + } + } + + if ( ! empty( $categories ) ) { + /* translators: %s: categories list */ + throw new Exception( sprintf( __( 'Sorry, this coupon is not applicable to the categories: %s.', 'woocommerce' ), implode( ', ', array_unique( $categories ) ) ), 114 ); + } + } + + return true; + } + + /** + * Check if a coupon is valid. + * + * @since 3.2.0 + * @throws Exception Error message. + * @param WC_Coupon $coupon Coupon data. + * @return bool|WP_Error + */ + public function is_coupon_valid( $coupon ) { + try { + $this->validate_coupon_exists( $coupon ); + $this->validate_coupon_usage_limit( $coupon ); + $this->validate_coupon_user_usage_limit( $coupon ); + $this->validate_coupon_expiry_date( $coupon ); + $this->validate_coupon_minimum_amount( $coupon ); + $this->validate_coupon_maximum_amount( $coupon ); + $this->validate_coupon_product_ids( $coupon ); + $this->validate_coupon_product_categories( $coupon ); + $this->validate_coupon_sale_items( $coupon ); + $this->validate_coupon_excluded_items( $coupon ); + $this->validate_coupon_eligible_items( $coupon ); + + if ( ! apply_filters( 'woocommerce_discount_is_coupon_valid', true, $coupon, $this ) ) { + throw new Exception( __( 'Coupon is not valid.', 'woocommerce' ), 100 ); + } + } catch ( Exception $e ) { + $error_message = $e->getMessage(); + $error_code = $e->getCode(); + + /** + * Coupon error message. + * + * Codes: + * - 100: Invalid filtered. + * - 101: Invalid removed. + * - 102: Not yours removed. + * - 103: Already applied. + * - 104: Individual use only. + * - 105: Not exists. + * - 106: Usage limit reached. + * - 107: Expired. + * - 108: Minimum spend limit not met. + * - 109: Not applicable. + * - 110: Not valid for sale items. + * - 111: Missing coupon code. + * - 112: Maximum spend limit met. + * - 113: Excluded products. + * - 114: Excluded categories. + * + * @param string $error_message Error message. + * @param int $error_code Error code. + * @param WC_Coupon $coupon Coupon data. + */ + $message = apply_filters( 'woocommerce_coupon_error', $error_message, $error_code, $coupon ); + + return new WP_Error( 'invalid_coupon', $message, array( + 'status' => 400, + ) ); + } // End try(). + + return true; + } + + /** + * Backwards compatibility method to get cart items. + * + * @return array + */ + protected function get_cart_items_backwards_compatibility() { + $items = array(); + + foreach ( $this->items as $item ) { + $is_variable = $item->product->is_type( 'variation' ); + $items[ $item->key ] = array( + 'key' => $item->key, + 'product_id' => $is_variable ? $item->product->get_parent_id() : $item->product->get_id(), + 'variation_id' => $is_variable ? $item->product->get_id() : 0, + 'variation' => $is_variable ? $item->product->get_variation_attributes() : array(), + 'quantity' => $item->quantity, + 'data' => $item->product, + ); + } + + return $items; + } } diff --git a/tests/unit-tests/discounts/discounts.php b/tests/unit-tests/discounts/discounts.php index 247f5257441..49238d92c31 100644 --- a/tests/unit-tests/discounts/discounts.php +++ b/tests/unit-tests/discounts/discounts.php @@ -58,15 +58,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $discounts->set_items( WC()->cart->get_cart() ); // Test applying multiple coupons and getting totals. - $coupon = new WC_Coupon; - $coupon->set_code( 'test' ); + $coupon = WC_Helper_Coupon::create_coupon( 'test' ); $coupon->set_amount( 50 ); $coupon->set_discount_type( 'percent' ); $discounts->apply_coupon( $coupon ); $this->assertEquals( array( 'test' => 5 ), $discounts->get_applied_coupons() ); - $coupon2 = new WC_Coupon; + $coupon2 = WC_Helper_Coupon::create_coupon( 'test2' ); $coupon2->set_code( 'test2' ); $coupon2->set_amount( 50 ); $coupon2->set_discount_type( 'percent' ); @@ -94,6 +93,8 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Cleanup. WC()->cart->empty_cart(); $product->delete( true ); + $coupon->delete( true ); + $coupon2->delete( true ); } /** @@ -108,8 +109,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $product->save(); WC()->cart->empty_cart(); WC()->cart->add_to_cart( $product->get_id(), 1 ); - $coupon = new WC_Coupon; - $coupon->set_code( 'test' ); + $coupon = WC_Helper_Coupon::create_coupon( 'test' ); $coupon->set_amount( 10 ); // Apply a percent discount. @@ -133,6 +133,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { // Cleanup. WC()->cart->empty_cart(); $product->delete( true ); + $coupon->delete( true ); } /** @@ -160,14 +161,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 1, - ) + ), ), 'coupons' => array( array( 'code' => 'test', 'discount_type' => 'percent', 'amount' => '20', - ) + ), ), 'expected_total_discount' => 2, ), @@ -177,14 +178,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 2, - ) + ), ), 'coupons' => array( array( 'code' => 'test', 'discount_type' => 'fixed_cart', 'amount' => '10', - ) + ), ), 'expected_total_discount' => 10, ), @@ -198,14 +199,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 1, - ) + ), ), 'coupons' => array( array( 'code' => 'test', 'discount_type' => 'fixed_cart', 'amount' => '10', - ) + ), ), 'expected_total_discount' => 10, ), @@ -223,14 +224,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 1, - ) + ), ), 'coupons' => array( array( 'code' => 'test', 'discount_type' => 'fixed_cart', 'amount' => '10', - ) + ), ), 'expected_total_discount' => 10, ), @@ -248,14 +249,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 2, - ) + ), ), 'coupons' => array( array( 'code' => 'test', 'discount_type' => 'fixed_cart', 'amount' => '10', - ) + ), ), 'expected_total_discount' => 10, ), @@ -305,14 +306,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 1, - ) + ), ), 'coupons' => array( array( 'code' => 'test', 'discount_type' => 'fixed_cart', 'amount' => '10', - ) + ), ), 'expected_total_discount' => 10, ), @@ -330,14 +331,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 1, 'qty' => 1, - ) + ), ), 'coupons' => array( array( 'code' => 'test', 'discount_type' => 'fixed_cart', 'amount' => '1', - ) + ), ), 'expected_total_discount' => 1, ), @@ -347,7 +348,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 2, - ) + ), ), 'coupons' => array( array( @@ -355,7 +356,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { 'discount_type' => 'percent', 'amount' => '10', 'limit_usage_to_x_items' => 1, - ) + ), ), 'expected_total_discount' => 1, ), @@ -369,7 +370,7 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { array( 'price' => 10, 'qty' => 2, - ) + ), ), 'coupons' => array( array( @@ -377,12 +378,14 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { 'discount_type' => 'percent', 'amount' => '10', 'limit_usage_to_x_items' => 1, - ) + ), ), 'expected_total_discount' => 1, ), ); + $coupon = WC_Helper_Coupon::create_coupon( 'test' ); + foreach ( $tests as $test_index => $test ) { $discounts = new WC_Discounts(); $products = array(); @@ -399,7 +402,6 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { $discounts->set_items( WC()->cart->get_cart() ); foreach ( $test['coupons'] as $coupon_props ) { - $coupon = new WC_Coupon; $coupon->set_props( $coupon_props ); $discounts->apply_coupon( $coupon ); } @@ -416,5 +418,6 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case { WC_Tax::_delete_tax_rate( $tax_rate_id ); update_option( 'woocommerce_calc_taxes', 'no' ); + $coupon->delete( true ); } } From e44120581168dc00d022c285e68288d47b230d6a Mon Sep 17 00:00:00 2001 From: Daniel Rey Lopez Date: Wed, 26 Jul 2017 11:01:17 +0100 Subject: [PATCH 121/224] Fixes incorrect "name" property of the State + ' + '' @@ -3940,7 +3939,7 @@ S2.define('select2/dropdown/search',[ '' + '' + + ' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' + '' ); diff --git a/assets/js/selectWoo/selectWoo.full.min.js b/assets/js/selectWoo/selectWoo.full.min.js index 5e509a2bcc0..46b146cdca9 100644 --- a/assets/js/selectWoo/selectWoo.full.min.js +++ b/assets/js/selectWoo/selectWoo.full.min.js @@ -1 +1 @@ -/*! Select2 5.0.0 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(b,c){return void 0===c&&(c="undefined"!=typeof window?require("jquery"):require("jquery")(b)),a(c),c}:a(jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return v.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o=b&&b.split("/"),p=t.map,q=p&&p["*"]||{};if(a){for(a=a.split("/"),g=a.length-1,t.nodeIdCompat&&x.test(a[g])&&(a[g]=a[g].replace(x,"")),"."===a[0].charAt(0)&&o&&(n=o.slice(0,o.length-1),a=n.concat(a)),k=0;k0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}if((o||q)&&p){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),o)for(l=o.length;l>0;l-=1)if((e=p[o.slice(0,l).join("/")])&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&q&&q[d]&&(i=q[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=w.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),o.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){r[a]=b}}function j(a){if(e(s,a)){var c=s[a];delete s[a],u[a]=!0,n.apply(b,c)}if(!e(r,a)&&!e(u,a))throw new Error("No "+a);return r[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return a?k(a):[]}function m(a){return function(){return t&&t.config&&t.config[a]||{}}}var n,o,p,q,r={},s={},t={},u={},v=Object.prototype.hasOwnProperty,w=[].slice,x=/\.js$/;p=function(a,b){var c,d=k(a),e=d[0],g=b[1];return a=d[1],e&&(e=f(e,g),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(g)):f(a,g):(a=f(a,g),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},q={require:function(a){return g(a)},exports:function(a){var b=r[a];return void 0!==b?b:r[a]={}},module:function(a){return{id:a,uri:"",exports:r[a],config:m(a)}}},n=function(a,c,d,f){var h,k,m,n,o,t,v,w=[],x=typeof d;if(f=f||a,t=l(f),"undefined"===x||"function"===x){for(c=!c.length&&d.length?["require","exports","module"]:c,o=0;o0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h":">",'"':""","'":"'","/":"/"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('
      ');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a(''),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()});b.$results.find(".select2-results__option[data-selected]").each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("data-selected","true"):b.attr("data-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"option","data-selected":"false"};b.disabled&&(delete d["data-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["data-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d["aria-label"]=b.text,delete d["data-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";a(h);this.template(b,h);for(var i=[],j=0;j",{class:"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("unselect",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("data-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[data-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):h-g<0&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[data-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted").attr("aria-selected","true"),d.$results.attr("aria-activedescendant",a.element.attr("id"))}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[data-selected]",function(b){var c=a(this),e=c.data("data");if("true"===c.attr("data-selected"))return void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{}));d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[data-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2-results__option--highlighted").attr("aria-selected","false"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){return this.$results.find(".select2-results__option--highlighted")},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[data-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),c<=2?this.$results.scrollTop(0):(g>this.$results.outerHeight()||g<0)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id,a.id+"-results"),f=this.options.get("minimumResultsForSearch")===1/0;this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){f&&d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),f&&d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),window.setTimeout(function(){d.$selection.focus()},1),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2");a(".select2.select2-container--open").each(function(){var b=a(this);this!=d[0]&&b.data("element").select2("close")})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){b.find(".selection").append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html(''),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("keydown",function(b){!a.isOpen()&&b.which>=48&&b.which<=90&&a.open()}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},e.prototype.selectionContainer=function(){return a("")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('
        '),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},d.prototype.selectionContainer=function(){return a('
      • ')},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d1||c)return a.call(this,b);this.clear();var d=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(d)},b}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e0||0===c.length)){var d=a('×');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this,f=b.id+"-results";a.call(this,b,d),b.on("open",function(){e.$search.attr("aria-owns",f),e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){if(a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented(),a.which===c.BACKSPACE&&""===e.$search.val()){var b=e.$searchContainer.prev(".select2-selection__choice");if(b.length>0){var d=b.data("data");e.searchRemoveChoice(d),a.preventDefault()}}});var g=document.documentMode,h=g&&g<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){if(h)return void e.$selection.off("input.search input.searchcheck");e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(h&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{a=.75*(this.$search.val().length+1)+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){return{"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"}}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d="";return d+=null!=b?b.id:a.generateChars(4),d+="-result-",d+=a.generateChars(4),null!=c.id?d+="-"+c.id.toString():d+="-"+a.generateChars(4),d},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h0&&b.term.length>this.maximumInputLength)return void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;if(d.maximumSelectionLength>0&&f>=d.maximumSelectionLength)return void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}});a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this,f=c.id+"-results";b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.attr("aria-owns",f),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){e.showSearch(a)?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}}),c.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){e.$results.offset().top+e.$results.outerHeight(!1)+50>=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1)&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('
      • '),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a(""),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id;this.$container.parents().filter(b.hasScroll).off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.topf.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),null==l.tokenSeparators&&null==l.tokenizer||(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){null==c(d,e.children[g])&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var h=b(e.text).toUpperCase(),i=b(d.term).toUpperCase();return h.indexOf(i)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)},new D}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return e<=0?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;h=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),null!=a&&0!==a.length||(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("select2/compat/utils",["jquery"],function(a){function b(b,c,d){var e,f,g=[];e=a.trim(b.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0===this.indexOf("select2-")&&g.push(this)})),e=a.trim(c.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0!==this.indexOf("select2-")&&null!=(f=d(this))&&g.push(f)})),b.attr("class",g.join(" "))}return{syncCssClasses:b}}),b.define("select2/compat/containerCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("containerCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptContainerCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("containerCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/dropdownCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("dropdownCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptDropdownCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("dropdownCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/initSelection",["jquery"],function(a){function b(a,b,c){c.get("debug")&&window.console&&console.warn&&console.warn("Select2: The `initSelection` option has been deprecated in favor of a custom data adapter that overrides the `current` method. This method is now called multiple times instead of a single time when the instance is initialized. Support will be removed for the `initSelection` option in future versions of Select2"),this.initSelection=c.get("initSelection"),this._isInitialized=!1,a.call(this,b,c)}return b.prototype.current=function(b,c){var d=this;if(this._isInitialized)return void b.call(this,c);this.initSelection.call(null,this.$element,function(b){d._isInitialized=!0,a.isArray(b)||(b=[b]),c(b)})},b}),b.define("select2/compat/inputData",["jquery"],function(a){function b(a,b,c){this._currentData=[],this._valueSeparator=c.get("valueSeparator")||",","hidden"===b.prop("type")&&c.get("debug")&&console&&console.warn&&console.warn("Select2: Using a hidden input with Select2 is no longer supported and may stop working in the future. It is recommended to use a `');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this,f=b.id+"-results";a.call(this,b,d),b.on("open",function(){e.$search.attr("aria-owns",f),e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){if(a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented(),a.which===c.BACKSPACE&&""===e.$search.val()){var b=e.$searchContainer.prev(".select2-selection__choice");if(b.length>0){var d=b.data("data");e.searchRemoveChoice(d),a.preventDefault()}}});var g=document.documentMode,h=g&&g<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){if(h)return void e.$selection.off("input.search input.searchcheck");e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(h&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{a=.75*(this.$search.val().length+1)+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){return{"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"}}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d="";return d+=null!=b?b.id:a.generateChars(4),d+="-result-",d+=a.generateChars(4),null!=c.id?d+="-"+c.id.toString():d+="-"+a.generateChars(4),d},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h0&&b.term.length>this.maximumInputLength)return void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;if(d.maximumSelectionLength>0&&f>=d.maximumSelectionLength)return void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}});a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this,f=c.id+"-results";b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.attr("aria-owns",f),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){e.showSearch(a)?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}}),c.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){e.$results.offset().top+e.$results.outerHeight(!1)+50>=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1)&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('
      • '),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a(""),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id;this.$container.parents().filter(b.hasScroll).off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.topf.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),null==l.tokenSeparators&&null==l.tokenizer||(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){null==c(d,e.children[g])&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var h=b(e.text).toUpperCase(),i=b(d.term).toUpperCase();return h.indexOf(i)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)},new D}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return e<=0?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;h=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),null!=a&&0!==a.length||(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("select2/compat/utils",["jquery"],function(a){function b(b,c,d){var e,f,g=[];e=a.trim(b.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0===this.indexOf("select2-")&&g.push(this)})),e=a.trim(c.attr("class")),e&&(e=""+e,a(e.split(/\s+/)).each(function(){0!==this.indexOf("select2-")&&null!=(f=d(this))&&g.push(f)})),b.attr("class",g.join(" "))}return{syncCssClasses:b}}),b.define("select2/compat/containerCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("containerCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptContainerCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("containerCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/dropdownCss",["jquery","./utils"],function(a,b){function c(a){return null}function d(){}return d.prototype.render=function(d){var e=d.call(this),f=this.options.get("dropdownCssClass")||"";a.isFunction(f)&&(f=f(this.$element));var g=this.options.get("adaptDropdownCssClass");if(g=g||c,-1!==f.indexOf(":all:")){f=f.replace(":all:","");var h=g;g=function(a){var b=h(a);return null!=b?b+" "+a:a}}var i=this.options.get("dropdownCss")||{};return a.isFunction(i)&&(i=i(this.$element)),b.syncCssClasses(e,this.$element,g),e.css(i),e.addClass(f),e},d}),b.define("select2/compat/initSelection",["jquery"],function(a){function b(a,b,c){c.get("debug")&&window.console&&console.warn&&console.warn("Select2: The `initSelection` option has been deprecated in favor of a custom data adapter that overrides the `current` method. This method is now called multiple times instead of a single time when the instance is initialized. Support will be removed for the `initSelection` option in future versions of Select2"),this.initSelection=c.get("initSelection"),this._isInitialized=!1,a.call(this,b,c)}return b.prototype.current=function(b,c){var d=this;if(this._isInitialized)return void b.call(this,c);this.initSelection.call(null,this.$element,function(b){d._isInitialized=!0,a.isArray(b)||(b=[b]),c(b)})},b}),b.define("select2/compat/inputData",["jquery"],function(a){function b(a,b,c){this._currentData=[],this._valueSeparator=c.get("valueSeparator")||",","hidden"===b.prop("type")&&c.get("debug")&&console&&console.warn&&console.warn("Select2: Using a hidden input with Select2 is no longer supported and may stop working in the future. It is recommended to use a `' + '' @@ -3940,7 +3939,7 @@ S2.define('select2/dropdown/search',[ '' + '' + + ' spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" />' + '' ); diff --git a/assets/js/selectWoo/selectWoo.min.js b/assets/js/selectWoo/selectWoo.min.js index 456ebbfdcaa..ecbf4173908 100644 --- a/assets/js/selectWoo/selectWoo.min.js +++ b/assets/js/selectWoo/selectWoo.min.js @@ -1 +1 @@ -/*! Select2 5.0.0 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(b,c){return void 0===c&&(c="undefined"!=typeof window?require("jquery"):require("jquery")(b)),a(c),c}:a(jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return v.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o=b&&b.split("/"),p=t.map,q=p&&p["*"]||{};if(a){for(a=a.split("/"),g=a.length-1,t.nodeIdCompat&&x.test(a[g])&&(a[g]=a[g].replace(x,"")),"."===a[0].charAt(0)&&o&&(n=o.slice(0,o.length-1),a=n.concat(a)),k=0;k0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}if((o||q)&&p){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),o)for(l=o.length;l>0;l-=1)if((e=p[o.slice(0,l).join("/")])&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&q&&q[d]&&(i=q[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=w.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),o.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){r[a]=b}}function j(a){if(e(s,a)){var c=s[a];delete s[a],u[a]=!0,n.apply(b,c)}if(!e(r,a)&&!e(u,a))throw new Error("No "+a);return r[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return a?k(a):[]}function m(a){return function(){return t&&t.config&&t.config[a]||{}}}var n,o,p,q,r={},s={},t={},u={},v=Object.prototype.hasOwnProperty,w=[].slice,x=/\.js$/;p=function(a,b){var c,d=k(a),e=d[0],g=b[1];return a=d[1],e&&(e=f(e,g),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(g)):f(a,g):(a=f(a,g),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},q={require:function(a){return g(a)},exports:function(a){var b=r[a];return void 0!==b?b:r[a]={}},module:function(a){return{id:a,uri:"",exports:r[a],config:m(a)}}},n=function(a,c,d,f){var h,k,m,n,o,t,v,w=[],x=typeof d;if(f=f||a,t=l(f),"undefined"===x||"function"===x){for(c=!c.length&&d.length?["require","exports","module"]:c,o=0;o0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h":">",'"':""","'":"'","/":"/"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('
          ');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a(''),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()});b.$results.find(".select2-results__option[data-selected]").each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("data-selected","true"):b.attr("data-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"option","data-selected":"false"};b.disabled&&(delete d["data-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["data-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d["aria-label"]=b.text,delete d["data-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";a(h);this.template(b,h);for(var i=[],j=0;j",{class:"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("unselect",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("data-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[data-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):h-g<0&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[data-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted").attr("aria-selected","true"),d.$results.attr("aria-activedescendant",a.element.attr("id"))}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[data-selected]",function(b){var c=a(this),e=c.data("data");if("true"===c.attr("data-selected"))return void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{}));d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[data-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2-results__option--highlighted").attr("aria-selected","false"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){return this.$results.find(".select2-results__option--highlighted")},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[data-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),c<=2?this.$results.scrollTop(0):(g>this.$results.outerHeight()||g<0)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id,a.id+"-results"),f=this.options.get("minimumResultsForSearch")===1/0;this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){f&&d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),f&&d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),window.setTimeout(function(){d.$selection.focus()},1),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2");a(".select2.select2-container--open").each(function(){var b=a(this);this!=d[0]&&b.data("element").select2("close")})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){b.find(".selection").append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html(''),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",d),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("keydown",function(b){!a.isOpen()&&b.which>=48&&b.which<=90&&a.open()}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},e.prototype.selectionContainer=function(){return a("")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('
            '),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},d.prototype.selectionContainer=function(){return a('
          • ')},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d1||c)return a.call(this,b);this.clear();var d=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(d)},b}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e0||0===c.length)){var d=a('×');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this,f=b.id+"-results";a.call(this,b,d),b.on("open",function(){e.$search.attr("aria-owns",f),e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){if(a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented(),a.which===c.BACKSPACE&&""===e.$search.val()){var b=e.$searchContainer.prev(".select2-selection__choice");if(b.length>0){var d=b.data("data");e.searchRemoveChoice(d),a.preventDefault()}}});var g=document.documentMode,h=g&&g<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){if(h)return void e.$selection.off("input.search input.searchcheck");e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(h&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{a=.75*(this.$search.val().length+1)+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){return{"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"}}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d="";return d+=null!=b?b.id:a.generateChars(4),d+="-result-",d+=a.generateChars(4),null!=c.id?d+="-"+c.id.toString():d+="-"+a.generateChars(4),d},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h0&&b.term.length>this.maximumInputLength)return void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;if(d.maximumSelectionLength>0&&f>=d.maximumSelectionLength)return void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}});a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this,f=c.id+"-results";b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.attr("aria-owns",f),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){e.showSearch(a)?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}}),c.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){e.$results.offset().top+e.$results.outerHeight(!1)+50>=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1)&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('
          • '),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a(""),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id;this.$container.parents().filter(b.hasScroll).off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.topf.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),null==l.tokenSeparators&&null==l.tokenizer||(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){null==c(d,e.children[g])&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var h=b(e.text).toUpperCase(),i=b(d.term).toUpperCase();return h.indexOf(i)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)},new D}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return e<=0?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;h=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),null!=a&&0!==a.length||(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("jquery-mousewheel",["jquery"],function(a){return a}),b.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults"],function(a,b,c,d){if(null==a.fn.selectWoo){var e=["open","close","destroy"];a.fn.selectWoo=function(b){if("object"==typeof(b=b||{}))return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d,f=Array.prototype.slice.call(arguments,1);return this.each(function(){var c=a(this).data("select2");null==c&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2."),d=c[b].apply(c,f)}),a.inArray(b,e)>-1?this:d}throw new Error("Invalid arguments for Select2: "+b)}}return null!=a.fn.select2&&null!=a.fn.select2.defaults&&(a.fn.selectWoo.defaults=a.fn.select2.defaults),null==a.fn.selectWoo.defaults&&(a.fn.selectWoo.defaults=d),a.fn.select2=a.fn.select2||a.fn.selectWoo,c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,a.fn.selectWoo.amd=b,c}); \ No newline at end of file +/*! Select2 5.0.0 | https://github.com/select2/select2/blob/master/LICENSE.md */!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=function(b,c){return void 0===c&&(c="undefined"!=typeof window?require("jquery"):require("jquery")(b)),a(c),c}:a(jQuery)}(function(a){var b=function(){if(a&&a.fn&&a.fn.select2&&a.fn.select2.amd)var b=a.fn.select2.amd;var b;return function(){if(!b||!b.requirejs){b?c=b:b={};var a,c,d;!function(b){function e(a,b){return v.call(a,b)}function f(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o=b&&b.split("/"),p=t.map,q=p&&p["*"]||{};if(a){for(a=a.split("/"),g=a.length-1,t.nodeIdCompat&&x.test(a[g])&&(a[g]=a[g].replace(x,"")),"."===a[0].charAt(0)&&o&&(n=o.slice(0,o.length-1),a=n.concat(a)),k=0;k0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}if((o||q)&&p){for(c=a.split("/"),k=c.length;k>0;k-=1){if(d=c.slice(0,k).join("/"),o)for(l=o.length;l>0;l-=1)if((e=p[o.slice(0,l).join("/")])&&(e=e[d])){f=e,h=k;break}if(f)break;!i&&q&&q[d]&&(i=q[d],j=k)}!f&&i&&(f=i,h=j),f&&(c.splice(0,h,f),a=c.join("/"))}return a}function g(a,c){return function(){var d=w.call(arguments,0);return"string"!=typeof d[0]&&1===d.length&&d.push(null),o.apply(b,d.concat([a,c]))}}function h(a){return function(b){return f(b,a)}}function i(a){return function(b){r[a]=b}}function j(a){if(e(s,a)){var c=s[a];delete s[a],u[a]=!0,n.apply(b,c)}if(!e(r,a)&&!e(u,a))throw new Error("No "+a);return r[a]}function k(a){var b,c=a?a.indexOf("!"):-1;return c>-1&&(b=a.substring(0,c),a=a.substring(c+1,a.length)),[b,a]}function l(a){return a?k(a):[]}function m(a){return function(){return t&&t.config&&t.config[a]||{}}}var n,o,p,q,r={},s={},t={},u={},v=Object.prototype.hasOwnProperty,w=[].slice,x=/\.js$/;p=function(a,b){var c,d=k(a),e=d[0],g=b[1];return a=d[1],e&&(e=f(e,g),c=j(e)),e?a=c&&c.normalize?c.normalize(a,h(g)):f(a,g):(a=f(a,g),d=k(a),e=d[0],a=d[1],e&&(c=j(e))),{f:e?e+"!"+a:a,n:a,pr:e,p:c}},q={require:function(a){return g(a)},exports:function(a){var b=r[a];return void 0!==b?b:r[a]={}},module:function(a){return{id:a,uri:"",exports:r[a],config:m(a)}}},n=function(a,c,d,f){var h,k,m,n,o,t,v,w=[],x=typeof d;if(f=f||a,t=l(f),"undefined"===x||"function"===x){for(c=!c.length&&d.length?["require","exports","module"]:c,o=0;o0&&(b.call(arguments,a.prototype.constructor),e=c.prototype.constructor),e.apply(this,arguments)}function e(){this.constructor=d}var f=b(c),g=b(a);c.displayName=a.displayName,d.prototype=new e;for(var h=0;h":">",'"':""","'":"'","/":"/"};return"string"!=typeof a?a:String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})},c.appendMany=function(b,c){if("1.7"===a.fn.jquery.substr(0,3)){var d=a();a.map(c,function(a){d=d.add(a)}),c=d}b.append(c)},c}),b.define("select2/results",["jquery","./utils"],function(a,b){function c(a,b,d){this.$element=a,this.data=d,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('
              ');return this.options.get("multiple")&&b.attr("aria-multiselectable","true"),this.$results=b,b},c.prototype.clear=function(){this.$results.empty()},c.prototype.displayMessage=function(b){var c=this.options.get("escapeMarkup");this.clear(),this.hideLoading();var d=a(''),e=this.options.get("translations").get(b.message);d.append(c(e(b.args))),d[0].className+=" select2-results__message",this.$results.append(d)},c.prototype.hideMessages=function(){this.$results.find(".select2-results__message").remove()},c.prototype.append=function(a){this.hideLoading();var b=[];if(null==a.results||0===a.results.length)return void(0===this.$results.children().length&&this.trigger("results:message",{message:"noResults"}));a.results=this.sort(a.results);for(var c=0;c0?b.first().trigger("mouseenter"):a.first().trigger("mouseenter"),this.ensureHighlightVisible()},c.prototype.setClasses=function(){var b=this;this.data.current(function(c){var d=a.map(c,function(a){return a.id.toString()});b.$results.find(".select2-results__option[data-selected]").each(function(){var b=a(this),c=a.data(this,"data"),e=""+c.id;null!=c.element&&c.element.selected||null==c.element&&a.inArray(e,d)>-1?b.attr("data-selected","true"):b.attr("data-selected","false")})})},c.prototype.showLoading=function(a){this.hideLoading();var b=this.options.get("translations").get("searching"),c={disabled:!0,loading:!0,text:b(a)},d=this.option(c);d.className+=" loading-results",this.$results.prepend(d)},c.prototype.hideLoading=function(){this.$results.find(".loading-results").remove()},c.prototype.option=function(b){var c=document.createElement("li");c.className="select2-results__option";var d={role:"option","data-selected":"false"};b.disabled&&(delete d["data-selected"],d["aria-disabled"]="true"),null==b.id&&delete d["data-selected"],null!=b._resultId&&(c.id=b._resultId),b.title&&(c.title=b.title),b.children&&(d["aria-label"]=b.text,delete d["data-selected"]);for(var e in d){var f=d[e];c.setAttribute(e,f)}if(b.children){var g=a(c),h=document.createElement("strong");h.className="select2-results__group";a(h);this.template(b,h);for(var i=[],j=0;j",{class:"select2-results__options select2-results__options--nested"});m.append(i),g.append(h),g.append(m)}else this.template(b,c);return a.data(c,"data",b),c},c.prototype.bind=function(b,c){var d=this,e=b.id+"-results";this.$results.attr("id",e),b.on("results:all",function(a){d.clear(),d.append(a.data),b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("results:append",function(a){d.append(a.data),b.isOpen()&&d.setClasses()}),b.on("query",function(a){d.hideMessages(),d.showLoading(a)}),b.on("select",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("unselect",function(){b.isOpen()&&(d.setClasses(),d.highlightFirstItem())}),b.on("open",function(){d.$results.attr("aria-expanded","true"),d.$results.attr("aria-hidden","false"),d.setClasses(),d.ensureHighlightVisible()}),b.on("close",function(){d.$results.attr("aria-expanded","false"),d.$results.attr("aria-hidden","true"),d.$results.removeAttr("aria-activedescendant")}),b.on("results:toggle",function(){var a=d.getHighlightedResults();0!==a.length&&a.trigger("mouseup")}),b.on("results:select",function(){var a=d.getHighlightedResults();if(0!==a.length){var b=a.data("data");"true"==a.attr("data-selected")?d.trigger("close",{}):d.trigger("select",{data:b})}}),b.on("results:previous",function(){var a=d.getHighlightedResults(),b=d.$results.find("[data-selected]"),c=b.index(a);if(0!==c){var e=c-1;0===a.length&&(e=0);var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top,h=f.offset().top,i=d.$results.scrollTop()+(h-g);0===e?d.$results.scrollTop(0):h-g<0&&d.$results.scrollTop(i)}}),b.on("results:next",function(){var a=d.getHighlightedResults(),b=d.$results.find("[data-selected]"),c=b.index(a),e=c+1;if(!(e>=b.length)){var f=b.eq(e);f.trigger("mouseenter");var g=d.$results.offset().top+d.$results.outerHeight(!1),h=f.offset().top+f.outerHeight(!1),i=d.$results.scrollTop()+h-g;0===e?d.$results.scrollTop(0):h>g&&d.$results.scrollTop(i)}}),b.on("results:focus",function(a){a.element.addClass("select2-results__option--highlighted").attr("aria-selected","true"),d.$results.attr("aria-activedescendant",a.element.attr("id"))}),b.on("results:message",function(a){d.displayMessage(a)}),a.fn.mousewheel&&this.$results.on("mousewheel",function(a){var b=d.$results.scrollTop(),c=d.$results.get(0).scrollHeight-b+a.deltaY,e=a.deltaY>0&&b-a.deltaY<=0,f=a.deltaY<0&&c<=d.$results.height();e?(d.$results.scrollTop(0),a.preventDefault(),a.stopPropagation()):f&&(d.$results.scrollTop(d.$results.get(0).scrollHeight-d.$results.height()),a.preventDefault(),a.stopPropagation())}),this.$results.on("mouseup",".select2-results__option[data-selected]",function(b){var c=a(this),e=c.data("data");if("true"===c.attr("data-selected"))return void(d.options.get("multiple")?d.trigger("unselect",{originalEvent:b,data:e}):d.trigger("close",{}));d.trigger("select",{originalEvent:b,data:e})}),this.$results.on("mouseenter",".select2-results__option[data-selected]",function(b){var c=a(this).data("data");d.getHighlightedResults().removeClass("select2-results__option--highlighted").attr("aria-selected","false"),d.trigger("results:focus",{data:c,element:a(this)})})},c.prototype.getHighlightedResults=function(){return this.$results.find(".select2-results__option--highlighted")},c.prototype.destroy=function(){this.$results.remove()},c.prototype.ensureHighlightVisible=function(){var a=this.getHighlightedResults();if(0!==a.length){var b=this.$results.find("[data-selected]"),c=b.index(a),d=this.$results.offset().top,e=a.offset().top,f=this.$results.scrollTop()+(e-d),g=e-d;f-=2*a.outerHeight(!1),c<=2?this.$results.scrollTop(0):(g>this.$results.outerHeight()||g<0)&&this.$results.scrollTop(f)}},c.prototype.template=function(b,c){var d=this.options.get("templateResult"),e=this.options.get("escapeMarkup"),f=d(b,c);null==f?c.style.display="none":"string"==typeof f?c.innerHTML=e(f):a(c).append(f)},c}),b.define("select2/keys",[],function(){return{BACKSPACE:8,TAB:9,ENTER:13,SHIFT:16,CTRL:17,ALT:18,ESC:27,SPACE:32,PAGE_UP:33,PAGE_DOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,DELETE:46}}),b.define("select2/selection/base",["jquery","../utils","../keys"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,b.Observable),d.prototype.render=function(){var b=a('');return this._tabindex=0,null!=this.$element.data("old-tabindex")?this._tabindex=this.$element.data("old-tabindex"):null!=this.$element.attr("tabindex")&&(this._tabindex=this.$element.attr("tabindex")),b.attr("title",this.$element.attr("title")),b.attr("tabindex",this._tabindex),this.$selection=b,b},d.prototype.bind=function(a,b){var d=this,e=(a.id,a.id+"-results");this.options.get("minimumResultsForSearch");this.container=a,this.$selection.on("focus",function(a){d.trigger("focus",a)}),this.$selection.on("blur",function(a){d._handleBlur(a)}),this.$selection.on("keydown",function(a){d.trigger("keypress",a),a.which===c.SPACE&&a.preventDefault()}),a.on("results:focus",function(a){d.$selection.attr("aria-activedescendant",a.data._resultId)}),a.on("selection:update",function(a){d.update(a.data)}),a.on("open",function(){d.$selection.attr("aria-expanded","true"),d.$selection.attr("aria-owns",e),d._attachCloseHandler(a)}),a.on("close",function(){d.$selection.attr("aria-expanded","false"),d.$selection.removeAttr("aria-activedescendant"),d.$selection.removeAttr("aria-owns"),window.setTimeout(function(){d.$selection.focus()},1),d._detachCloseHandler(a)}),a.on("enable",function(){d.$selection.attr("tabindex",d._tabindex)}),a.on("disable",function(){d.$selection.attr("tabindex","-1")})},d.prototype._handleBlur=function(b){var c=this;window.setTimeout(function(){document.activeElement==c.$selection[0]||a.contains(c.$selection[0],document.activeElement)||c.trigger("blur",b)},1)},d.prototype._attachCloseHandler=function(b){a(document.body).on("mousedown.select2."+b.id,function(b){var c=a(b.target),d=c.closest(".select2");a(".select2.select2-container--open").each(function(){var b=a(this);this!=d[0]&&b.data("element").select2("close")})})},d.prototype._detachCloseHandler=function(b){a(document.body).off("mousedown.select2."+b.id)},d.prototype.position=function(a,b){b.find(".selection").append(a)},d.prototype.destroy=function(){this._detachCloseHandler(this.container)},d.prototype.update=function(a){throw new Error("The `update` method must be defined in child classes.")},d}),b.define("select2/selection/single",["jquery","./base","../utils","../keys"],function(a,b,c,d){function e(){e.__super__.constructor.apply(this,arguments)}return c.Extend(e,b),e.prototype.render=function(){var a=e.__super__.render.call(this);return a.addClass("select2-selection--single"),a.html(''),a},e.prototype.bind=function(a,b){var c=this;e.__super__.bind.apply(this,arguments);var d=a.id+"-container";this.$selection.find(".select2-selection__rendered").attr("id",d).attr("role","textbox").attr("aria-readonly","true"),this.$selection.attr("aria-labelledby",d),this.$selection.attr("role","combobox"),this.$selection.on("mousedown",function(a){1===a.which&&c.trigger("toggle",{originalEvent:a})}),this.$selection.on("focus",function(a){}),this.$selection.on("keydown",function(b){!a.isOpen()&&b.which>=48&&b.which<=90&&a.open()}),this.$selection.on("blur",function(a){}),a.on("focus",function(b){a.isOpen()||c.$selection.focus()}),a.on("selection:update",function(a){c.update(a.data)})},e.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},e.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},e.prototype.selectionContainer=function(){return a("")},e.prototype.update=function(a){if(0===a.length)return void this.clear();var b=a[0],c=this.$selection.find(".select2-selection__rendered"),d=this.display(b,c);c.empty().append(d),c.prop("title",b.title||b.text)},e}),b.define("select2/selection/multiple",["jquery","./base","../utils"],function(a,b,c){function d(a,b){d.__super__.constructor.apply(this,arguments)}return c.Extend(d,b),d.prototype.render=function(){var a=d.__super__.render.call(this);return a.addClass("select2-selection--multiple"),a.html('
                '),a},d.prototype.bind=function(b,c){var e=this;d.__super__.bind.apply(this,arguments),this.$selection.on("click",function(a){e.trigger("toggle",{originalEvent:a})}),this.$selection.on("click",".select2-selection__choice__remove",function(b){if(!e.options.get("disabled")){var c=a(this),d=c.parent(),f=d.data("data");e.trigger("unselect",{originalEvent:b,data:f})}})},d.prototype.clear=function(){this.$selection.find(".select2-selection__rendered").empty()},d.prototype.display=function(a,b){var c=this.options.get("templateSelection");return this.options.get("escapeMarkup")(c(a,b))},d.prototype.selectionContainer=function(){return a('
              • ')},d.prototype.update=function(a){if(this.clear(),0!==a.length){for(var b=[],d=0;d1||c)return a.call(this,b);this.clear();var d=this.createPlaceholder(this.placeholder);this.$selection.find(".select2-selection__rendered").append(d)},b}),b.define("select2/selection/allowClear",["jquery","../keys"],function(a,b){function c(){}return c.prototype.bind=function(a,b,c){var d=this;a.call(this,b,c),null==this.placeholder&&this.options.get("debug")&&window.console&&console.error&&console.error("Select2: The `allowClear` option should be used in combination with the `placeholder` option."),this.$selection.on("mousedown",".select2-selection__clear",function(a){d._handleClear(a)}),b.on("keypress",function(a){d._handleKeyboardClear(a,b)})},c.prototype._handleClear=function(a,b){if(!this.options.get("disabled")){var c=this.$selection.find(".select2-selection__clear");if(0!==c.length){b.stopPropagation();for(var d=c.data("data"),e=0;e0||0===c.length)){var d=a('×');d.data("data",c),this.$selection.find(".select2-selection__rendered").prepend(d)}},c}),b.define("select2/selection/search",["jquery","../utils","../keys"],function(a,b,c){function d(a,b,c){a.call(this,b,c)}return d.prototype.render=function(b){var c=a('');this.$searchContainer=c,this.$search=c.find("input");var d=b.call(this);return this._transferTabIndex(),d},d.prototype.bind=function(a,b,d){var e=this,f=b.id+"-results";a.call(this,b,d),b.on("open",function(){e.$search.attr("aria-owns",f),e.$search.trigger("focus")}),b.on("close",function(){e.$search.val(""),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.trigger("focus")}),b.on("enable",function(){e.$search.prop("disabled",!1),e._transferTabIndex()}),b.on("disable",function(){e.$search.prop("disabled",!0)}),b.on("focus",function(a){e.$search.trigger("focus")}),b.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)}),this.$selection.on("focusin",".select2-search--inline",function(a){e.trigger("focus",a)}),this.$selection.on("focusout",".select2-search--inline",function(a){e._handleBlur(a)}),this.$selection.on("keydown",".select2-search--inline",function(a){if(a.stopPropagation(),e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented(),a.which===c.BACKSPACE&&""===e.$search.val()){var b=e.$searchContainer.prev(".select2-selection__choice");if(b.length>0){var d=b.data("data");e.searchRemoveChoice(d),a.preventDefault()}}});var g=document.documentMode,h=g&&g<=11;this.$selection.on("input.searchcheck",".select2-search--inline",function(a){if(h)return void e.$selection.off("input.search input.searchcheck");e.$selection.off("keyup.search")}),this.$selection.on("keyup.search input.search",".select2-search--inline",function(a){if(h&&"input"===a.type)return void e.$selection.off("input.search input.searchcheck");var b=a.which;b!=c.SHIFT&&b!=c.CTRL&&b!=c.ALT&&b!=c.TAB&&e.handleSearch(a)})},d.prototype._transferTabIndex=function(a){this.$search.attr("tabindex",this.$selection.attr("tabindex")),this.$selection.attr("tabindex","-1")},d.prototype.createPlaceholder=function(a,b){this.$search.attr("placeholder",b.text)},d.prototype.update=function(a,b){var c=this.$search[0]==document.activeElement;this.$search.attr("placeholder",""),a.call(this,b),this.$selection.find(".select2-selection__rendered").append(this.$searchContainer),this.resizeSearch(),c&&this.$search.focus()},d.prototype.handleSearch=function(){if(this.resizeSearch(),!this._keyUpPrevented){var a=this.$search.val();this.trigger("query",{term:a})}this._keyUpPrevented=!1},d.prototype.searchRemoveChoice=function(a,b){this.trigger("unselect",{data:b}),this.$search.val(b.text),this.handleSearch()},d.prototype.resizeSearch=function(){this.$search.css("width","25px");var a="";if(""!==this.$search.attr("placeholder"))a=this.$selection.find(".select2-selection__rendered").innerWidth();else{a=.75*(this.$search.val().length+1)+"em"}this.$search.css("width",a)},d}),b.define("select2/selection/eventRelay",["jquery"],function(a){function b(){}return b.prototype.bind=function(b,c,d){var e=this,f=["open","opening","close","closing","select","selecting","unselect","unselecting"],g=["opening","closing","selecting","unselecting"];b.call(this,c,d),c.on("*",function(b,c){if(-1!==a.inArray(b,f)){c=c||{};var d=a.Event("select2:"+b,{params:c});e.$element.trigger(d),-1!==a.inArray(b,g)&&(c.prevented=d.isDefaultPrevented())}})},b}),b.define("select2/translation",["jquery","require"],function(a,b){function c(a){this.dict=a||{}}return c.prototype.all=function(){return this.dict},c.prototype.get=function(a){return this.dict[a]},c.prototype.extend=function(b){this.dict=a.extend({},b.all(),this.dict)},c._cache={},c.loadPath=function(a){if(!(a in c._cache)){var d=b(a);c._cache[a]=d}return new c(c._cache[a])},c}),b.define("select2/diacritics",[],function(){return{"Ⓐ":"A","A":"A","À":"A","Á":"A","Â":"A","Ầ":"A","Ấ":"A","Ẫ":"A","Ẩ":"A","Ã":"A","Ā":"A","Ă":"A","Ằ":"A","Ắ":"A","Ẵ":"A","Ẳ":"A","Ȧ":"A","Ǡ":"A","Ä":"A","Ǟ":"A","Ả":"A","Å":"A","Ǻ":"A","Ǎ":"A","Ȁ":"A","Ȃ":"A","Ạ":"A","Ậ":"A","Ặ":"A","Ḁ":"A","Ą":"A","Ⱥ":"A","Ɐ":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ⓑ":"B","B":"B","Ḃ":"B","Ḅ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ɓ":"B","Ⓒ":"C","C":"C","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","Ç":"C","Ḉ":"C","Ƈ":"C","Ȼ":"C","Ꜿ":"C","Ⓓ":"D","D":"D","Ḋ":"D","Ď":"D","Ḍ":"D","Ḑ":"D","Ḓ":"D","Ḏ":"D","Đ":"D","Ƌ":"D","Ɗ":"D","Ɖ":"D","Ꝺ":"D","DZ":"DZ","DŽ":"DZ","Dz":"Dz","Dž":"Dz","Ⓔ":"E","E":"E","È":"E","É":"E","Ê":"E","Ề":"E","Ế":"E","Ễ":"E","Ể":"E","Ẽ":"E","Ē":"E","Ḕ":"E","Ḗ":"E","Ĕ":"E","Ė":"E","Ë":"E","Ẻ":"E","Ě":"E","Ȅ":"E","Ȇ":"E","Ẹ":"E","Ệ":"E","Ȩ":"E","Ḝ":"E","Ę":"E","Ḙ":"E","Ḛ":"E","Ɛ":"E","Ǝ":"E","Ⓕ":"F","F":"F","Ḟ":"F","Ƒ":"F","Ꝼ":"F","Ⓖ":"G","G":"G","Ǵ":"G","Ĝ":"G","Ḡ":"G","Ğ":"G","Ġ":"G","Ǧ":"G","Ģ":"G","Ǥ":"G","Ɠ":"G","Ꞡ":"G","Ᵹ":"G","Ꝿ":"G","Ⓗ":"H","H":"H","Ĥ":"H","Ḣ":"H","Ḧ":"H","Ȟ":"H","Ḥ":"H","Ḩ":"H","Ḫ":"H","Ħ":"H","Ⱨ":"H","Ⱶ":"H","Ɥ":"H","Ⓘ":"I","I":"I","Ì":"I","Í":"I","Î":"I","Ĩ":"I","Ī":"I","Ĭ":"I","İ":"I","Ï":"I","Ḯ":"I","Ỉ":"I","Ǐ":"I","Ȉ":"I","Ȋ":"I","Ị":"I","Į":"I","Ḭ":"I","Ɨ":"I","Ⓙ":"J","J":"J","Ĵ":"J","Ɉ":"J","Ⓚ":"K","K":"K","Ḱ":"K","Ǩ":"K","Ḳ":"K","Ķ":"K","Ḵ":"K","Ƙ":"K","Ⱪ":"K","Ꝁ":"K","Ꝃ":"K","Ꝅ":"K","Ꞣ":"K","Ⓛ":"L","L":"L","Ŀ":"L","Ĺ":"L","Ľ":"L","Ḷ":"L","Ḹ":"L","Ļ":"L","Ḽ":"L","Ḻ":"L","Ł":"L","Ƚ":"L","Ɫ":"L","Ⱡ":"L","Ꝉ":"L","Ꝇ":"L","Ꞁ":"L","LJ":"LJ","Lj":"Lj","Ⓜ":"M","M":"M","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ɯ":"M","Ⓝ":"N","N":"N","Ǹ":"N","Ń":"N","Ñ":"N","Ṅ":"N","Ň":"N","Ṇ":"N","Ņ":"N","Ṋ":"N","Ṉ":"N","Ƞ":"N","Ɲ":"N","Ꞑ":"N","Ꞥ":"N","NJ":"NJ","Nj":"Nj","Ⓞ":"O","O":"O","Ò":"O","Ó":"O","Ô":"O","Ồ":"O","Ố":"O","Ỗ":"O","Ổ":"O","Õ":"O","Ṍ":"O","Ȭ":"O","Ṏ":"O","Ō":"O","Ṑ":"O","Ṓ":"O","Ŏ":"O","Ȯ":"O","Ȱ":"O","Ö":"O","Ȫ":"O","Ỏ":"O","Ő":"O","Ǒ":"O","Ȍ":"O","Ȏ":"O","Ơ":"O","Ờ":"O","Ớ":"O","Ỡ":"O","Ở":"O","Ợ":"O","Ọ":"O","Ộ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Ɔ":"O","Ɵ":"O","Ꝋ":"O","Ꝍ":"O","Ƣ":"OI","Ꝏ":"OO","Ȣ":"OU","Ⓟ":"P","P":"P","Ṕ":"P","Ṗ":"P","Ƥ":"P","Ᵽ":"P","Ꝑ":"P","Ꝓ":"P","Ꝕ":"P","Ⓠ":"Q","Q":"Q","Ꝗ":"Q","Ꝙ":"Q","Ɋ":"Q","Ⓡ":"R","R":"R","Ŕ":"R","Ṙ":"R","Ř":"R","Ȑ":"R","Ȓ":"R","Ṛ":"R","Ṝ":"R","Ŗ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꝛ":"R","Ꞧ":"R","Ꞃ":"R","Ⓢ":"S","S":"S","ẞ":"S","Ś":"S","Ṥ":"S","Ŝ":"S","Ṡ":"S","Š":"S","Ṧ":"S","Ṣ":"S","Ṩ":"S","Ș":"S","Ş":"S","Ȿ":"S","Ꞩ":"S","Ꞅ":"S","Ⓣ":"T","T":"T","Ṫ":"T","Ť":"T","Ṭ":"T","Ț":"T","Ţ":"T","Ṱ":"T","Ṯ":"T","Ŧ":"T","Ƭ":"T","Ʈ":"T","Ⱦ":"T","Ꞇ":"T","Ꜩ":"TZ","Ⓤ":"U","U":"U","Ù":"U","Ú":"U","Û":"U","Ũ":"U","Ṹ":"U","Ū":"U","Ṻ":"U","Ŭ":"U","Ü":"U","Ǜ":"U","Ǘ":"U","Ǖ":"U","Ǚ":"U","Ủ":"U","Ů":"U","Ű":"U","Ǔ":"U","Ȕ":"U","Ȗ":"U","Ư":"U","Ừ":"U","Ứ":"U","Ữ":"U","Ử":"U","Ự":"U","Ụ":"U","Ṳ":"U","Ų":"U","Ṷ":"U","Ṵ":"U","Ʉ":"U","Ⓥ":"V","V":"V","Ṽ":"V","Ṿ":"V","Ʋ":"V","Ꝟ":"V","Ʌ":"V","Ꝡ":"VY","Ⓦ":"W","W":"W","Ẁ":"W","Ẃ":"W","Ŵ":"W","Ẇ":"W","Ẅ":"W","Ẉ":"W","Ⱳ":"W","Ⓧ":"X","X":"X","Ẋ":"X","Ẍ":"X","Ⓨ":"Y","Y":"Y","Ỳ":"Y","Ý":"Y","Ŷ":"Y","Ỹ":"Y","Ȳ":"Y","Ẏ":"Y","Ÿ":"Y","Ỷ":"Y","Ỵ":"Y","Ƴ":"Y","Ɏ":"Y","Ỿ":"Y","Ⓩ":"Z","Z":"Z","Ź":"Z","Ẑ":"Z","Ż":"Z","Ž":"Z","Ẓ":"Z","Ẕ":"Z","Ƶ":"Z","Ȥ":"Z","Ɀ":"Z","Ⱬ":"Z","Ꝣ":"Z","ⓐ":"a","a":"a","ẚ":"a","à":"a","á":"a","â":"a","ầ":"a","ấ":"a","ẫ":"a","ẩ":"a","ã":"a","ā":"a","ă":"a","ằ":"a","ắ":"a","ẵ":"a","ẳ":"a","ȧ":"a","ǡ":"a","ä":"a","ǟ":"a","ả":"a","å":"a","ǻ":"a","ǎ":"a","ȁ":"a","ȃ":"a","ạ":"a","ậ":"a","ặ":"a","ḁ":"a","ą":"a","ⱥ":"a","ɐ":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ⓑ":"b","b":"b","ḃ":"b","ḅ":"b","ḇ":"b","ƀ":"b","ƃ":"b","ɓ":"b","ⓒ":"c","c":"c","ć":"c","ĉ":"c","ċ":"c","č":"c","ç":"c","ḉ":"c","ƈ":"c","ȼ":"c","ꜿ":"c","ↄ":"c","ⓓ":"d","d":"d","ḋ":"d","ď":"d","ḍ":"d","ḑ":"d","ḓ":"d","ḏ":"d","đ":"d","ƌ":"d","ɖ":"d","ɗ":"d","ꝺ":"d","dz":"dz","dž":"dz","ⓔ":"e","e":"e","è":"e","é":"e","ê":"e","ề":"e","ế":"e","ễ":"e","ể":"e","ẽ":"e","ē":"e","ḕ":"e","ḗ":"e","ĕ":"e","ė":"e","ë":"e","ẻ":"e","ě":"e","ȅ":"e","ȇ":"e","ẹ":"e","ệ":"e","ȩ":"e","ḝ":"e","ę":"e","ḙ":"e","ḛ":"e","ɇ":"e","ɛ":"e","ǝ":"e","ⓕ":"f","f":"f","ḟ":"f","ƒ":"f","ꝼ":"f","ⓖ":"g","g":"g","ǵ":"g","ĝ":"g","ḡ":"g","ğ":"g","ġ":"g","ǧ":"g","ģ":"g","ǥ":"g","ɠ":"g","ꞡ":"g","ᵹ":"g","ꝿ":"g","ⓗ":"h","h":"h","ĥ":"h","ḣ":"h","ḧ":"h","ȟ":"h","ḥ":"h","ḩ":"h","ḫ":"h","ẖ":"h","ħ":"h","ⱨ":"h","ⱶ":"h","ɥ":"h","ƕ":"hv","ⓘ":"i","i":"i","ì":"i","í":"i","î":"i","ĩ":"i","ī":"i","ĭ":"i","ï":"i","ḯ":"i","ỉ":"i","ǐ":"i","ȉ":"i","ȋ":"i","ị":"i","į":"i","ḭ":"i","ɨ":"i","ı":"i","ⓙ":"j","j":"j","ĵ":"j","ǰ":"j","ɉ":"j","ⓚ":"k","k":"k","ḱ":"k","ǩ":"k","ḳ":"k","ķ":"k","ḵ":"k","ƙ":"k","ⱪ":"k","ꝁ":"k","ꝃ":"k","ꝅ":"k","ꞣ":"k","ⓛ":"l","l":"l","ŀ":"l","ĺ":"l","ľ":"l","ḷ":"l","ḹ":"l","ļ":"l","ḽ":"l","ḻ":"l","ſ":"l","ł":"l","ƚ":"l","ɫ":"l","ⱡ":"l","ꝉ":"l","ꞁ":"l","ꝇ":"l","lj":"lj","ⓜ":"m","m":"m","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ɯ":"m","ⓝ":"n","n":"n","ǹ":"n","ń":"n","ñ":"n","ṅ":"n","ň":"n","ṇ":"n","ņ":"n","ṋ":"n","ṉ":"n","ƞ":"n","ɲ":"n","ʼn":"n","ꞑ":"n","ꞥ":"n","nj":"nj","ⓞ":"o","o":"o","ò":"o","ó":"o","ô":"o","ồ":"o","ố":"o","ỗ":"o","ổ":"o","õ":"o","ṍ":"o","ȭ":"o","ṏ":"o","ō":"o","ṑ":"o","ṓ":"o","ŏ":"o","ȯ":"o","ȱ":"o","ö":"o","ȫ":"o","ỏ":"o","ő":"o","ǒ":"o","ȍ":"o","ȏ":"o","ơ":"o","ờ":"o","ớ":"o","ỡ":"o","ở":"o","ợ":"o","ọ":"o","ộ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","ɔ":"o","ꝋ":"o","ꝍ":"o","ɵ":"o","ƣ":"oi","ȣ":"ou","ꝏ":"oo","ⓟ":"p","p":"p","ṕ":"p","ṗ":"p","ƥ":"p","ᵽ":"p","ꝑ":"p","ꝓ":"p","ꝕ":"p","ⓠ":"q","q":"q","ɋ":"q","ꝗ":"q","ꝙ":"q","ⓡ":"r","r":"r","ŕ":"r","ṙ":"r","ř":"r","ȑ":"r","ȓ":"r","ṛ":"r","ṝ":"r","ŗ":"r","ṟ":"r","ɍ":"r","ɽ":"r","ꝛ":"r","ꞧ":"r","ꞃ":"r","ⓢ":"s","s":"s","ß":"s","ś":"s","ṥ":"s","ŝ":"s","ṡ":"s","š":"s","ṧ":"s","ṣ":"s","ṩ":"s","ș":"s","ş":"s","ȿ":"s","ꞩ":"s","ꞅ":"s","ẛ":"s","ⓣ":"t","t":"t","ṫ":"t","ẗ":"t","ť":"t","ṭ":"t","ț":"t","ţ":"t","ṱ":"t","ṯ":"t","ŧ":"t","ƭ":"t","ʈ":"t","ⱦ":"t","ꞇ":"t","ꜩ":"tz","ⓤ":"u","u":"u","ù":"u","ú":"u","û":"u","ũ":"u","ṹ":"u","ū":"u","ṻ":"u","ŭ":"u","ü":"u","ǜ":"u","ǘ":"u","ǖ":"u","ǚ":"u","ủ":"u","ů":"u","ű":"u","ǔ":"u","ȕ":"u","ȗ":"u","ư":"u","ừ":"u","ứ":"u","ữ":"u","ử":"u","ự":"u","ụ":"u","ṳ":"u","ų":"u","ṷ":"u","ṵ":"u","ʉ":"u","ⓥ":"v","v":"v","ṽ":"v","ṿ":"v","ʋ":"v","ꝟ":"v","ʌ":"v","ꝡ":"vy","ⓦ":"w","w":"w","ẁ":"w","ẃ":"w","ŵ":"w","ẇ":"w","ẅ":"w","ẘ":"w","ẉ":"w","ⱳ":"w","ⓧ":"x","x":"x","ẋ":"x","ẍ":"x","ⓨ":"y","y":"y","ỳ":"y","ý":"y","ŷ":"y","ỹ":"y","ȳ":"y","ẏ":"y","ÿ":"y","ỷ":"y","ẙ":"y","ỵ":"y","ƴ":"y","ɏ":"y","ỿ":"y","ⓩ":"z","z":"z","ź":"z","ẑ":"z","ż":"z","ž":"z","ẓ":"z","ẕ":"z","ƶ":"z","ȥ":"z","ɀ":"z","ⱬ":"z","ꝣ":"z","Ά":"Α","Έ":"Ε","Ή":"Η","Ί":"Ι","Ϊ":"Ι","Ό":"Ο","Ύ":"Υ","Ϋ":"Υ","Ώ":"Ω","ά":"α","έ":"ε","ή":"η","ί":"ι","ϊ":"ι","ΐ":"ι","ό":"ο","ύ":"υ","ϋ":"υ","ΰ":"υ","ω":"ω","ς":"σ"}}),b.define("select2/data/base",["../utils"],function(a){function b(a,c){b.__super__.constructor.call(this)}return a.Extend(b,a.Observable),b.prototype.current=function(a){throw new Error("The `current` method must be defined in child classes.")},b.prototype.query=function(a,b){throw new Error("The `query` method must be defined in child classes.")},b.prototype.bind=function(a,b){},b.prototype.destroy=function(){},b.prototype.generateResultId=function(b,c){var d="";return d+=null!=b?b.id:a.generateChars(4),d+="-result-",d+=a.generateChars(4),null!=c.id?d+="-"+c.id.toString():d+="-"+a.generateChars(4),d},b}),b.define("select2/data/select",["./base","../utils","jquery"],function(a,b,c){function d(a,b){this.$element=a,this.options=b,d.__super__.constructor.call(this)}return b.Extend(d,a),d.prototype.current=function(a){var b=[],d=this;this.$element.find(":selected").each(function(){var a=c(this),e=d.item(a);b.push(e)}),a(b)},d.prototype.select=function(a){var b=this;if(a.selected=!0,c(a.element).is("option"))return a.element.selected=!0,void this.$element.trigger("change");if(this.$element.prop("multiple"))this.current(function(d){var e=[];a=[a],a.push.apply(a,d);for(var f=0;f=0){var k=f.filter(d(j)),l=this.item(k),m=c.extend(!0,{},j,l),n=this.option(m);k.replaceWith(n)}else{var o=this.option(j);if(j.children){var p=this.convertToOptions(j.children);b.appendMany(o,p)}h.push(o)}}return h},d}),b.define("select2/data/ajax",["./array","../utils","jquery"],function(a,b,c){function d(a,b){this.ajaxOptions=this._applyDefaults(b.get("ajax")),null!=this.ajaxOptions.processResults&&(this.processResults=this.ajaxOptions.processResults),d.__super__.constructor.call(this,a,b)}return b.Extend(d,a),d.prototype._applyDefaults=function(a){var b={data:function(a){return c.extend({},a,{q:a.term})},transport:function(a,b,d){var e=c.ajax(a);return e.then(b),e.fail(d),e}};return c.extend({},b,a,!0)},d.prototype.processResults=function(a){return a},d.prototype.query=function(a,b){function d(){var d=f.transport(f,function(d){var f=e.processResults(d,a);e.options.get("debug")&&window.console&&console.error&&(f&&f.results&&c.isArray(f.results)||console.error("Select2: The AJAX results did not return an array in the `results` key of the response.")),b(f)},function(){d.status&&"0"===d.status||e.trigger("results:message",{message:"errorLoading"})});e._request=d}var e=this;null!=this._request&&(c.isFunction(this._request.abort)&&this._request.abort(),this._request=null);var f=c.extend({type:"GET"},this.ajaxOptions);"function"==typeof f.url&&(f.url=f.url.call(this.$element,a)),"function"==typeof f.data&&(f.data=f.data.call(this.$element,a)),this.ajaxOptions.delay&&null!=a.term?(this._queryTimeout&&window.clearTimeout(this._queryTimeout),this._queryTimeout=window.setTimeout(d,this.ajaxOptions.delay)):d()},d}),b.define("select2/data/tags",["jquery"],function(a){function b(b,c,d){var e=d.get("tags"),f=d.get("createTag");void 0!==f&&(this.createTag=f);var g=d.get("insertTag");if(void 0!==g&&(this.insertTag=g),b.call(this,c,d),a.isArray(e))for(var h=0;h0&&b.term.length>this.maximumInputLength)return void this.trigger("results:message",{message:"inputTooLong",args:{maximum:this.maximumInputLength,input:b.term,params:b}});a.call(this,b,c)},a}),b.define("select2/data/maximumSelectionLength",[],function(){function a(a,b,c){this.maximumSelectionLength=c.get("maximumSelectionLength"),a.call(this,b,c)}return a.prototype.query=function(a,b,c){var d=this;this.current(function(e){var f=null!=e?e.length:0;if(d.maximumSelectionLength>0&&f>=d.maximumSelectionLength)return void d.trigger("results:message",{message:"maximumSelected",args:{maximum:d.maximumSelectionLength}});a.call(d,b,c)})},a}),b.define("select2/dropdown",["jquery","./utils"],function(a,b){function c(a,b){this.$element=a,this.options=b,c.__super__.constructor.call(this)}return b.Extend(c,b.Observable),c.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$dropdown=b,b},c.prototype.bind=function(){},c.prototype.position=function(a,b){},c.prototype.destroy=function(){this.$dropdown.remove()},c}),b.define("select2/dropdown/search",["jquery","../utils"],function(a,b){function c(){}return c.prototype.render=function(b){var c=b.call(this),d=a('');return this.$searchContainer=d,this.$search=d.find("input"),c.prepend(d),c},c.prototype.bind=function(b,c,d){var e=this,f=c.id+"-results";b.call(this,c,d),this.$search.on("keydown",function(a){e.trigger("keypress",a),e._keyUpPrevented=a.isDefaultPrevented()}),this.$search.on("input",function(b){a(this).off("keyup")}),this.$search.on("keyup input",function(a){e.handleSearch(a)}),c.on("open",function(){e.$search.attr("tabindex",0),e.$search.attr("aria-owns",f),e.$search.focus(),window.setTimeout(function(){e.$search.focus()},0)}),c.on("close",function(){e.$search.attr("tabindex",-1),e.$search.removeAttr("aria-activedescendant"),e.$search.removeAttr("aria-owns"),e.$search.val("")}),c.on("focus",function(){c.isOpen()&&e.$search.focus()}),c.on("results:all",function(a){if(null==a.query.term||""===a.query.term){e.showSearch(a)?e.$searchContainer.removeClass("select2-search--hide"):e.$searchContainer.addClass("select2-search--hide")}}),c.on("results:focus",function(a){e.$search.attr("aria-activedescendant",a.data._resultId)})},c.prototype.handleSearch=function(a){if(!this._keyUpPrevented){var b=this.$search.val();this.trigger("query",{term:b})}this._keyUpPrevented=!1},c.prototype.showSearch=function(a,b){return!0},c}),b.define("select2/dropdown/hidePlaceholder",[],function(){function a(a,b,c,d){this.placeholder=this.normalizePlaceholder(c.get("placeholder")),a.call(this,b,c,d)}return a.prototype.append=function(a,b){b.results=this.removePlaceholder(b.results),a.call(this,b)},a.prototype.normalizePlaceholder=function(a,b){return"string"==typeof b&&(b={id:"",text:b}),b},a.prototype.removePlaceholder=function(a,b){for(var c=b.slice(0),d=b.length-1;d>=0;d--){var e=b[d];this.placeholder.id===e.id&&c.splice(d,1)}return c},a}),b.define("select2/dropdown/infiniteScroll",["jquery"],function(a){function b(a,b,c,d){this.lastParams={},a.call(this,b,c,d),this.$loadingMore=this.createLoadingMore(),this.loading=!1}return b.prototype.append=function(a,b){this.$loadingMore.remove(),this.loading=!1,a.call(this,b),this.showLoadingMore(b)&&this.$results.append(this.$loadingMore)},b.prototype.bind=function(b,c,d){var e=this;b.call(this,c,d),c.on("query",function(a){e.lastParams=a,e.loading=!0}),c.on("query:append",function(a){e.lastParams=a,e.loading=!0}),this.$results.on("scroll",function(){var b=a.contains(document.documentElement,e.$loadingMore[0]);if(!e.loading&&b){e.$results.offset().top+e.$results.outerHeight(!1)+50>=e.$loadingMore.offset().top+e.$loadingMore.outerHeight(!1)&&e.loadMore()}})},b.prototype.loadMore=function(){this.loading=!0;var b=a.extend({},{page:1},this.lastParams);b.page++,this.trigger("query:append",b)},b.prototype.showLoadingMore=function(a,b){return b.pagination&&b.pagination.more},b.prototype.createLoadingMore=function(){var b=a('
              • '),c=this.options.get("translations").get("loadingMore");return b.html(c(this.lastParams)),b},b}),b.define("select2/dropdown/attachBody",["jquery","../utils"],function(a,b){function c(b,c,d){this.$dropdownParent=d.get("dropdownParent")||a(document.body),b.call(this,c,d)}return c.prototype.bind=function(a,b,c){var d=this,e=!1;a.call(this,b,c),b.on("open",function(){d._showDropdown(),d._attachPositioningHandler(b),e||(e=!0,b.on("results:all",function(){d._positionDropdown(),d._resizeDropdown()}),b.on("results:append",function(){d._positionDropdown(),d._resizeDropdown()}))}),b.on("close",function(){d._hideDropdown(),d._detachPositioningHandler(b)}),this.$dropdownContainer.on("mousedown",function(a){a.stopPropagation()})},c.prototype.destroy=function(a){a.call(this),this.$dropdownContainer.remove()},c.prototype.position=function(a,b,c){b.attr("class",c.attr("class")),b.removeClass("select2"),b.addClass("select2-container--open"),b.css({position:"absolute",top:-999999}),this.$container=c},c.prototype.render=function(b){var c=a(""),d=b.call(this);return c.append(d),this.$dropdownContainer=c,c},c.prototype._hideDropdown=function(a){this.$dropdownContainer.detach()},c.prototype._attachPositioningHandler=function(c,d){var e=this,f="scroll.select2."+d.id,g="resize.select2."+d.id,h="orientationchange.select2."+d.id,i=this.$container.parents().filter(b.hasScroll);i.each(function(){a(this).data("select2-scroll-position",{x:a(this).scrollLeft(),y:a(this).scrollTop()})}),i.on(f,function(b){var c=a(this).data("select2-scroll-position");a(this).scrollTop(c.y)}),a(window).on(f+" "+g+" "+h,function(a){e._positionDropdown(),e._resizeDropdown()})},c.prototype._detachPositioningHandler=function(c,d){var e="scroll.select2."+d.id,f="resize.select2."+d.id,g="orientationchange.select2."+d.id;this.$container.parents().filter(b.hasScroll).off(e),a(window).off(e+" "+f+" "+g)},c.prototype._positionDropdown=function(){var b=a(window),c=this.$dropdown.hasClass("select2-dropdown--above"),d=this.$dropdown.hasClass("select2-dropdown--below"),e=null,f=this.$container.offset();f.bottom=f.top+this.$container.outerHeight(!1);var g={height:this.$container.outerHeight(!1)};g.top=f.top,g.bottom=f.top+g.height;var h={height:this.$dropdown.outerHeight(!1)},i={top:b.scrollTop(),bottom:b.scrollTop()+b.height()},j=i.topf.bottom+h.height,l={left:f.left,top:g.bottom},m=this.$dropdownParent;"static"===m.css("position")&&(m=m.offsetParent());var n=m.offset();l.top-=n.top,l.left-=n.left,c||d||(e="below"),k||!j||c?!j&&k&&c&&(e="below"):e="above",("above"==e||c&&"below"!==e)&&(l.top=g.top-n.top-h.height),null!=e&&(this.$dropdown.removeClass("select2-dropdown--below select2-dropdown--above").addClass("select2-dropdown--"+e),this.$container.removeClass("select2-container--below select2-container--above").addClass("select2-container--"+e)),this.$dropdownContainer.css(l)},c.prototype._resizeDropdown=function(){var a={width:this.$container.outerWidth(!1)+"px"};this.options.get("dropdownAutoWidth")&&(a.minWidth=a.width,a.position="relative",a.width="auto"),this.$dropdown.css(a)},c.prototype._showDropdown=function(a){this.$dropdownContainer.appendTo(this.$dropdownParent),this._positionDropdown(),this._resizeDropdown()},c}),b.define("select2/dropdown/minimumResultsForSearch",[],function(){function a(b){for(var c=0,d=0;d0&&(l.dataAdapter=j.Decorate(l.dataAdapter,r)),l.maximumInputLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,s)),l.maximumSelectionLength>0&&(l.dataAdapter=j.Decorate(l.dataAdapter,t)),l.tags&&(l.dataAdapter=j.Decorate(l.dataAdapter,p)),null==l.tokenSeparators&&null==l.tokenizer||(l.dataAdapter=j.Decorate(l.dataAdapter,q)),null!=l.query){var C=b(l.amdBase+"compat/query");l.dataAdapter=j.Decorate(l.dataAdapter,C)}if(null!=l.initSelection){var D=b(l.amdBase+"compat/initSelection");l.dataAdapter=j.Decorate(l.dataAdapter,D)}}if(null==l.resultsAdapter&&(l.resultsAdapter=c,null!=l.ajax&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,x)),null!=l.placeholder&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,w)),l.selectOnClose&&(l.resultsAdapter=j.Decorate(l.resultsAdapter,A))),null==l.dropdownAdapter){if(l.multiple)l.dropdownAdapter=u;else{var E=j.Decorate(u,v);l.dropdownAdapter=E}if(0!==l.minimumResultsForSearch&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,z)),l.closeOnSelect&&(l.dropdownAdapter=j.Decorate(l.dropdownAdapter,B)),null!=l.dropdownCssClass||null!=l.dropdownCss||null!=l.adaptDropdownCssClass){var F=b(l.amdBase+"compat/dropdownCss");l.dropdownAdapter=j.Decorate(l.dropdownAdapter,F)}l.dropdownAdapter=j.Decorate(l.dropdownAdapter,y)}if(null==l.selectionAdapter){if(l.multiple?l.selectionAdapter=e:l.selectionAdapter=d,null!=l.placeholder&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,f)),l.allowClear&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,g)),l.multiple&&(l.selectionAdapter=j.Decorate(l.selectionAdapter,h)),null!=l.containerCssClass||null!=l.containerCss||null!=l.adaptContainerCssClass){var G=b(l.amdBase+"compat/containerCss");l.selectionAdapter=j.Decorate(l.selectionAdapter,G)}l.selectionAdapter=j.Decorate(l.selectionAdapter,i)}if("string"==typeof l.language)if(l.language.indexOf("-")>0){var H=l.language.split("-"),I=H[0];l.language=[l.language,I]}else l.language=[l.language];if(a.isArray(l.language)){var J=new k;l.language.push("en");for(var K=l.language,L=0;L0){for(var f=a.extend(!0,{},e),g=e.children.length-1;g>=0;g--){null==c(d,e.children[g])&&f.children.splice(g,1)}return f.children.length>0?f:c(d,f)}var h=b(e.text).toUpperCase(),i=b(d.term).toUpperCase();return h.indexOf(i)>-1?e:null}this.defaults={amdBase:"./",amdLanguageBase:"./i18n/",closeOnSelect:!0,debug:!1,dropdownAutoWidth:!1,escapeMarkup:j.escapeMarkup,language:C,matcher:c,minimumInputLength:0,maximumInputLength:0,maximumSelectionLength:0,minimumResultsForSearch:0,selectOnClose:!1,sorter:function(a){return a},templateResult:function(a){return a.text},templateSelection:function(a){return a.text},theme:"default",width:"resolve"}},D.prototype.set=function(b,c){var d=a.camelCase(b),e={};e[d]=c;var f=j._convertData(e);a.extend(this.defaults,f)},new D}),b.define("select2/options",["require","jquery","./defaults","./utils"],function(a,b,c,d){function e(b,e){if(this.options=b,null!=e&&this.fromElement(e),this.options=c.apply(this.options),e&&e.is("input")){var f=a(this.get("amdBase")+"compat/inputData");this.options.dataAdapter=d.Decorate(this.options.dataAdapter,f)}}return e.prototype.fromElement=function(a){var c=["select2"];null==this.options.multiple&&(this.options.multiple=a.prop("multiple")),null==this.options.disabled&&(this.options.disabled=a.prop("disabled")),null==this.options.language&&(a.prop("lang")?this.options.language=a.prop("lang").toLowerCase():a.closest("[lang]").prop("lang")&&(this.options.language=a.closest("[lang]").prop("lang"))),null==this.options.dir&&(a.prop("dir")?this.options.dir=a.prop("dir"):a.closest("[dir]").prop("dir")?this.options.dir=a.closest("[dir]").prop("dir"):this.options.dir="ltr"),a.prop("disabled",this.options.disabled),a.prop("multiple",this.options.multiple),a.data("select2Tags")&&(this.options.debug&&window.console&&console.warn&&console.warn('Select2: The `data-select2-tags` attribute has been changed to use the `data-data` and `data-tags="true"` attributes and will be removed in future versions of Select2.'),a.data("data",a.data("select2Tags")),a.data("tags",!0)),a.data("ajaxUrl")&&(this.options.debug&&window.console&&console.warn&&console.warn("Select2: The `data-ajax-url` attribute has been changed to `data-ajax--url` and support for the old attribute will be removed in future versions of Select2."),a.attr("ajax--url",a.data("ajaxUrl")),a.data("ajax--url",a.data("ajaxUrl")));var e={};e=b.fn.jquery&&"1."==b.fn.jquery.substr(0,2)&&a[0].dataset?b.extend(!0,{},a[0].dataset,a.data()):a.data();var f=b.extend(!0,{},e);f=d._convertData(f);for(var g in f)b.inArray(g,c)>-1||(b.isPlainObject(this.options[g])?b.extend(this.options[g],f[g]):this.options[g]=f[g]);return this},e.prototype.get=function(a){return this.options[a]},e.prototype.set=function(a,b){this.options[a]=b},e}),b.define("select2/core",["jquery","./options","./utils","./keys"],function(a,b,c,d){var e=function(a,c){null!=a.data("select2")&&a.data("select2").destroy(),this.$element=a,this.id=this._generateId(a),c=c||{},this.options=new b(c,a),e.__super__.constructor.call(this);var d=a.attr("tabindex")||0;a.data("old-tabindex",d),a.attr("tabindex","-1");var f=this.options.get("dataAdapter");this.dataAdapter=new f(a,this.options);var g=this.render();this._placeContainer(g);var h=this.options.get("selectionAdapter");this.selection=new h(a,this.options),this.$selection=this.selection.render(),this.selection.position(this.$selection,g);var i=this.options.get("dropdownAdapter");this.dropdown=new i(a,this.options),this.$dropdown=this.dropdown.render(),this.dropdown.position(this.$dropdown,g);var j=this.options.get("resultsAdapter");this.results=new j(a,this.options,this.dataAdapter),this.$results=this.results.render(),this.results.position(this.$results,this.$dropdown);var k=this;this._bindAdapters(),this._registerDomEvents(),this._registerDataEvents(),this._registerSelectionEvents(),this._registerDropdownEvents(),this._registerResultsEvents(),this._registerEvents(),this.dataAdapter.current(function(a){k.trigger("selection:update",{data:a})}),a.addClass("select2-hidden-accessible"),a.attr("aria-hidden","true"),this._syncAttributes(),a.data("select2",this)};return c.Extend(e,c.Observable),e.prototype._generateId=function(a){var b="";return b=null!=a.attr("id")?a.attr("id"):null!=a.attr("name")?a.attr("name")+"-"+c.generateChars(2):c.generateChars(4),b=b.replace(/(:|\.|\[|\]|,)/g,""),b="select2-"+b},e.prototype._placeContainer=function(a){a.insertAfter(this.$element);var b=this._resolveWidth(this.$element,this.options.get("width"));null!=b&&a.css("width",b)},e.prototype._resolveWidth=function(a,b){var c=/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;if("resolve"==b){var d=this._resolveWidth(a,"style");return null!=d?d:this._resolveWidth(a,"element")}if("element"==b){var e=a.outerWidth(!1);return e<=0?"auto":e+"px"}if("style"==b){var f=a.attr("style");if("string"!=typeof f)return null;for(var g=f.split(";"),h=0,i=g.length;h=1)return k[1]}return null}return b},e.prototype._bindAdapters=function(){this.dataAdapter.bind(this,this.$container),this.selection.bind(this,this.$container),this.dropdown.bind(this,this.$container),this.results.bind(this,this.$container)},e.prototype._registerDomEvents=function(){var b=this;this.$element.on("change.select2",function(){b.dataAdapter.current(function(a){b.trigger("selection:update",{data:a})})}),this.$element.on("focus.select2",function(a){b.trigger("focus",a)}),this._syncA=c.bind(this._syncAttributes,this),this._syncS=c.bind(this._syncSubtree,this),this.$element[0].attachEvent&&this.$element[0].attachEvent("onpropertychange",this._syncA);var d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver;null!=d?(this._observer=new d(function(c){a.each(c,b._syncA),a.each(c,b._syncS)}),this._observer.observe(this.$element[0],{attributes:!0,childList:!0,subtree:!1})):this.$element[0].addEventListener&&(this.$element[0].addEventListener("DOMAttrModified",b._syncA,!1),this.$element[0].addEventListener("DOMNodeInserted",b._syncS,!1),this.$element[0].addEventListener("DOMNodeRemoved",b._syncS,!1))},e.prototype._registerDataEvents=function(){var a=this;this.dataAdapter.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerSelectionEvents=function(){var b=this,c=["toggle","focus"];this.selection.on("toggle",function(){b.toggleDropdown()}),this.selection.on("focus",function(a){b.focus(a)}),this.selection.on("*",function(d,e){-1===a.inArray(d,c)&&b.trigger(d,e)})},e.prototype._registerDropdownEvents=function(){var a=this;this.dropdown.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerResultsEvents=function(){var a=this;this.results.on("*",function(b,c){a.trigger(b,c)})},e.prototype._registerEvents=function(){var a=this;this.on("open",function(){a.$container.addClass("select2-container--open")}),this.on("close",function(){a.$container.removeClass("select2-container--open")}),this.on("enable",function(){a.$container.removeClass("select2-container--disabled")}),this.on("disable",function(){a.$container.addClass("select2-container--disabled")}),this.on("blur",function(){a.$container.removeClass("select2-container--focus")}),this.on("query",function(b){a.isOpen()||a.trigger("open",{}),this.dataAdapter.query(b,function(c){a.trigger("results:all",{data:c,query:b})})}),this.on("query:append",function(b){this.dataAdapter.query(b,function(c){a.trigger("results:append",{data:c,query:b})})}),this.on("keypress",function(b){var c=b.which;a.isOpen()?c===d.ESC||c===d.TAB||c===d.UP&&b.altKey?(a.close(),b.preventDefault()):c===d.ENTER?(a.trigger("results:select",{}),b.preventDefault()):c===d.SPACE&&b.ctrlKey?(a.trigger("results:toggle",{}),b.preventDefault()):c===d.UP?(a.trigger("results:previous",{}),b.preventDefault()):c===d.DOWN&&(a.trigger("results:next",{}),b.preventDefault()):(c===d.ENTER||c===d.SPACE||c===d.DOWN&&b.altKey)&&(a.open(),b.preventDefault())})},e.prototype._syncAttributes=function(){this.options.set("disabled",this.$element.prop("disabled")),this.options.get("disabled")?(this.isOpen()&&this.close(),this.trigger("disable",{})):this.trigger("enable",{})},e.prototype._syncSubtree=function(a,b){var c=!1,d=this;if(!a||!a.target||"OPTION"===a.target.nodeName||"OPTGROUP"===a.target.nodeName){if(b)if(b.addedNodes&&b.addedNodes.length>0)for(var e=0;e0&&(c=!0);else c=!0;c&&this.dataAdapter.current(function(a){d.trigger("selection:update",{data:a})})}},e.prototype.trigger=function(a,b){var c=e.__super__.trigger,d={open:"opening",close:"closing",select:"selecting",unselect:"unselecting"};if(void 0===b&&(b={}),a in d){var f=d[a],g={prevented:!1,name:a,args:b};if(c.call(this,f,g),g.prevented)return void(b.prevented=!0)}c.call(this,a,b)},e.prototype.toggleDropdown=function(){this.options.get("disabled")||(this.isOpen()?this.close():this.open())},e.prototype.open=function(){this.isOpen()||this.trigger("query",{})},e.prototype.close=function(){this.isOpen()&&this.trigger("close",{})},e.prototype.isOpen=function(){return this.$container.hasClass("select2-container--open")},e.prototype.hasFocus=function(){return this.$container.hasClass("select2-container--focus")},e.prototype.focus=function(a){this.hasFocus()||(this.$container.addClass("select2-container--focus"),this.trigger("focus",{}))},e.prototype.enable=function(a){this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("enable")` method has been deprecated and will be removed in later Select2 versions. Use $element.prop("disabled") instead.'),null!=a&&0!==a.length||(a=[!0]);var b=!a[0];this.$element.prop("disabled",b)},e.prototype.data=function(){this.options.get("debug")&&arguments.length>0&&window.console&&console.warn&&console.warn('Select2: Data can no longer be set using `select2("data")`. You should consider setting the value instead using `$element.val()`.');var a=[];return this.dataAdapter.current(function(b){a=b}),a},e.prototype.val=function(b){if(this.options.get("debug")&&window.console&&console.warn&&console.warn('Select2: The `select2("val")` method has been deprecated and will be removed in later Select2 versions. Use $element.val() instead.'),null==b||0===b.length)return this.$element.val();var c=b[0];a.isArray(c)&&(c=a.map(c,function(a){return a.toString()})),this.$element.val(c).trigger("change")},e.prototype.destroy=function(){this.$container.remove(),this.$element[0].detachEvent&&this.$element[0].detachEvent("onpropertychange",this._syncA),null!=this._observer?(this._observer.disconnect(),this._observer=null):this.$element[0].removeEventListener&&(this.$element[0].removeEventListener("DOMAttrModified",this._syncA,!1),this.$element[0].removeEventListener("DOMNodeInserted",this._syncS,!1),this.$element[0].removeEventListener("DOMNodeRemoved",this._syncS,!1)),this._syncA=null,this._syncS=null,this.$element.off(".select2"),this.$element.attr("tabindex",this.$element.data("old-tabindex")),this.$element.removeClass("select2-hidden-accessible"),this.$element.attr("aria-hidden","false"),this.$element.removeData("select2"),this.dataAdapter.destroy(),this.selection.destroy(),this.dropdown.destroy(),this.results.destroy(),this.dataAdapter=null,this.selection=null,this.dropdown=null,this.results=null},e.prototype.render=function(){var b=a('');return b.attr("dir",this.options.get("dir")),this.$container=b,this.$container.addClass("select2-container--"+this.options.get("theme")),b.data("element",this.$element),b},e}),b.define("jquery-mousewheel",["jquery"],function(a){return a}),b.define("jquery.select2",["jquery","jquery-mousewheel","./select2/core","./select2/defaults"],function(a,b,c,d){if(null==a.fn.selectWoo){var e=["open","close","destroy"];a.fn.selectWoo=function(b){if("object"==typeof(b=b||{}))return this.each(function(){var d=a.extend(!0,{},b);new c(a(this),d)}),this;if("string"==typeof b){var d,f=Array.prototype.slice.call(arguments,1);return this.each(function(){var c=a(this).data("select2");null==c&&window.console&&console.error&&console.error("The select2('"+b+"') method was called on an element that is not using Select2."),d=c[b].apply(c,f)}),a.inArray(b,e)>-1?this:d}throw new Error("Invalid arguments for Select2: "+b)}}return null!=a.fn.select2&&null!=a.fn.select2.defaults&&(a.fn.selectWoo.defaults=a.fn.select2.defaults),null==a.fn.selectWoo.defaults&&(a.fn.selectWoo.defaults=d),a.fn.select2=a.fn.select2||a.fn.selectWoo,c}),{define:b.define,require:b.require}}(),c=b.require("jquery.select2");return a.fn.select2.amd=b,a.fn.selectWoo.amd=b,c}); \ No newline at end of file From 48f03b15ed0a8462b3ad4a4576ef7dec8ea53be4 Mon Sep 17 00:00:00 2001 From: claudiulodro Date: Fri, 4 Aug 2017 14:27:22 -0700 Subject: [PATCH 189/224] Use correct server variable --- includes/class-wc-geolocation.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-geolocation.php b/includes/class-wc-geolocation.php index 3ee4fa27cbe..66617a3bf99 100644 --- a/includes/class-wc-geolocation.php +++ b/includes/class-wc-geolocation.php @@ -98,8 +98,8 @@ class WC_Geolocation { * @return string */ public static function get_ip_address() { - if ( isset( $_SERVER['X-Real-IP'] ) ) { - return $_SERVER['X-Real-IP']; + if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) { + return $_SERVER['HTTP_X_REAL_IP']; } elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { // Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2 // Make sure we always only send through the first IP in the list which should always be the client IP. From 8ebe8a1720c44d28b226a41b9d0d950b506cd9a9 Mon Sep 17 00:00:00 2001 From: James Kemp Date: Sat, 5 Aug 2017 12:05:47 +0100 Subject: [PATCH 190/224] Add sorting ID and menu orders to sorting action --- includes/class-wc-ajax.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 1e41cb4a022..93489455a89 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -1411,7 +1411,7 @@ class WC_AJAX { $wpdb->update( $wpdb->posts, array( 'menu_order' => $menu_orders[ $sorting_id ] ), array( 'ID' => $sorting_id ) ); - do_action( 'woocommerce_after_product_ordering' ); + do_action( 'woocommerce_after_product_ordering', $sorting_id, $menu_orders ); wp_send_json( $menu_orders ); } From 393abc96193c6a973576f206c2e9c71229853191 Mon Sep 17 00:00:00 2001 From: Jesse Ostrander Date: Sun, 6 Aug 2017 12:16:46 -0400 Subject: [PATCH 191/224] Fix typo in login-form.php template. Woocomerce -> woocommerce --- templates/myaccount/form-login.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/myaccount/form-login.php b/templates/myaccount/form-login.php index c916fdd48f1..426afb8f757 100644 --- a/templates/myaccount/form-login.php +++ b/templates/myaccount/form-login.php @@ -36,7 +36,7 @@ if ( ! defined( 'ABSPATH' ) ) {

                -
                - -

                From d46988b6a6d397f69ebeee6e45b6376b01b53b79 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 7 Aug 2017 12:39:16 +0100 Subject: [PATCH 199/224] Improve add payment method logic --- includes/class-wc-form-handler.php | 36 ++++++++++++++++++-------- includes/class-wc-payment-gateways.php | 4 +-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/includes/class-wc-form-handler.php b/includes/class-wc-form-handler.php index 25c7f750764..a4be37cdc3c 100644 --- a/includes/class-wc-form-handler.php +++ b/includes/class-wc-form-handler.php @@ -374,27 +374,41 @@ class WC_Form_Handler { */ public static function add_payment_method_action() { if ( isset( $_POST['woocommerce_add_payment_method'], $_POST['payment_method'], $_POST['_wpnonce'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'woocommerce-add-payment-method' ) ) { - ob_start(); - $payment_method = wc_clean( $_POST['payment_method'] ); - + $payment_method_id = wc_clean( wp_unslash( $_POST['payment_method'] ) ); $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); - // Validate - $available_gateways[ $payment_method ]->validate_fields(); - // Process - if ( wc_notice_count( 'wc_errors' ) == 0 ) { - $result = $available_gateways[ $payment_method ]->add_payment_method(); - // Redirect to success/confirmation/payment page + if ( isset( $available_gateways[ $payment_method_id ] ) ) { + $gateway = $available_gateways[ $payment_method_id ]; + + if ( ! $gateway->supports( 'add_payment_method' ) && ! $gateway->supports( 'tokenization' ) ) { + wc_add_notice( __( 'Invalid payment gateway.', 'woocommerce' ), 'error' ); + return; + } + + $gateway->validate_fields(); + + if ( wc_notice_count( 'error' ) > 0 ) { + return; + } + + $result = $gateway->add_payment_method(); + if ( 'success' === $result['result'] ) { - wc_add_notice( __( 'Payment method added.', 'woocommerce' ) ); + wc_add_notice( __( 'Payment method successfully added.', 'woocommerce' ) ); + } + + if ( 'failure' === $result['result'] ) { + wc_add_notice( __( 'Unable to add payment method to your account.', 'woocommerce' ), 'error' ); + } + + if ( ! empty( $result['redirect'] ) ) { wp_redirect( $result['redirect'] ); exit(); } } } - } /** diff --git a/includes/class-wc-payment-gateways.php b/includes/class-wc-payment-gateways.php index 46fc76063a7..b04e72ad318 100644 --- a/includes/class-wc-payment-gateways.php +++ b/includes/class-wc-payment-gateways.php @@ -154,9 +154,7 @@ class WC_Payment_Gateways { if ( $gateway->is_available() ) { if ( ! is_add_payment_method_page() ) { $_available_gateways[ $gateway->id ] = $gateway; - } elseif ( $gateway->supports( 'add_payment_method' ) ) { - $_available_gateways[ $gateway->id ] = $gateway; - } elseif ( $gateway->supports( 'tokenization' ) ) { + } elseif ( $gateway->supports( 'add_payment_method' ) || $gateway->supports( 'tokenization' ) ) { $_available_gateways[ $gateway->id ] = $gateway; } } From 9022261f13f558ceba5f9c7292a3d82248bfa9f0 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Mon, 7 Aug 2017 15:29:17 +0200 Subject: [PATCH 200/224] Fix cache issue. From discussion with @mikejolley: The class is not doing its job of invaliding cache after adding a line item. At any point (action hook), a 3pd can call `get_items` and it will cache the items for that particular order so any subsequent calls to it will return bad data. Unless you ADD items, `get_items` will return good data. So adding items in this case is not invalidating, so that's the bug. Relates to: https://github.com/woocommerce/woocommerce-bookings/issues/1310 --- includes/data-stores/abstract-wc-order-item-type-data-store.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/data-stores/abstract-wc-order-item-type-data-store.php b/includes/data-stores/abstract-wc-order-item-type-data-store.php index 3de53d7c547..9bbf98f4a6c 100644 --- a/includes/data-stores/abstract-wc-order-item-type-data-store.php +++ b/includes/data-stores/abstract-wc-order-item-type-data-store.php @@ -143,5 +143,6 @@ abstract class Abstract_WC_Order_Item_Type_Data_Store extends WC_Data_Store_WP i */ public function clear_cache( &$item ) { wp_cache_delete( 'item-' . $item->get_id(), 'order-items' ); + wp_cache_delete( 'order-items-' . $item->get_order_id(), 'orders' ); } } From 8db31eefe2a27f73cc696c18bce5cc72500a0869 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 7 Aug 2017 15:16:38 +0100 Subject: [PATCH 201/224] Fix variable stock status setting by using CRUD methods Closes #16287 --- includes/admin/class-wc-admin-post-types.php | 115 ++++++++----------- 1 file changed, 50 insertions(+), 65 deletions(-) diff --git a/includes/admin/class-wc-admin-post-types.php b/includes/admin/class-wc-admin-post-types.php index 4b1aa4cee3c..20d3f25498c 100644 --- a/includes/admin/class-wc-admin-post-types.php +++ b/includes/admin/class-wc-admin-post-types.php @@ -1032,40 +1032,33 @@ class WC_Admin_Post_Types { $stock_status = ! empty( $_REQUEST['_stock_status'] ) ? wc_clean( $_REQUEST['_stock_status'] ) : 'instock'; $stock_amount = 'yes' === $manage_stock ? wc_stock_amount( $_REQUEST['_stock'] ) : ''; + $product->set_manage_stock( $manage_stock ); + $product->set_backorders( $backorders ); + if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { - - // Apply product type constraints to stock status - if ( $product->is_type( 'external' ) ) { - // External always in stock - $stock_status = 'instock'; - } elseif ( $product->is_type( 'variable' ) ) { - // Stock status is always determined by children - foreach ( $product->get_children() as $child_id ) { - $child = wc_get_product( $child_id ); - if ( ! $product->get_manage_stock() ) { - $child->set_stock_status( $stock_status ); - $child->save(); - } - } - - $product = WC_Product_Variable::sync( $product, false ); - } - - $product->set_manage_stock( $manage_stock ); - $product->set_backorders( $backorders ); - $product->save(); - - if ( ! $product->is_type( 'variable' ) ) { - wc_update_product_stock_status( $post_id, $stock_status ); - } - - wc_update_product_stock( $post_id, $stock_amount ); - - } else { - $product->save(); - wc_update_product_stock_status( $post_id, $stock_status ); + $product->set_stock_quantity( $stock_amount ); } + // Apply product type constraints to stock status. + if ( $product->is_type( 'external' ) ) { + // External products are always in stock. + $product->set_stock_status( 'instock' ); + } elseif ( $product->is_type( 'variable' ) && ! $product->get_manage_stock() ) { + // Stock status is determined by children. + foreach ( $product->get_children() as $child_id ) { + $child = wc_get_product( $child_id ); + if ( ! $product->get_manage_stock() ) { + $child->set_stock_status( $stock_status ); + $child->save(); + } + } + $product = WC_Product_Variable::sync( $product, false ); + } else { + $product->set_stock_status( $stock_status ); + } + + $product->save(); + do_action( 'woocommerce_product_quick_edit_save', $product ); } @@ -1243,9 +1236,8 @@ class WC_Admin_Post_Types { $was_managing_stock = $product->get_manage_stock() ? 'yes' : 'no'; $stock_status = $product->get_stock_status(); $backorders = $product->get_backorders(); - - $backorders = ! empty( $_REQUEST['_backorders'] ) ? wc_clean( $_REQUEST['_backorders'] ) : $backorders; - $stock_status = ! empty( $_REQUEST['_stock_status'] ) ? wc_clean( $_REQUEST['_stock_status'] ) : $stock_status; + $backorders = ! empty( $_REQUEST['_backorders'] ) ? wc_clean( $_REQUEST['_backorders'] ) : $backorders; + $stock_status = ! empty( $_REQUEST['_stock_status'] ) ? wc_clean( $_REQUEST['_stock_status'] ) : $stock_status; if ( ! empty( $_REQUEST['_manage_stock'] ) ) { $manage_stock = 'yes' === wc_clean( $_REQUEST['_manage_stock'] ) && 'grouped' !== $product->get_type() ? 'yes' : 'no'; @@ -1255,40 +1247,33 @@ class WC_Admin_Post_Types { $stock_amount = 'yes' === $manage_stock && ! empty( $_REQUEST['change_stock'] ) ? wc_stock_amount( $_REQUEST['_stock'] ) : $product->get_stock_quantity(); + $product->set_manage_stock( $manage_stock ); + $product->set_backorders( $backorders ); + if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { - - // Apply product type constraints to stock status - if ( $product->is_type( 'external' ) ) { - // External always in stock - $stock_status = 'instock'; - } elseif ( $product->is_type( 'variable' ) ) { - // Stock status is always determined by children - foreach ( $product->get_children() as $child_id ) { - $child = wc_get_product( $child_id ); - if ( ! $product->get_manage_stock() ) { - $child->set_stock_status( $stock_status ); - $child->save(); - } - } - - $product = WC_Product_Variable::sync( $product, false ); - } - - $product->set_manage_stock( $manage_stock ); - $product->set_backorders( $backorders ); - $product->save(); - - if ( ! $product->is_type( 'variable' ) ) { - wc_update_product_stock_status( $post_id, $stock_status ); - } - - wc_update_product_stock( $post_id, $stock_amount ); - - } else { - $product->save(); - wc_update_product_stock_status( $post_id, $stock_status ); + $product->set_stock_quantity( $stock_amount ); } + // Apply product type constraints to stock status. + if ( $product->is_type( 'external' ) ) { + // External products are always in stock. + $product->set_stock_status( 'instock' ); + } elseif ( $product->is_type( 'variable' ) && ! $product->get_manage_stock() ) { + // Stock status is determined by children. + foreach ( $product->get_children() as $child_id ) { + $child = wc_get_product( $child_id ); + if ( ! $product->get_manage_stock() ) { + $child->set_stock_status( $stock_status ); + $child->save(); + } + } + $product = WC_Product_Variable::sync( $product, false ); + } else { + $product->set_stock_status( $stock_status ); + } + + $product->save(); + do_action( 'woocommerce_product_bulk_edit_save', $product ); } From 622b4b35d6d212d25aafe09bbf23f753f1137b89 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 7 Aug 2017 17:10:36 +0100 Subject: [PATCH 202/224] Remove white space --- includes/class-wc-cache-helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-cache-helper.php b/includes/class-wc-cache-helper.php index 7bbef730277..fb72f26b035 100644 --- a/includes/class-wc-cache-helper.php +++ b/includes/class-wc-cache-helper.php @@ -156,7 +156,7 @@ class WC_Cache_Helper { return; } $page_ids = array_filter( array( wc_get_page_id( 'cart' ), wc_get_page_id( 'checkout' ), wc_get_page_id( 'myaccount' ) ) ); - + if ( isset( $_GET['download_file'] ) || is_page( $page_ids ) ) { self::nocache(); } From 5e2ce545307359722f1e2692384c8b8390c3c0ff Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 7 Aug 2017 17:19:19 +0100 Subject: [PATCH 203/224] update tests --- tests/unit-tests/product/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit-tests/product/functions.php b/tests/unit-tests/product/functions.php index d4b9be2ce97..2c9f824b508 100644 --- a/tests/unit-tests/product/functions.php +++ b/tests/unit-tests/product/functions.php @@ -631,7 +631,7 @@ class WC_Tests_Product_Functions extends WC_Unit_Test_Case { 'key' => '_price', 'value' => array( 10, 100 ), 'compare' => 'BETWEEN', - 'type' => 'NUMERIC', + 'type' => 'DECIMAL(10,2)', ), $meta_query ); } From 7a829036c2bb985ef8b7b88abb153e94802b3a7d Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 7 Aug 2017 17:49:46 +0100 Subject: [PATCH 204/224] Fix tests --- includes/class-wc-tax.php | 2 +- tests/unit-tests/tax/tax.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-wc-tax.php b/includes/class-wc-tax.php index e458dc68ee3..a772f093615 100644 --- a/includes/class-wc-tax.php +++ b/includes/class-wc-tax.php @@ -553,7 +553,7 @@ class WC_Tax { 'tax_class' => $tax_class, ) ); - } else { + } elseif ( WC()->cart->get_cart() ) { // This will be per order shipping - loop through the order and find the highest tax class rate $cart_tax_classes = WC()->cart->get_cart_item_tax_classes(); diff --git a/tests/unit-tests/tax/tax.php b/tests/unit-tests/tax/tax.php index f949570b87f..46474148703 100644 --- a/tests/unit-tests/tax/tax.php +++ b/tests/unit-tests/tax/tax.php @@ -63,7 +63,7 @@ class WC_Tests_Tax extends WC_Unit_Test_Case { $tax_rates = WC_Tax::get_shipping_tax_rates(); - $this->assertEquals( $tax_rates, array( $tax_rate_id => array( 'rate' => '20.0000', 'label' => 'VAT', 'shipping' => 'yes', 'compound' => 'no' ) ) ); + $this->assertEquals( $tax_rates, array( $tax_rate_id => array( 'rate' => '20.0000', 'label' => 'VAT', 'shipping' => 'yes', 'compound' => 'no' ) ), print_r( $tax_rates, true ) ); WC_Tax::_delete_tax_rate( $tax_rate_id ); } From dd36ae87d3118ca93d58524f5e9ba906b342453a Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 13 Jul 2017 19:55:47 -0300 Subject: [PATCH 205/224] Improved wc_create_attribute() inline doc --- includes/wc-attribute-functions.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php index d44e6c98619..04d7f99b08c 100644 --- a/includes/wc-attribute-functions.php +++ b/includes/wc-attribute-functions.php @@ -382,17 +382,21 @@ function wc_get_attribute( $id ) { /** * Create attribute. * - * Options: - * - * id - Unique identifier, used to update an attribute. - * name - Attribute name. - * slug - Attribute alphanumeric identifier. - * type - Type of attribute, core options: 'select' and 'text', default to 'select'. - * order_by - Sort order, options: 'menu_order', 'name', 'name_num' and 'id'. - * has_archives - Enable or disable attribute archives. - * * @since 3.2.0 - * @param array $args Attribute arguments. + * @param array $args Attribute arguments { + * Array of attribute parameters. + * + * @type int $id Unique identifier, used to update an attribute. + * @type string $name Attribute name. Always required. + * @type string $slug Attribute alphanumeric identifier. + * @type string $type Type of attribute. + * Core by default accepts: 'select' and 'text'. + * Default to 'select'. + * @type string $order_by Sort order. + * Accepts: 'menu_order', 'name', 'name_num' and 'id'. + * Default to 'menu_order'. + * @type bool $has_archives Enable or disable attribute archives. False by default. + * } * @return int|WP_Error */ function wc_create_attribute( $args ) { From 29e73d27f4c754381f6221c9f77482e7cfcbffb7 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Thu, 13 Jul 2017 20:15:44 -0300 Subject: [PATCH 206/224] Introduced woocommerce_attribute_created And flush rewrite rules when created attribute. --- includes/wc-attribute-functions.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php index 04d7f99b08c..726cc5ca09e 100644 --- a/includes/wc-attribute-functions.php +++ b/includes/wc-attribute-functions.php @@ -472,7 +472,16 @@ function wc_create_attribute( $args ) { } } - // Clear transients. + /** + * Attribute created. + * + * @param int $id Created attribute ID. + * @param array $data Data used to created the attribute. + */ + do_action( 'woocommerce_attribute_created', $id, $data ); + + // Clear cache and flush rewrite rules. + wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); delete_transient( 'wc_attribute_taxonomies' ); return $id; From 6dfcf95b5e83a15ce5a34d6669106c79a8c77aef Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Mon, 7 Aug 2017 21:40:24 -0300 Subject: [PATCH 207/224] Properly update and delete attributes - Now keep all the original hooks without deprecated anything, - Update product metadata and terms metadata while changing slugs. - Delete terms while deleting attributes --- includes/wc-attribute-functions.php | 125 +++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 23 deletions(-) diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php index 726cc5ca09e..9f3dd2ad1e2 100644 --- a/includes/wc-attribute-functions.php +++ b/includes/wc-attribute-functions.php @@ -408,7 +408,7 @@ function wc_create_attribute( $args ) { // Name is required. if ( empty( $args['name'] ) ) { - return new WP_Error( 'missing_attribute_name', __( 'Missing attribute name', 'woocommerce' ), array( 'status' => 400 ) ); + return new WP_Error( 'missing_attribute_name', __( 'Please, provide an attribute name.', 'woocommerce' ), array( 'status' => 400 ) ); } // Set the attribute slug. @@ -423,7 +423,7 @@ function wc_create_attribute( $args ) { return new WP_Error( 'invalid_product_attribute_slug_too_long', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) ); } elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) { return new WP_Error( 'invalid_product_attribute_slug_reserved_name', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) ); - } elseif ( 0 === $id && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) { + } elseif ( ( 0 === $id && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) || ( isset( $args['old_slug'] ) && $args['old_slug'] !== $args['slug'] && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) ) { return new WP_Error( 'invalid_product_attribute_slug_already_exists', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), array( 'status' => 400 ) ); } @@ -442,7 +442,7 @@ function wc_create_attribute( $args ) { 'attribute_name' => $slug, 'attribute_type' => $args['type'], 'attribute_orderby' => $args['order_by'], - 'attribute_public' => isset( $args['has_archives'] ) ? (bool) $args['has_archives'] : false, + 'attribute_public' => isset( $args['has_archives'] ) ? (int) $args['has_archives'] : 0, ); // Create or update. @@ -458,6 +458,14 @@ function wc_create_attribute( $args ) { } $id = $wpdb->insert_id; + + /** + * Attribute added. + * + * @param int $id Added attribute ID. + * @param array $data Attribute data. + */ + do_action( 'woocommerce_attribute_added', $id, $data ); } else { $results = $wpdb->update( $wpdb->prefix . 'woocommerce_attribute_taxonomies', @@ -470,15 +478,52 @@ function wc_create_attribute( $args ) { if ( false === $results ) { return new WP_Error( 'cannot_update_attribute', __( 'Could not update the attribute.', 'woocommerce' ), array( 'status' => 400 ) ); } - } - /** - * Attribute created. - * - * @param int $id Created attribute ID. - * @param array $data Data used to created the attribute. - */ - do_action( 'woocommerce_attribute_created', $id, $data ); + // Set old_slug to check for database chances. + $args['old_slug'] = ! empty( $args['old_slug'] ) ? $args['old_slug'] : $args['slug']; + + /** + * Attribute updated. + * + * @param int $id Added attribute ID. + * @param array $data Attribute data. + * @param string $old_slug Attribute old name. + */ + do_action( 'woocommerce_attribute_updated', $id, $data, $args['old_slug'] ); + + if ( $args['old_slug'] !== $args['slug'] ) { + // Update taxonomies in the wp term taxonomy table. + $wpdb->update( + $wpdb->term_taxonomy, + array( 'taxonomy' => wc_attribute_taxonomy_name( $data['attribute_name'] ) ), + array( 'taxonomy' => 'pa_' . $args['old_slug'] ) + ); + + // Update taxonomy ordering term meta. + $table_name = get_option( 'db_version' ) < 34370 ? $wpdb->prefix . 'woocommerce_termmeta' : $wpdb->termmeta; + $wpdb->update( + $table_name, + array( 'meta_key' => 'order_pa_' . sanitize_title( $data['attribute_name'] ) ), + array( 'meta_key' => 'order_pa_' . sanitize_title( $args['old_slug'] ) ) + ); + + // Update product attributes which use this taxonomy. + $old_attribute_name_length = strlen( $args['old_slug'] ) + 3; + $attribute_name_length = strlen( $data['attribute_name'] ) + 3; + + $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE( meta_value, %s, %s ) WHERE meta_key = '_product_attributes'", + 's:' . $old_attribute_name_length . ':"pa_' . $args['old_slug'] . '"', + 's:' . $attribute_name_length . ':"pa_' . $data['attribute_name'] . '"' + ) ); + + // Update variations which use this taxonomy. + $wpdb->update( + $wpdb->postmeta, + array( 'meta_key' => 'attribute_pa_' . sanitize_title( $data['attribute_name'] ) ), + array( 'meta_key' => 'attribute_pa_' . sanitize_title( $args['old_slug'] ) ) + ); + } + } // Clear cache and flush rewrite rules. wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); @@ -495,9 +540,11 @@ function wc_create_attribute( $args ) { * @since 3.2.0 * @param int $id Attribute ID. * @param array $args Attribute arguments. - * @return int|WP_Error] + * @return int|WP_Error */ function wc_update_attribute( $id, $args ) { + global $wpdb; + $attribute = wc_get_attribute( $id ); $args['id'] = $attribute ? $attribute->id : 0; @@ -506,6 +553,13 @@ function wc_update_attribute( $id, $args ) { $args['name'] = $attribute->name; } + $args['old_slug'] = $wpdb->get_var( $wpdb->prepare( " + SELECT attribute_name + FROM {$wpdb->prefix}woocommerce_attribute_taxonomies + WHERE attribute_id = %d + ", $args['id'] + ) ); + return wc_create_attribute( $args ); } @@ -519,18 +573,43 @@ function wc_update_attribute( $id, $args ) { function wc_delete_attribute( $id ) { global $wpdb; - $deleted = $wpdb->delete( - $wpdb->prefix . 'woocommerce_attribute_taxonomies', - array( 'attribute_id' => $id ), - array( '%d' ) - ); + $name = $wpdb->get_var( $wpdb->prepare( " + SELECT attribute_name + FROM {$wpdb->prefix}woocommerce_attribute_taxonomies + WHERE attribute_id = %d + ", $id ) ); - if ( in_array( $deleted, array( 0, false ), true ) ) { - return false; + $taxonomy = wc_attribute_taxonomy_name( $name ); + + /** + * Before delete an attribute. + * + * @param int $id Attribute ID. + * @param string $name Attribute name. + * @param string $taxonomy Attribute taxonomy name. + */ + do_action( 'woocommerce_before_attribute_delete', $id, $name, $taxonomy ); + + if ( $name && $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = $id" ) ) { + if ( taxonomy_exists( $taxonomy ) ) { + $terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' ); + foreach ( $terms as $term ) { + wp_delete_term( $term->term_id, $taxonomy ); + } + } + + /** + * After delete an attribute. + * + * @param int $id Attribute ID. + * @param string $name Attribute name. + * @param string $taxonomy Attribute taxonomy name. + */ + do_action( 'woocommerce_attribute_deleted', $id, $name, $taxonomy ); + delete_transient( 'wc_attribute_taxonomies' ); + + return true; } - // Clear transients. - delete_transient( 'wc_attribute_taxonomies' ); - - return true; + return false; } From 27966150543fc3d290244e834b187d4e9ebc79f4 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Mon, 7 Aug 2017 22:23:50 -0300 Subject: [PATCH 208/224] Flush rewrite rules after delete attributes --- includes/wc-attribute-functions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php index 9f3dd2ad1e2..984781414e5 100644 --- a/includes/wc-attribute-functions.php +++ b/includes/wc-attribute-functions.php @@ -606,6 +606,7 @@ function wc_delete_attribute( $id ) { * @param string $taxonomy Attribute taxonomy name. */ do_action( 'woocommerce_attribute_deleted', $id, $name, $taxonomy ); + wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); delete_transient( 'wc_attribute_taxonomies' ); return true; From a3a3d7c24241b12c8b589adbf0d28f1a736f4305 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Mon, 7 Aug 2017 22:33:57 -0300 Subject: [PATCH 209/224] Initial implementation of product attribute helper functions --- includes/admin/class-wc-admin-attributes.php | 68 ++++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/includes/admin/class-wc-admin-attributes.php b/includes/admin/class-wc-admin-attributes.php index d3c16cd0491..7323701b133 100644 --- a/includes/admin/class-wc-admin-attributes.php +++ b/includes/admin/class-wc-admin-attributes.php @@ -88,48 +88,38 @@ class WC_Admin_Attributes { return $attribute; } - /** - * See if an attribute name is valid. - * @param string $attribute_name - * @return bool|WP_error result - */ - private static function valid_attribute_name( $attribute_name ) { - if ( strlen( $attribute_name ) > 28 ) { - /* translators: %s: attribute name */ - return new WP_Error( 'error', sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), sanitize_title( $attribute_name ) ) ); - } elseif ( wc_check_if_attribute_name_is_reserved( $attribute_name ) ) { - /* translators: %s: attribute name */ - return new WP_Error( 'error', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), sanitize_title( $attribute_name ) ) ); - } - - return true; - } - /** * Add an attribute. * @return bool|WP_Error */ private static function process_add_attribute() { - global $wpdb; check_admin_referer( 'woocommerce-add-new_attribute' ); $attribute = self::get_posted_attribute(); - if ( empty( $attribute['attribute_name'] ) || empty( $attribute['attribute_label'] ) ) { - return new WP_Error( 'error', __( 'Please, provide an attribute name and slug.', 'woocommerce' ) ); - } elseif ( ( $valid_attribute_name = self::valid_attribute_name( $attribute['attribute_name'] ) ) && is_wp_error( $valid_attribute_name ) ) { - return $valid_attribute_name; - } elseif ( taxonomy_exists( wc_attribute_taxonomy_name( $attribute['attribute_name'] ) ) ) { - /* translators: %s: attribute name */ - return new WP_Error( 'error', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), sanitize_title( $attribute['attribute_name'] ) ) ); + $args = array( + 'name' => $attribute['attribute_label'], + 'slug' => $attribute['attribute_name'], + 'type' => $attribute['attribute_type'], + 'order_by' => $attribute['attribute_orderby'], + 'has_archives' => $attribute['attribute_public'], + ); + + $id = wc_create_attribute( $args ); + + if ( is_wp_error( $id ) ) { + return $id; } - $wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute ); - - do_action( 'woocommerce_attribute_added', $wpdb->insert_id, $attribute ); - - wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); - delete_transient( 'wc_attribute_taxonomies' ); + /** + * Attribute added. + * + * @deprecated 3.2.0 + * + * @param int $id Added attribute ID. + * @param array $attribute Attribute arguments. + */ + do_action( 'woocommerce_attribute_added', $id, $attribute ); return true; } @@ -145,10 +135,18 @@ class WC_Admin_Attributes { $attribute = self::get_posted_attribute(); - if ( empty( $attribute['attribute_name'] ) || empty( $attribute['attribute_label'] ) ) { - return new WP_Error( 'error', __( 'Please, provide an attribute name and slug.', 'woocommerce' ) ); - } elseif ( ( $valid_attribute_name = self::valid_attribute_name( $attribute['attribute_name'] ) ) && is_wp_error( $valid_attribute_name ) ) { - return $valid_attribute_name; + $args = array( + 'name' => $attribute['attribute_label'], + 'slug' => $attribute['attribute_name'], + 'type' => $attribute['attribute_type'], + 'order_by' => $attribute['attribute_orderby'], + 'has_archives' => $attribute['attribute_public'], + ); + + $id = wc_update_attribute( $attribute_id, $args ); + + if ( is_wp_error( $id ) ) { + return $id; } $taxonomy_exists = taxonomy_exists( wc_attribute_taxonomy_name( $attribute['attribute_name'] ) ); From 70e0ab857e6b973e1f5998d9e949ddfe3600dc79 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Mon, 7 Aug 2017 21:42:56 -0300 Subject: [PATCH 210/224] Implement attributes functions to admin --- includes/admin/class-wc-admin-attributes.php | 97 ++------------------ 1 file changed, 6 insertions(+), 91 deletions(-) diff --git a/includes/admin/class-wc-admin-attributes.php b/includes/admin/class-wc-admin-attributes.php index 7323701b133..3e46f1aaa57 100644 --- a/includes/admin/class-wc-admin-attributes.php +++ b/includes/admin/class-wc-admin-attributes.php @@ -90,14 +90,14 @@ class WC_Admin_Attributes { /** * Add an attribute. + * * @return bool|WP_Error */ private static function process_add_attribute() { check_admin_referer( 'woocommerce-add-new_attribute' ); $attribute = self::get_posted_attribute(); - - $args = array( + $args = array( 'name' => $attribute['attribute_label'], 'slug' => $attribute['attribute_name'], 'type' => $attribute['attribute_type'], @@ -111,31 +111,20 @@ class WC_Admin_Attributes { return $id; } - /** - * Attribute added. - * - * @deprecated 3.2.0 - * - * @param int $id Added attribute ID. - * @param array $attribute Attribute arguments. - */ - do_action( 'woocommerce_attribute_added', $id, $attribute ); - return true; } /** * Edit an attribute. + * * @return bool|WP_Error */ private static function process_edit_attribute() { - global $wpdb; $attribute_id = absint( $_GET['edit'] ); check_admin_referer( 'woocommerce-save-attribute_' . $attribute_id ); $attribute = self::get_posted_attribute(); - - $args = array( + $args = array( 'name' => $attribute['attribute_label'], 'slug' => $attribute['attribute_name'], 'type' => $attribute['attribute_type'], @@ -149,95 +138,21 @@ class WC_Admin_Attributes { return $id; } - $taxonomy_exists = taxonomy_exists( wc_attribute_taxonomy_name( $attribute['attribute_name'] ) ); - $old_attribute_name = $wpdb->get_var( "SELECT attribute_name FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = $attribute_id" ); - if ( $old_attribute_name != $attribute['attribute_name'] && wc_sanitize_taxonomy_name( $old_attribute_name ) != $attribute['attribute_name'] && $taxonomy_exists ) { - /* translators: %s: attribute name */ - return new WP_Error( 'error', sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), sanitize_title( $attribute['attribute_name'] ) ) ); - } - - $wpdb->update( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute, array( 'attribute_id' => $attribute_id ) ); - - do_action( 'woocommerce_attribute_updated', $attribute_id, $attribute, $old_attribute_name ); - - if ( $old_attribute_name != $attribute['attribute_name'] && ! empty( $old_attribute_name ) ) { - // Update taxonomies in the wp term taxonomy table - $wpdb->update( - $wpdb->term_taxonomy, - array( 'taxonomy' => wc_attribute_taxonomy_name( $attribute['attribute_name'] ) ), - array( 'taxonomy' => 'pa_' . $old_attribute_name ) - ); - - // Update taxonomy ordering term meta - if ( get_option( 'db_version' ) < 34370 ) { - $wpdb->update( - $wpdb->prefix . 'woocommerce_termmeta', - array( 'meta_key' => 'order_pa_' . sanitize_title( $attribute['attribute_name'] ) ), - array( 'meta_key' => 'order_pa_' . sanitize_title( $old_attribute_name ) ) - ); - } else { - $wpdb->update( - $wpdb->termmeta, - array( 'meta_key' => 'order_pa_' . sanitize_title( $attribute['attribute_name'] ) ), - array( 'meta_key' => 'order_pa_' . sanitize_title( $old_attribute_name ) ) - ); - } - - // Update product attributes which use this taxonomy - $old_attribute_name_length = strlen( $old_attribute_name ) + 3; - $attribute_name_length = strlen( $attribute['attribute_name'] ) + 3; - - $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE( meta_value, %s, %s ) WHERE meta_key = '_product_attributes'", - 's:' . $old_attribute_name_length . ':"pa_' . $old_attribute_name . '"', - 's:' . $attribute_name_length . ':"pa_' . $attribute['attribute_name'] . '"' - ) ); - - // Update variations which use this taxonomy - $wpdb->update( - $wpdb->postmeta, - array( 'meta_key' => 'attribute_pa_' . sanitize_title( $attribute['attribute_name'] ) ), - array( 'meta_key' => 'attribute_pa_' . sanitize_title( $old_attribute_name ) ) - ); - } - echo '

                ' . __( 'Attribute updated successfully', 'woocommerce' ) . '

                ' . __( 'Back to Attributes', 'woocommerce' ) . '

                '; - wp_schedule_single_event( time(), 'woocommerce_flush_rewrite_rules' ); - delete_transient( 'wc_attribute_taxonomies' ); - return true; } /** * Delete an attribute. + * * @return bool */ private static function process_delete_attribute() { - global $wpdb; - $attribute_id = absint( $_GET['delete'] ); - check_admin_referer( 'woocommerce-delete-attribute_' . $attribute_id ); - $attribute_name = $wpdb->get_var( "SELECT attribute_name FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = $attribute_id" ); - $taxonomy = wc_attribute_taxonomy_name( $attribute_name ); - - do_action( 'woocommerce_before_attribute_delete', $attribute_id, $attribute_name, $taxonomy ); - - if ( $attribute_name && $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_id = $attribute_id" ) ) { - if ( taxonomy_exists( $taxonomy ) ) { - $terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' ); - foreach ( $terms as $term ) { - wp_delete_term( $term->term_id, $taxonomy ); - } - } - - do_action( 'woocommerce_attribute_deleted', $attribute_id, $attribute_name, $taxonomy ); - delete_transient( 'wc_attribute_taxonomies' ); - return true; - } - - return false; + return wc_delete_attribute( $attribute_id ); } /** From 1a9b499be8c032d6c903e51f7a6bb4ef3eeb3a19 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Mon, 7 Aug 2017 22:14:10 -0300 Subject: [PATCH 211/224] Apply attribute helper functions to importers and REST API --- includes/admin/class-wc-admin-importers.php | 16 +-- ...-wc-rest-product-attributes-controller.php | 123 ++++-------------- .../import/abstract-wc-product-importer.php | 54 ++++---- 3 files changed, 55 insertions(+), 138 deletions(-) diff --git a/includes/admin/class-wc-admin-importers.php b/includes/admin/class-wc-admin-importers.php index 4a4afb4eb98..43d88fa4d66 100644 --- a/includes/admin/class-wc-admin-importers.php +++ b/includes/admin/class-wc-admin-importers.php @@ -158,15 +158,13 @@ class WC_Admin_Importers { // Create the taxonomy if ( ! in_array( $attribute_name, wc_get_attribute_taxonomies() ) ) { - $attribute = array( - 'attribute_label' => $attribute_name, - 'attribute_name' => $attribute_name, - 'attribute_type' => 'select', - 'attribute_orderby' => 'menu_order', - 'attribute_public' => 0, - ); - $wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute ); - delete_transient( 'wc_attribute_taxonomies' ); + wc_create_attribute( array( + 'name' => $attribute_name, + 'slug' => $attribute_name, + 'type' => 'select', + 'order_by' => 'menu_order', + 'has_archives' => false, + ) ); } // Register the taxonomy now so that the import works! diff --git a/includes/api/v1/class-wc-rest-product-attributes-controller.php b/includes/api/v1/class-wc-rest-product-attributes-controller.php index 32fe92f5189..e4290657e1d 100644 --- a/includes/api/v1/class-wc-rest-product-attributes-controller.php +++ b/includes/api/v1/class-wc-rest-product-attributes-controller.php @@ -240,38 +240,20 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { public function create_item( $request ) { global $wpdb; - $args = array( - 'attribute_label' => $request['name'], - 'attribute_name' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ), - 'attribute_type' => ! empty( $request['type'] ) ? $request['type'] : 'select', - 'attribute_orderby' => ! empty( $request['order_by'] ) ? $request['order_by'] : 'menu_order', - 'attribute_public' => true === $request['has_archives'], - ); - - // Set the attribute slug. - if ( empty( $args['attribute_name'] ) ) { - $args['attribute_name'] = wc_sanitize_taxonomy_name( stripslashes( $args['attribute_label'] ) ); - } else { - $args['attribute_name'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $args['attribute_name'] ) ) ); - } - - $valid_slug = $this->validate_attribute_slug( $args['attribute_name'], true ); - if ( is_wp_error( $valid_slug ) ) { - return $valid_slug; - } - - $insert = $wpdb->insert( - $wpdb->prefix . 'woocommerce_attribute_taxonomies', - $args, - array( '%s', '%s', '%s', '%s', '%d' ) - ); + $id = wc_create_attribute( array( + 'name' => $request['name'], + 'slug' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ), + 'type' => ! empty( $request['type'] ) ? $request['type'] : 'select', + 'order_by' => ! empty( $request['order_by'] ) ? $request['order_by'] : 'menu_order', + 'has_archives' => true === $request['has_archives'], + ) ); // Checks for errors. - if ( is_wp_error( $insert ) ) { - return new WP_Error( 'woocommerce_rest_cannot_create', $insert->get_error_message(), array( 'status' => 400 ) ); + if ( is_wp_error( $id ) ) { + return new WP_Error( 'woocommerce_rest_cannot_create', $id->get_error_message(), array( 'status' => 400 ) ); } - $attribute = $this->get_attribute( $wpdb->insert_id ); + $attribute = $this->get_attribute( $id ); if ( is_wp_error( $attribute ) ) { return $attribute; @@ -294,10 +276,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { $response->set_status( 201 ); $response->header( 'Location', rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/' . $attribute->attribute_id ) ); - // Clear transients. - $this->flush_rewrite_rules(); - delete_transient( 'wc_attribute_taxonomies' ); - return $response; } @@ -329,48 +307,17 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { global $wpdb; $id = (int) $request['id']; - $format = array( '%s', '%s', '%s', '%s', '%d' ); - $args = array( - 'attribute_label' => $request['name'], - 'attribute_name' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ), - 'attribute_type' => $request['type'], - 'attribute_orderby' => $request['order_by'], - 'attribute_public' => $request['has_archives'], - ); + $edited = wc_update_attribute( $id, array( + 'name' => $request['name'], + 'slug' => wc_sanitize_taxonomy_name( stripslashes( $request['slug'] ) ), + 'type' => $request['type'], + 'order_by' => $request['order_by'], + 'has_archives' => $request['has_archives'], + ) ); - $i = 0; - foreach ( $args as $key => $value ) { - if ( empty( $value ) && ! is_bool( $value ) ) { - unset( $args[ $key ] ); - unset( $format[ $i ] ); - } - - $i++; - } - - // Set the attribute slug. - if ( ! empty( $args['attribute_name'] ) ) { - $args['attribute_name'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $args['attribute_name'] ) ) ); - - $valid_slug = $this->validate_attribute_slug( $args['attribute_name'], false ); - if ( is_wp_error( $valid_slug ) ) { - return $valid_slug; - } - } - - if ( $args ) { - $update = $wpdb->update( - $wpdb->prefix . 'woocommerce_attribute_taxonomies', - $args, - array( 'attribute_id' => $id ), - $format, - array( '%d' ) - ); - - // Checks for errors. - if ( false === $update ) { - return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Could not edit the attribute.', 'woocommerce' ), array( 'status' => 400 ) ); - } + // Checks for errors. + if ( is_wp_error( $edited ) ) { + return new WP_Error( 'woocommerce_rest_cannot_edit', $edited->get_error_message(), array( 'status' => 400 ) ); } $attribute = $this->get_attribute( $id ); @@ -393,10 +340,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $attribute, $request ); - // Clear transients. - $this->flush_rewrite_rules(); - delete_transient( 'wc_attribute_taxonomies' ); - return rest_ensure_response( $response ); } @@ -407,8 +350,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { * @return WP_REST_Response|WP_Error */ public function delete_item( $request ) { - global $wpdb; - $force = isset( $request['force'] ) ? (bool) $request['force'] : false; // We don't support trashing for this type, error out. @@ -425,25 +366,12 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $attribute, $request ); - $deleted = $wpdb->delete( - $wpdb->prefix . 'woocommerce_attribute_taxonomies', - array( 'attribute_id' => $attribute->attribute_id ), - array( '%d' ) - ); + $deleted = wc_delete_attribute( $attribute->attribute_id ); if ( false === $deleted ) { return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) ); } - $taxonomy = wc_attribute_taxonomy_name( $attribute->attribute_name ); - - if ( taxonomy_exists( $taxonomy ) ) { - $terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' ); - foreach ( $terms as $term ) { - wp_delete_term( $term->term_id, $taxonomy ); - } - } - /** * Fires after a single attribute is deleted via the REST API. * @@ -453,13 +381,6 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { */ do_action( 'woocommerce_rest_delete_product_attribute', $attribute, $response, $request ); - // Fires woocommerce_attribute_deleted hook. - do_action( 'woocommerce_attribute_deleted', $attribute->attribute_id, $attribute->attribute_name, $taxonomy ); - - // Clear transients. - $this->flush_rewrite_rules(); - delete_transient( 'wc_attribute_taxonomies' ); - return $response; } @@ -636,6 +557,7 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { /** * Validate attribute slug. * + * @deprecated 3.2.0 * @param string $slug * @param bool $new_data * @return bool|WP_Error @@ -655,6 +577,7 @@ class WC_REST_Product_Attributes_V1_Controller extends WC_REST_Controller { /** * Schedule to flush rewrite rules. * + * @deprecated 3.2.0 * @since 3.0.0 */ protected function flush_rewrite_rules() { diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php index 251139d29fa..e7ce0cfb359 100644 --- a/includes/import/abstract-wc-product-importer.php +++ b/includes/import/abstract-wc-product-importer.php @@ -586,43 +586,39 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { } // If the attribute does not exist, create it. - $args = array( - 'attribute_label' => $raw_name, - 'attribute_name' => $attribute_name, - 'attribute_type' => 'select', - 'attribute_orderby' => 'menu_order', - 'attribute_public' => 0, - ); + $attribute_id = wc_create_attribute( array( + 'name' => $raw_name, + 'slug' => $attribute_name, + 'type' => 'select', + 'order_by' => 'menu_order', + 'has_archives' => false, + ) ); - // Validate attribute. - if ( strlen( $attribute_name ) >= 28 ) { - throw new Exception( sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $attribute_name ), 400 ); - } elseif ( wc_check_if_attribute_name_is_reserved( $attribute_name ) ) { - throw new Exception( sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $attribute_name ), 400 ); - } elseif ( taxonomy_exists( wc_attribute_taxonomy_name( $attribute_name ) ) ) { - throw new Exception( sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $attribute_name ), 400 ); + if ( is_wp_error( $attribute_id ) ) { + throw new Exception( $attribute_id->get_error_message(), 400 ); } - $result = $wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $args, array( '%s', '%s', '%s', '%s', '%d' ) ); - - // Pass errors. - if ( is_wp_error( $result ) ) { - throw new Exception( $result->get_error_message(), 400 ); - } - - $attribute_id = absint( $wpdb->insert_id ); - - // Delete transient. - delete_transient( 'wc_attribute_taxonomies' ); - // Register as taxonomy while importing. - register_taxonomy( wc_attribute_taxonomy_name( $attribute_name ), array( 'product' ), array( 'labels' => array( 'name' => $raw_name ) ) ); + $taxonomy_name = wc_attribute_taxonomy_name( $attribute_name ); + register_taxonomy( + $taxonomy_name, + apply_filters( 'woocommerce_taxonomy_objects_' . $taxonomy_name, array( 'product' ) ), + apply_filters( 'woocommerce_taxonomy_args_' . $taxonomy_name, array( + 'labels' => array( + 'name' => $raw_name, + ), + 'hierarchical' => true, + 'show_ui' => false, + 'query_var' => true, + 'rewrite' => false, + ) ) + ); // Set product attributes global. $wc_product_attributes = array(); - foreach ( wc_get_attribute_taxonomies() as $tax ) { - $wc_product_attributes[ wc_attribute_taxonomy_name( $attribute_name ) ] = $tax; + foreach ( wc_get_attribute_taxonomies() as $taxonomy ) { + $wc_product_attributes[ wc_attribute_taxonomy_name( $taxonomy['attribute_name'] ) ] = $taxonomy; } return $attribute_id; From f7c4cf82d80d7fd2a63c67d7010b0a5afb71d84c Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 09:24:03 +0100 Subject: [PATCH 212/224] Fix subtotal unit tests --- tests/unit-tests/totals/totals.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index fde95f81510..ec50f774939 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -147,8 +147,8 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { $this->assertEquals( 40.00, $cart->fee_total ); $this->assertEquals( 27.00, $cart->cart_contents_total ); $this->assertEquals( 90.40, $cart->total ); - $this->assertEquals( 32.40, $cart->subtotal ); - $this->assertEquals( 27.00, $cart->subtotal_ex_tax ); + $this->assertEquals( 36.00, $cart->subtotal ); + $this->assertEquals( 30.00, $cart->subtotal_ex_tax ); $this->assertEquals( 11.40, $cart->tax_total ); $this->assertEquals( 3.00, $cart->discount_cart ); $this->assertEquals( 0.60, $cart->discount_cart_tax ); From 81806617d7a1dc7e253fc1fdd30f93c8d30535d1 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 09:24:26 +0100 Subject: [PATCH 213/224] Fix total and rounding logic for tests --- includes/class-wc-cart-totals.php | 50 ++++++++++++++++--------------- includes/class-wc-discounts.php | 32 +++++++++++++------- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/includes/class-wc-cart-totals.php b/includes/class-wc-cart-totals.php index 37bea691f1f..2362e1555b5 100644 --- a/includes/class-wc-cart-totals.php +++ b/includes/class-wc-cart-totals.php @@ -235,7 +235,7 @@ final class WC_Cart_Totals { $fee->total_tax = array_sum( $fee->taxes ); if ( ! $this->round_at_subtotal() ) { - $fee->total_tax = wc_round_tax_total( $fee->total_tax, 0 ); + $fee->total_tax = wc_round_tax_total( $fee->total_tax, wc_get_rounding_precision() ); } } @@ -259,7 +259,7 @@ final class WC_Cart_Totals { $shipping_line->total_tax = wc_add_number_precision_deep( array_sum( $shipping_object->taxes ) ); if ( ! $this->round_at_subtotal() ) { - $shipping_line->total_tax = wc_round_tax_total( $shipping_line->total_tax, 0 ); + $shipping_line->total_tax = wc_round_tax_total( $shipping_line->total_tax, wc_get_rounding_precision() ); } $this->shipping[ $key ] = $shipping_line; @@ -282,12 +282,14 @@ final class WC_Cart_Totals { * If the customer is outside of the base location, this removes the base * taxes. This is off by default unless the filter is used. * + * Uses edit context so unfiltered tax class is returned. + * * @since 3.2.0 * @param object $item Item to adjust the prices of. * @return object */ protected function adjust_non_base_location_price( $item ) { - $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->tax_class ); + $base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->get_tax_class( 'edit' ) ); if ( $item->tax_rates !== $base_tax_rates ) { // Work out a new base price without the shop's base tax. @@ -308,8 +310,9 @@ final class WC_Cart_Totals { * @return int */ protected function get_discounted_price_in_cents( $item_key ) { - $item = $this->items[ $item_key ]; + $item = $this->items[ $item_key ]; $price = $item->subtotal - $this->discount_totals[ $item_key ]; + if ( $item->price_includes_tax ) { $price += $item->subtotal_tax; } @@ -429,7 +432,7 @@ final class WC_Cart_Totals { $item->total_tax = array_sum( $item->taxes ); if ( ! $this->round_at_subtotal() ) { - $item->total_tax = wc_round_tax_total( $item->total_tax, 0 ); + $item->total_tax = wc_round_tax_total( $item->total_tax, wc_get_rounding_precision() ); } if ( $item->price_includes_tax ) { @@ -445,9 +448,6 @@ final class WC_Cart_Totals { $this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) ); $this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) ); - - $this->object->subtotal = $this->get_total( 'items_total' ) + $this->get_total( 'items_total_tax' ); - $this->object->subtotal_ex_tax = $this->get_total( 'items_total' ); } /** @@ -467,14 +467,15 @@ final class WC_Cart_Totals { protected function calculate_item_subtotals() { foreach ( $this->items as $item_key => $item ) { if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { - $item = $this->adjust_non_base_location_price( $item ); + $item = $this->adjust_non_base_location_price( $item ); } + if ( $this->calculate_tax && $item->product->is_taxable() ) { $subtotal_taxes = WC_Tax::calc_tax( $item->subtotal, $item->tax_rates, $item->price_includes_tax ); $item->subtotal_tax = array_sum( $subtotal_taxes ); if ( ! $this->round_at_subtotal() ) { - $item->subtotal_tax = wc_round_tax_total( $item->subtotal_tax, 0 ); + $item->subtotal_tax = wc_round_tax_total( $item->subtotal_tax, wc_get_rounding_precision() ); } if ( $item->price_includes_tax ) { @@ -488,8 +489,8 @@ final class WC_Cart_Totals { $this->set_total( 'items_subtotal', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal' ) ) ) ); $this->set_total( 'items_subtotal_tax', array_sum( array_values( wp_list_pluck( $this->items, 'subtotal_tax' ) ) ) ); - $this->object->subtotal = $this->get_total( 'items_total' ) + $this->get_total( 'items_total_tax' ); - $this->object->subtotal_ex_tax = $this->get_total( 'items_total' ); + $this->object->subtotal = $this->get_total( 'items_subtotal' ) + $this->get_total( 'items_subtotal_tax' ); + $this->object->subtotal_ex_tax = $this->get_total( 'items_subtotal' ); } /** @@ -507,15 +508,11 @@ final class WC_Cart_Totals { $discounts->apply_discount( $coupon ); } - $this->discount_totals = $discounts->get_discounts_by_item( true ); - $this->totals['discounts_total'] = ! empty( $this->discount_totals ) ? array_sum( $this->discount_totals ) : 0; - $this->object->coupon_discount_amounts = $discounts->get_discounts_by_coupon(); + $coupon_discount_amounts = $discounts->get_discounts_by_coupon( true ); + $coupon_discount_tax_amounts = array(); // See how much tax was 'discounted' per item and per coupon. if ( $this->calculate_tax ) { - $coupon_discount_tax_amounts = array(); - $item_taxes = 0; - foreach ( $discounts->get_discounts( true ) as $coupon_code => $coupon_discounts ) { $coupon_discount_tax_amounts[ $coupon_code ] = 0; @@ -523,16 +520,21 @@ final class WC_Cart_Totals { $item = $this->items[ $item_key ]; if ( $item->product->is_taxable() ) { - $item_tax = array_sum( WC_Tax::calc_tax( $item_discount, $item->tax_rates, false ) ); - $item_taxes += $item_tax; + $item_tax = array_sum( WC_Tax::calc_tax( $item_discount, $item->tax_rates, $item->price_includes_tax ) ); $coupon_discount_tax_amounts[ $coupon_code ] += $item_tax; } } - } - $this->totals['discounts_tax_total'] = $item_taxes; - $this->object->coupon_discount_tax_amounts = $coupon_discount_tax_amounts; + $coupon_discount_amounts[ $coupon_code ] -= $coupon_discount_tax_amounts[ $coupon_code ]; + } } + + $this->discount_totals = $discounts->get_discounts_by_item( true ); + $this->object->coupon_discount_amounts = wc_remove_number_precision_deep( $coupon_discount_amounts ); + $this->object->coupon_discount_tax_amounts = wc_remove_number_precision_deep( $coupon_discount_tax_amounts ); + + $this->set_total( 'discounts_total', ! empty( $this->discount_totals ) ? array_sum( $this->discount_totals ) : 0 ); + $this->set_total( 'discounts_tax_total', array_sum( $coupon_discount_tax_amounts ) ); } /** @@ -599,7 +601,7 @@ final class WC_Cart_Totals { $this->object->cart_contents_total = $this->get_total( 'items_total' ); $this->object->tax_total = $this->get_total( 'tax_total' ); $this->object->total = $this->get_total( 'total' ); - $this->object->discount_cart = $this->get_total( 'discounts_total' ); + $this->object->discount_cart = $this->get_total( 'discounts_total' ) - $this->get_total( 'discounts_tax_total' ); $this->object->discount_cart_tax = $this->get_total( 'discounts_tax_total' ); // Allow plugins to hook and alter totals before final total is calculated. diff --git a/includes/class-wc-discounts.php b/includes/class-wc-discounts.php index 14d7517c9d1..5b77db9f751 100644 --- a/includes/class-wc-discounts.php +++ b/includes/class-wc-discounts.php @@ -174,10 +174,10 @@ class WC_Discounts { * * @since 3.2.0 * @param object $item Get data for this item. - * @return float + * @return int */ public function get_discounted_price_in_cents( $item ) { - return $item->price - $this->get_discount( $item->key, true ); + return absint( $item->price - $this->get_discount( $item->key, true ) ); } /** @@ -388,6 +388,7 @@ class WC_Discounts { */ protected function apply_coupon_percent( $coupon, $items_to_apply ) { $total_discount = 0; + $cart_total = 0; foreach ( $items_to_apply as $item ) { // Find out how much price is available to discount for the item. @@ -396,14 +397,25 @@ class WC_Discounts { // Get the price we actually want to discount, based on settings. $price_to_discount = ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ) ? $item->price: $discounted_price; + // Total up. + $cart_total += $price_to_discount; + // Run coupon calculations. - $discount = $coupon->get_amount() * ( $price_to_discount / 100 ); + $discount = floor( $price_to_discount * ( $coupon->get_amount() / 100 ) ); $discount = min( $discounted_price, apply_filters( 'woocommerce_coupon_get_discount_amount', $discount, $price_to_discount, $item->object, false, $coupon ) ); $total_discount += $discount; // Store code and discount amount per item. $this->discounts[ $coupon->get_code() ][ $item->key ] += $discount; } + + // Work out how much discount would have been given to the cart has a whole and compare to what was discounted on all line items. + $cart_total_discount = wc_cart_round_discount( $cart_total * ( $coupon->get_amount() / 100 ), 0 ); + + if ( $total_discount < $cart_total_discount ) { + $total_discount += $this->apply_coupon_remainder( $coupon, $items_to_apply, $cart_total_discount - $total_discount ); + } + return $total_discount; } @@ -449,26 +461,26 @@ class WC_Discounts { */ protected function apply_coupon_fixed_cart( $coupon, $items_to_apply, $amount = null ) { $total_discount = 0; - $amount = $amount ? $amount: wc_add_number_precision( $coupon->get_amount() ); + $amount = $amount ? $amount : wc_add_number_precision( $coupon->get_amount() ); $items_to_apply = array_filter( $items_to_apply, array( $this, 'filter_products_with_price' ) ); if ( ! $item_count = array_sum( wp_list_pluck( $items_to_apply, 'quantity' ) ) ) { return $total_discount; } - $per_item_discount = floor( $amount / $item_count ); // round it down to the nearest cent. + $per_item_discount = absint( $amount / $item_count ); // round it down to the nearest cent. if ( $per_item_discount > 0 ) { - $total_discounted = $this->apply_coupon_fixed_product( $coupon, $items_to_apply, $per_item_discount ); + $total_discount = $this->apply_coupon_fixed_product( $coupon, $items_to_apply, $per_item_discount ); /** * If there is still discount remaining, repeat the process. */ - if ( $total_discounted > 0 && $total_discounted < $amount ) { - $total_discounted = $total_discounted + $this->apply_coupon_fixed_cart( $coupon, $items_to_apply, $amount - $total_discounted ); + if ( $total_discount > 0 && $total_discount < $amount ) { + $total_discount += $this->apply_coupon_fixed_cart( $coupon, $items_to_apply, $amount - $total_discount ); } } elseif ( $amount > 0 ) { - $total_discounted = $this->apply_coupon_fixed_cart_remainder( $coupon, $items_to_apply, $amount ); + $total_discount += $this->apply_coupon_remainder( $coupon, $items_to_apply, $amount ); } return $total_discount; } @@ -483,7 +495,7 @@ class WC_Discounts { * @param int $amount Fixed discount amount to apply. * @return int Total discounted. */ - protected function apply_coupon_fixed_cart_remainder( $coupon, $items_to_apply, $amount ) { + protected function apply_coupon_remainder( $coupon, $items_to_apply, $amount ) { $total_discount = 0; foreach ( $items_to_apply as $item ) { From ce93599b5c15d20f53bce25e61e3acee9b48290b Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 09:30:11 +0100 Subject: [PATCH 214/224] Test correction --- tests/unit-tests/totals/totals.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit-tests/totals/totals.php b/tests/unit-tests/totals/totals.php index ec50f774939..ab43f98d7f6 100644 --- a/tests/unit-tests/totals/totals.php +++ b/tests/unit-tests/totals/totals.php @@ -150,7 +150,7 @@ class WC_Tests_Totals extends WC_Unit_Test_Case { $this->assertEquals( 36.00, $cart->subtotal ); $this->assertEquals( 30.00, $cart->subtotal_ex_tax ); $this->assertEquals( 11.40, $cart->tax_total ); - $this->assertEquals( 3.00, $cart->discount_cart ); + $this->assertEquals( 2.40, $cart->discount_cart ); $this->assertEquals( 0.60, $cart->discount_cart_tax ); $this->assertEquals( 40.00, $cart->fee_total ); $this->assertEquals( 10, $cart->shipping_total ); From 6dc67ee133129a5b31e205a21c6c322f5bbf3803 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 10:51:35 +0100 Subject: [PATCH 215/224] phpcs --- includes/class-wc-cart.php | 645 ++++++++--------------- includes/legacy/class-wc-legacy-cart.php | 124 ++++- 2 files changed, 337 insertions(+), 432 deletions(-) diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 409539dc673..aa21212e003 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -1,86 +1,158 @@ 0, 'total' => 0, @@ -111,8 +183,8 @@ class WC_Cart extends WC_Legacy_Cart { */ public function __construct() { add_action( 'wp_loaded', array( $this, 'init' ) ); // Get cart after WP and plugins are loaded. - add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 ); // Set cookies - add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 ); // Set cookies before shutdown and ob flushing + add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 ); // Set cookies. + add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 ); // Set cookies before shutdown and ob flushing. add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 ); add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 ); } @@ -120,7 +192,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Auto-load in-accessible properties on demand. * - * @param mixed $key + * @param mixed $key Key to get. * @return mixed */ public function __get( $key ) { @@ -150,10 +222,12 @@ class WC_Cart extends WC_Legacy_Cart { case 'tax' : wc_deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' ); $this->tax = new WC_Tax(); - return $this->tax; + return $this->tax; case 'discount_total': wc_deprecated_argument( 'WC_Cart->discount_total', '2.3', 'After tax coupons are no longer supported. For more information see: https://woocommerce.wordpress.com/2014/12/upcoming-coupon-changes-in-woocommerce-2-3/' ); - return 0; + return 0; + case 'coupons' : + return $this->get_coupons(); } } @@ -185,12 +259,12 @@ class WC_Cart extends WC_Legacy_Cart { * Set cart hash cookie and items in cart. * * @access private - * @param bool $set (default: true) + * @param bool $set Should cookies be set (true) or unset. */ private function set_cart_cookies( $set = true ) { if ( $set ) { wc_setcookie( 'woocommerce_items_in_cart', 1 ); - wc_setcookie( 'woocommerce_cart_hash', md5( json_encode( $this->get_cart_for_session() ) ) ); + wc_setcookie( 'woocommerce_cart_hash', md5( wp_json_encode( $this->get_cart_for_session() ) ) ); } elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) { wc_setcookie( 'woocommerce_items_in_cart', 0, time() - HOUR_IN_SECONDS ); wc_setcookie( 'woocommerce_cart_hash', '', time() - HOUR_IN_SECONDS ); @@ -198,15 +272,10 @@ class WC_Cart extends WC_Legacy_Cart { do_action( 'woocommerce_set_cart_cookies', $set ); } - /* - /* Cart Session Handling - */ - /** * Get the cart data from the PHP session and store it in class variables. */ public function get_cart_from_session() { - // Load cart session data from session foreach ( $this->cart_session_data as $key => $default ) { $this->$key = WC()->session->get( $key, $default ); } @@ -228,7 +297,7 @@ class WC_Cart extends WC_Legacy_Cart { } if ( is_array( $cart ) ) { - // Prime meta cache to reduce future queries + // Prime meta cache to reduce future queries. update_meta_cache( 'post', wp_list_pluck( $cart, 'product_id' ) ); update_object_term_cache( wp_list_pluck( $cart, 'product_id' ), 'product' ); @@ -238,32 +307,28 @@ class WC_Cart extends WC_Legacy_Cart { if ( ! empty( $product ) && $product->exists() && $values['quantity'] > 0 ) { if ( ! $product->is_purchasable() ) { - - // Flag to indicate the stored cart should be update - $update_cart_session = true; + $update_cart_session = true; // Flag to indicate the stored cart should be updated. /* translators: %s: product name */ wc_add_notice( sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'woocommerce' ), $product->get_name() ), 'error' ); do_action( 'woocommerce_remove_cart_item_from_session', $key, $values ); } else { - // Put session data into array. Run through filter so other plugins can load their own session data + // Put session data into array. Run through filter so other plugins can load their own session data. $session_data = array_merge( $values, array( 'data' => $product ) ); $this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key ); - } } } } - // Trigger action do_action( 'woocommerce_cart_loaded_from_session', $this ); if ( $update_cart_session ) { WC()->session->cart = $this->get_cart_for_session(); } - // Queue re-calc if subtotal is not set + // Queue re-calc if subtotal is not set. if ( ( ! $this->subtotal && ! $this->is_empty() ) || $update_cart_session ) { $this->calculate_totals(); } @@ -273,7 +338,6 @@ class WC_Cart extends WC_Legacy_Cart { * Sets the php session data for the cart and coupons. */ public function set_session() { - // Set cart and coupon session data $cart_session = $this->get_cart_for_session(); WC()->session->set( 'cart', $cart_session ); @@ -296,7 +360,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Empties the cart and optionally the persistent cart too. * - * @param bool $clear_persistent_cart (default: true) + * @param bool $clear_persistent_cart Should the persistant cart be cleared too. Defaults to true. */ public function empty_cart( $clear_persistent_cart = true ) { $this->cart_contents = array(); @@ -312,10 +376,6 @@ class WC_Cart extends WC_Legacy_Cart { do_action( 'woocommerce_cart_emptied' ); } - /* - * Persistent cart handling - */ - /** * Save the persistent cart when the cart is updated. */ @@ -332,23 +392,9 @@ class WC_Cart extends WC_Legacy_Cart { delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart_' . get_current_blog_id() ); } - /* - * Cart Data Functions. - */ - - /** - * Coupons enabled function. Filterable. - * - * @deprecated 2.5.0 in favor to wc_coupons_enabled() - * - * @return bool - */ - public function coupons_enabled() { - return wc_coupons_enabled(); - } - /** * Get number of items in the cart. + * * @return int */ public function get_cart_contents_count() { @@ -357,6 +403,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get weight of items in the cart. + * * @since 2.5.0 * @return int */ @@ -376,18 +423,14 @@ class WC_Cart extends WC_Legacy_Cart { * @return bool */ public function is_empty() { - return 0 === sizeof( $this->get_cart() ); + return 0 === count( $this->get_cart() ); } /** * Check all cart items for errors. */ public function check_cart_items() { - - // Result $return = true; - - // Check cart item validity $result = $this->check_cart_item_validity(); if ( is_wp_error( $result ) ) { @@ -395,7 +438,6 @@ class WC_Cart extends WC_Legacy_Cart { $return = false; } - // Check item stock $result = $this->check_cart_item_stock(); if ( is_wp_error( $result ) ) { @@ -415,13 +457,10 @@ class WC_Cart extends WC_Legacy_Cart { $coupon = new WC_Coupon( $code ); if ( ! $coupon->is_valid() ) { - // Error message $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED ); - - // Remove the coupon $this->remove_coupon( $code ); - // Flag totals for refresh + // Flag totals for refresh. WC()->session->set( 'refresh_totals', true ); } } @@ -474,7 +513,6 @@ class WC_Cart extends WC_Legacy_Cart { $error = new WP_Error(); $product_qty_in_cart = $this->get_cart_item_quantities(); - // First stock check loop foreach ( $this->get_cart() as $cart_item_key => $values ) { $product = $values['data']; @@ -537,8 +575,8 @@ class WC_Cart extends WC_Legacy_Cart { /** * Gets and formats a list of cart item data + variations for display on the frontend. * - * @param array $cart_item - * @param bool $flat (default: false) + * @param array $cart_item Cart item object. + * @param bool $flat Should the data be returned flat or in a list. * @return string */ public function get_item_data( $cart_item, $flat = false ) { @@ -550,16 +588,15 @@ class WC_Cart extends WC_Legacy_Cart { foreach ( $cart_item['variation'] as $name => $value ) { $taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $name ) ) ); - // If this is a term slug, get the term's nice name if ( taxonomy_exists( $taxonomy ) ) { + // If this is a term slug, get the term's nice name. $term = get_term_by( 'slug', $value, $taxonomy ); if ( ! is_wp_error( $term ) && $term && $term->name ) { $value = $term->name; } $label = wc_attribute_label( $taxonomy ); - - // If this is a custom option slug, get the options name. } else { + // If this is a custom option slug, get the options name. $value = apply_filters( 'woocommerce_variation_option_name', $value ); $label = wc_attribute_label( str_replace( 'attribute_', '', $name ), $cart_item['data'] ); } @@ -576,10 +613,10 @@ class WC_Cart extends WC_Legacy_Cart { } } - // Filter item data to allow 3rd parties to add more to the array + // Filter item data to allow 3rd parties to add more to the array. $item_data = apply_filters( 'woocommerce_get_item_data', $item_data, $cart_item ); - // Format item data ready to display + // Format item data ready to display. foreach ( $item_data as $key => $data ) { // Set hidden to true to not display meta on cart. if ( ! empty( $data['hidden'] ) ) { @@ -590,8 +627,8 @@ class WC_Cart extends WC_Legacy_Cart { $item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value']; } - // Output flat or in list format - if ( sizeof( $item_data ) > 0 ) { + // Output flat or in list format. + if ( count( $item_data ) > 0 ) { ob_start(); if ( $flat ) { @@ -628,32 +665,10 @@ class WC_Cart extends WC_Legacy_Cart { return apply_filters( 'woocommerce_cart_crosssell_ids', wp_parse_id_list( $cross_sells ), $this ); } - /** - * Gets the url to the cart page. - * - * @deprecated 2.5.0 in favor to wc_get_cart_url() - * - * @return string url to page - */ - public function get_cart_url() { - return wc_get_cart_url(); - } - - /** - * Gets the url to the checkout page. - * - * @deprecated 2.5.0 in favor to wc_get_checkout_url() - * - * @return string url to page - */ - public function get_checkout_url() { - return wc_get_checkout_url(); - } - /** * Gets the url to remove an item from the cart. * - * @param string $cart_item_key contains the id of the cart item + * @param string $cart_item_key contains the id of the cart item. * @return string url to page */ public function get_remove_url( $cart_item_key ) { @@ -664,7 +679,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Gets the url to re-add an item into the cart. * - * @param string $cart_item_key + * @param string $cart_item_key Cart item key to undo. * @return string url to page */ public function get_undo_url( $cart_item_key ) { @@ -703,7 +718,7 @@ class WC_Cart extends WC_Legacy_Cart { if ( $this->get_cart() ) { foreach ( $this->get_cart() as $key => $values ) { $cart_session[ $key ] = $values; - unset( $cart_session[ $key ]['data'] ); // Unset product object + unset( $cart_session[ $key ]['data'] ); // Unset product object. } } @@ -732,7 +747,6 @@ class WC_Cart extends WC_Legacy_Cart { public function get_taxes() { $taxes = array(); - // Merge foreach ( array_keys( $this->taxes + $this->shipping_taxes ) as $key ) { $taxes[ $key ] = ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); } @@ -775,6 +789,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get all tax classes for items in the cart. + * * @return array */ public function get_cart_item_tax_classes() { @@ -809,16 +824,12 @@ class WC_Cart extends WC_Legacy_Cart { } } - /* - * Add to cart handling. - */ - /** * Check if product is in the cart and return cart item key. * * Cart item key will be unique based on the item and its properties, such as variations. * - * @param mixed id of product to find in the cart + * @param mixed $cart_id id of product to find in the cart. * @return string cart item key */ public function find_product_in_cart( $cart_id = false ) { @@ -833,16 +844,16 @@ class WC_Cart extends WC_Legacy_Cart { /** * Generate a unique ID for the cart item being added. * - * @param int $product_id - id of the product the key is being generated for - * @param int $variation_id of the product the key is being generated for - * @param array $variation data for the cart item - * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart + * @param int $product_id - id of the product the key is being generated for. + * @param int $variation_id of the product the key is being generated for. + * @param array $variation data for the cart item. + * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart. * @return string cart item key */ public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) { $id_parts = array( $product_id ); - if ( $variation_id && 0 != $variation_id ) { + if ( $variation_id && 0 !== $variation_id ) { $id_parts[] = $variation_id; } @@ -872,46 +883,43 @@ class WC_Cart extends WC_Legacy_Cart { /** * Add a product to the cart. * - * @param int $product_id contains the id of the product to add to the cart - * @param int $quantity contains the quantity of the item to add - * @param int $variation_id - * @param array $variation attribute values - * @param array $cart_item_data extra cart item data we want to pass into the item + * @throws Exception To prevent adding to cart. + * @param int $product_id contains the id of the product to add to the cart. + * @param int $quantity contains the quantity of the item to add. + * @param int $variation_id ID of the variation being added to the cart. + * @param array $variation attribute values. + * @param array $cart_item_data extra cart item data we want to pass into the item. * @return string|bool $cart_item_key */ public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) { - // Wrap in try catch so plugins can throw an exception to prevent adding to cart + // Wrap in try catch so plugins can throw an exception to prevent adding to cart. try { $product_id = absint( $product_id ); $variation_id = absint( $variation_id ); - // Ensure we don't add a variation to the cart directly by variation ID + // Ensure we don't add a variation to the cart directly by variation ID. if ( 'product_variation' === get_post_type( $product_id ) ) { $variation_id = $product_id; $product_id = wp_get_post_parent_id( $variation_id ); } - // Get the product $product_data = wc_get_product( $variation_id ? $variation_id : $product_id ); - - // Filter quantity being added to the cart before stock checks $quantity = apply_filters( 'woocommerce_add_to_cart_quantity', $quantity, $product_id ); - // Sanity check if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->get_status() ) { return false; } - // Load cart item data - may be added by other plugins + // Load cart item data - may be added by other plugins. $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id ); - // Generate a ID based on product ID, variation ID, variation data, and other cart item data + // Generate a ID based on product ID, variation ID, variation data, and other cart item data. $cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data ); - // Find the cart item key in the existing cart + // Find the cart item key in the existing cart. $cart_item_key = $this->find_product_in_cart( $cart_id ); - // Force quantity to 1 if sold individually and check for existing item in cart + // Force quantity to 1 if sold individually and check for existing item in cart. if ( $product_data->is_sold_individually() ) { $quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data ); $found_in_cart = apply_filters( 'woocommerce_add_to_cart_sold_individually_found_in_cart', $cart_item_key && $this->cart_contents[ $cart_item_key ]['quantity'] > 0, $product_id, $variation_id, $cart_item_data, $cart_id ); @@ -922,12 +930,11 @@ class WC_Cart extends WC_Legacy_Cart { } } - // Check product is_purchasable if ( ! $product_data->is_purchasable() ) { throw new Exception( __( 'Sorry, this product cannot be purchased.', 'woocommerce' ) ); } - // Stock check - only check if we're managing stock and backorders are not allowed + // Stock check - only check if we're managing stock and backorders are not allowed. if ( ! $product_data->is_in_stock() ) { throw new Exception( sprintf( __( 'You cannot add "%s" to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() ) ); } @@ -937,7 +944,7 @@ class WC_Cart extends WC_Legacy_Cart { throw new Exception( sprintf( __( 'You cannot add that amount of "%1$s" to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ) ) ); } - // Stock check - this time accounting for whats already in-cart + // Stock check - this time accounting for whats already in-cart. if ( $product_data->managing_stock() ) { $products_qty_in_cart = $this->get_cart_item_quantities(); @@ -951,14 +958,14 @@ class WC_Cart extends WC_Legacy_Cart { } } - // If cart_item_key is set, the item is already in the cart + // If cart_item_key is set, the item is already in the cart. if ( $cart_item_key ) { $new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity']; $this->set_quantity( $cart_item_key, $new_quantity, false ); } else { $cart_item_key = $cart_id; - // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item + // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item. $this->cart_contents[ $cart_item_key ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array( 'key' => $cart_item_key, 'product_id' => $product_id, @@ -989,7 +996,7 @@ class WC_Cart extends WC_Legacy_Cart { * Remove a cart item. * * @since 2.3.0 - * @param string $cart_item_key + * @param string $cart_item_key Cart item key to remove from the cart. * @return bool */ public function remove_cart_item( $cart_item_key ) { @@ -1014,7 +1021,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Restore a cart item. * - * @param string $cart_item_key + * @param string $cart_item_key Cart item key to restore to the cart. * @return bool */ public function restore_cart_item( $cart_item_key ) { @@ -1039,14 +1046,13 @@ class WC_Cart extends WC_Legacy_Cart { /** * Set the quantity for an item in the cart. * - * @param string $cart_item_key contains the id of the cart item - * @param int $quantity contains the quantity of the item - * @param bool $refresh_totals whether or not to calculate totals after setting the new qty - * + * @param string $cart_item_key contains the id of the cart item. + * @param int $quantity contains the quantity of the item. + * @param bool $refresh_totals whether or not to calculate totals after setting the new qty. * @return bool */ public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) { - if ( 0 == $quantity || $quantity < 0 ) { + if ( 0 === $quantity || $quantity < 0 ) { do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key ); unset( $this->cart_contents[ $cart_item_key ] ); } else { @@ -1062,14 +1068,10 @@ class WC_Cart extends WC_Legacy_Cart { return true; } - /* - * Cart Calculation Functions. - */ - /** * Reset cart totals to the defaults. Useful before running calculations. * - * @param bool $unset_session If true, the session data will be forced unset. + * @param bool $unset_session If true, the session data will be forced unset. * @access private */ private function reset( $unset_session = false ) { @@ -1082,21 +1084,6 @@ class WC_Cart extends WC_Legacy_Cart { do_action( 'woocommerce_cart_reset', $this, $unset_session ); } - /** - * Sort by subtotal. - * @param array $a - * @param array $b - * @return int - */ - private function sort_by_subtotal( $a, $b ) { - $first_item_subtotal = isset( $a['line_subtotal'] ) ? $a['line_subtotal'] : 0; - $second_item_subtotal = isset( $b['line_subtotal'] ) ? $b['line_subtotal'] : 0; - if ( $first_item_subtotal === $second_item_subtotal ) { - return 0; - } - return ( $first_item_subtotal < $second_item_subtotal ) ? 1 : -1; - } - /** * Get cart's owner. * @@ -1109,10 +1096,11 @@ class WC_Cart extends WC_Legacy_Cart { /** * Calculate totals for the items in the cart. + * + * @uses WC_Cart_Totals */ public function calculate_totals() { $this->reset(); - $this->coupons = $this->get_coupons(); do_action( 'woocommerce_before_calculate_totals', $this ); @@ -1121,11 +1109,6 @@ class WC_Cart extends WC_Legacy_Cart { return; } - $cart = $this->get_cart(); - - // Order cart items by price so coupon logic is 'fair' for customers and not based on order added to cart. - uasort( $cart, apply_filters( 'woocommerce_sort_by_subtotal_callback', array( $this, 'sort_by_subtotal' ) ) ); - new WC_Cart_Totals( $this ); do_action( 'woocommerce_after_calculate_totals', $this ); @@ -1183,7 +1166,7 @@ class WC_Cart extends WC_Legacy_Cart { * Given a set of packages with rates, get the chosen ones only. * * @since 3.2.0 - * @param array $calculated_shipping_packages + * @param array $calculated_shipping_packages Array of packages. * @return array */ protected function get_chosen_shipping_methods( $calculated_shipping_packages = array() ) { @@ -1202,7 +1185,7 @@ class WC_Cart extends WC_Legacy_Cart { * Filter items needing shipping callback. * * @since 3.0.0 - * @param array $item + * @param array $item Item to check for shipping. * @return bool */ protected function filter_items_needing_shipping( $item ) { @@ -1286,14 +1269,7 @@ class WC_Cart extends WC_Legacy_Cart { * @return bool */ public function needs_shipping_address() { - - $needs_shipping_address = false; - - if ( $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() ) { - $needs_shipping_address = true; - } - - return apply_filters( 'woocommerce_cart_needs_shipping_address', $needs_shipping_address ); + return apply_filters( 'woocommerce_cart_needs_shipping_address', $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() ); } /** @@ -1317,17 +1293,6 @@ class WC_Cart extends WC_Legacy_Cart { return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true ); } - /** - * Sees if we need a shipping address. - * - * @deprecated 2.5.0 in favor to wc_ship_to_billing_address_only() - * - * @return bool - */ - public function ship_to_billing_address_only() { - return wc_ship_to_billing_address_only(); - } - /** * Gets the shipping total (after calculation). * @@ -1337,9 +1302,7 @@ class WC_Cart extends WC_Legacy_Cart { if ( isset( $this->shipping_total ) ) { if ( $this->shipping_total > 0 ) { - // Display varies depending on settings if ( 'excl' === $this->tax_display_cart ) { - $return = wc_price( $this->shipping_total ); if ( $this->shipping_tax_total > 0 && $this->prices_include_tax ) { @@ -1347,9 +1310,7 @@ class WC_Cart extends WC_Legacy_Cart { } return $return; - } else { - $return = wc_price( $this->shipping_total + $this->shipping_tax_total ); if ( $this->shipping_tax_total > 0 && ! $this->prices_include_tax ) { @@ -1357,20 +1318,14 @@ class WC_Cart extends WC_Legacy_Cart { } return $return; - } } else { return __( 'Free!', 'woocommerce' ); } } - return ''; } - /* - * Coupons/Discount related functions. - */ - /** * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error. * @@ -1378,7 +1333,7 @@ class WC_Cart extends WC_Legacy_Cart { * 1. Where a list of customer emails are set (limits coupon usage to those defined). * 2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email). * - * @param array $posted + * @param array $posted Post data. */ public function check_customer_coupons( $posted ) { if ( ! empty( $this->applied_coupons ) ) { @@ -1387,8 +1342,8 @@ class WC_Cart extends WC_Legacy_Cart { if ( $coupon->is_valid() ) { - // Limit to defined email addresses - if ( is_array( $coupon->get_email_restrictions() ) && sizeof( $coupon->get_email_restrictions() ) > 0 ) { + // Limit to defined email addresses. + if ( is_array( $coupon->get_email_restrictions() ) && count( $coupon->get_email_restrictions() ) > 0 ) { $check_emails = array(); if ( is_user_logged_in() ) { $current_user = wp_get_current_user(); @@ -1397,18 +1352,16 @@ class WC_Cart extends WC_Legacy_Cart { $check_emails[] = $posted['billing_email']; $check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) ); - if ( 0 == sizeof( array_intersect( $check_emails, $coupon->get_email_restrictions() ) ) ) { + if ( 0 === count( array_intersect( $check_emails, $coupon->get_email_restrictions() ) ) ) { $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED ); - - // Remove the coupon $this->remove_coupon( $code ); - // Flag totals for refresh + // Flag totals for refresh. WC()->session->set( 'refresh_totals', true ); } } - // Usage limits per user - check against billing and user email and user ID + // Usage limits per user - check against billing and user email and user ID. if ( $coupon->get_usage_limit_per_user() > 0 ) { $check_emails = array(); $used_by = $coupon->get_used_by(); @@ -1416,28 +1369,26 @@ class WC_Cart extends WC_Legacy_Cart { if ( is_user_logged_in() ) { $current_user = wp_get_current_user(); $check_emails[] = sanitize_email( $current_user->user_email ); - $usage_count = sizeof( array_keys( $used_by, get_current_user_id() ) ); + $usage_count = count( array_keys( $used_by, get_current_user_id(), true ) ); } else { $check_emails[] = sanitize_email( $posted['billing_email'] ); $user = get_user_by( 'email', $posted['billing_email'] ); if ( $user ) { - $usage_count = sizeof( array_keys( $used_by, $user->ID ) ); + $usage_count = count( array_keys( $used_by, $user->ID, true ) ); } else { $usage_count = 0; } } foreach ( $check_emails as $check_email ) { - $usage_count = $usage_count + sizeof( array_keys( $used_by, $check_email ) ); + $usage_count = $usage_count + count( array_keys( $used_by, $check_email, true ) ); } if ( $usage_count >= $coupon->get_usage_limit_per_user() ) { $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ); - - // Remove the coupon $this->remove_coupon( $code ); - // Flag totals for refresh + // Flag totals for refresh. WC()->session->set( 'refresh_totals', true ); } } @@ -1448,18 +1399,19 @@ class WC_Cart extends WC_Legacy_Cart { /** * Returns whether or not a discount has been applied. - * @param string $coupon_code + * + * @param string $coupon_code Coupon code to check. * @return bool */ public function has_discount( $coupon_code = '' ) { - return $coupon_code ? in_array( wc_format_coupon_code( $coupon_code ), $this->applied_coupons ) : sizeof( $this->applied_coupons ) > 0; + return $coupon_code ? in_array( wc_format_coupon_code( $coupon_code ), $this->applied_coupons, true ) : count( $this->applied_coupons ) > 0; } /** * Applies a coupon code passed to the method. * - * @param string $coupon_code - The code to apply - * @return bool True if the coupon is applied, false if it does not exist or cannot be applied + * @param string $coupon_code - The code to apply. + * @return bool True if the coupon is applied, false if it does not exist or cannot be applied. */ public function add_discount( $coupon_code ) { // Coupons are globally disabled. @@ -1497,7 +1449,7 @@ class WC_Cart extends WC_Legacy_Cart { $coupons_to_keep = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons ); foreach ( $this->applied_coupons as $applied_coupon ) { - $keep_key = array_search( $applied_coupon, $coupons_to_keep ); + $keep_key = array_search( $applied_coupon, $coupons_to_keep, true ); if ( false === $keep_key ) { $this->remove_coupon( $applied_coupon ); } else { @@ -1549,8 +1501,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get array of applied coupon objects and codes. * - * @param null $deprecated - * + * @param null $deprecated No longer used. * @return array of applied coupons */ public function get_coupons( $deprecated = null ) { @@ -1579,8 +1530,9 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get the discount amount for a used coupon. - * @param string $code coupon code - * @param bool $ex_tax inc or ex tax + * + * @param string $code coupon code. + * @param bool $ex_tax inc or ex tax. * @return float discount amount */ public function get_coupon_discount_amount( $code, $ex_tax = true ) { @@ -1595,8 +1547,8 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get the discount tax amount for a used coupon (for tax inclusive prices). - * @param string $code coupon code - * @param bool inc or ex tax + * + * @param string $code coupon code. * @return float discount amount */ public function get_coupon_discount_tax_amount( $code ) { @@ -1606,7 +1558,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax. * - * @param null $deprecated + * @param null $deprecated No longer used. */ public function remove_coupons( $deprecated = null ) { $this->applied_coupons = $this->coupon_discount_amounts = $this->coupon_discount_tax_amounts = $this->coupon_applied_count = array(); @@ -1617,18 +1569,18 @@ class WC_Cart extends WC_Legacy_Cart { /** * Remove a single coupon by code. - * @param string $coupon_code Code of the coupon to remove + * + * @param string $coupon_code Code of the coupon to remove. * @return bool */ public function remove_coupon( $coupon_code ) { - // Coupons are globally disabled + // Coupons are globally disabled. if ( ! wc_coupons_enabled() ) { return false; } - // Get the coupon $coupon_code = wc_format_coupon_code( $coupon_code ); - $position = array_search( $coupon_code, $this->applied_coupons ); + $position = array_search( $coupon_code, $this->applied_coupons, true ); if ( false !== $position ) { unset( $this->applied_coupons[ $position ] ); @@ -1641,76 +1593,6 @@ class WC_Cart extends WC_Legacy_Cart { return true; } - /** - * Function to apply discounts to a product and get the discounted price (before tax is applied). - * - * @param mixed $values - * @param mixed $price - * @param bool $add_totals (default: false) - * @return float price - */ - public function get_discounted_price( $values, $price, $add_totals = false ) { - if ( ! $price ) { - return $price; - } - - $undiscounted_price = $price; - - if ( ! empty( $this->coupons ) ) { - $product = $values['data']; - - foreach ( $this->coupons as $code => $coupon ) { - if ( $coupon->is_valid() && ( $coupon->is_valid_for_product( $product, $values ) || $coupon->is_valid_for_cart() ) ) { - $discount_amount = $coupon->get_discount_amount( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ? $price : $undiscounted_price, $values, true ); - $discount_amount = min( $price, $discount_amount ); - $price = max( $price - $discount_amount, 0 ); - - // Store the totals for DISPLAY in the cart. - if ( $add_totals ) { - $total_discount = $discount_amount * $values['quantity']; - $total_discount_tax = 0; - - if ( wc_tax_enabled() && $product->is_taxable() ) { - $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); - $taxes = WC_Tax::calc_tax( $discount_amount, $tax_rates, $this->prices_include_tax ); - $total_discount_tax = WC_Tax::get_tax_total( $taxes ) * $values['quantity']; - $total_discount = $this->prices_include_tax ? $total_discount - $total_discount_tax : $total_discount; - $this->discount_cart_tax += $total_discount_tax; - } - - $this->discount_cart += $total_discount; - $this->increase_coupon_discount_amount( $code, $total_discount, $total_discount_tax ); - $this->increase_coupon_applied_count( $code, $values['quantity'] ); - } - } - - // If the price is 0, we can stop going through coupons because there is nothing more to discount for this product. - if ( 0 >= $price ) { - break; - } - } - } - - return apply_filters( 'woocommerce_get_discounted_price', $price, $values, $this ); - } - - /** - * Store how much discount each coupon grants. - * - * @access private - * @param string $code - * @param double $amount - * @param double $tax - */ - private function increase_coupon_discount_amount( $code, $amount, $tax ) { - $this->coupon_discount_amounts[ $code ] = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] + $amount : $amount; - $this->coupon_discount_tax_amounts[ $code ] = isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] + $tax : $tax; - } - - /* - * Fees API to add additional costs to orders. - */ - /** * Add additional fee to the cart. * @@ -1727,12 +1609,11 @@ class WC_Cart extends WC_Legacy_Cart { * @param string $tax_class The tax class for the fee if taxable. A blank string is standard tax class. (default: ''). */ public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) { - $new_fee_id = sanitize_title( $name ); // Only add each fee once. foreach ( $this->fees as $fee ) { - if ( $fee->id == $new_fee_id ) { + if ( $fee->id === $new_fee_id ) { return; } } @@ -1761,31 +1642,27 @@ class WC_Cart extends WC_Legacy_Cart { * Calculate fees. */ public function calculate_fees() { - // Reset fees before calculation + // Reset fees before calculation. $this->fee_total = 0; $this->fees = array(); - // Fire an action where developers can add their fees + // Fire an action where developers can add their fees. do_action( 'woocommerce_cart_calculate_fees', $this ); - // If fees were added, total them and calculate tax + // If fees were added, total them and calculate tax. if ( ! empty( $this->fees ) ) { foreach ( $this->fees as $fee_key => $fee ) { $this->fee_total += $fee->amount; if ( $fee->taxable ) { - // Get tax rates $tax_rates = WC_Tax::get_rates( $fee->tax_class ); $fee_taxes = WC_Tax::calc_tax( $fee->amount, $tax_rates, false ); if ( ! empty( $fee_taxes ) ) { - // Set the tax total for this fee $this->fees[ $fee_key ]->tax = array_sum( $fee_taxes ); - - // Set tax data - Since 2.2 $this->fees[ $fee_key ]->tax_data = $fee_taxes; - // Tax rows - merge the totals we just got + // Tax rows - merge the totals we just got. foreach ( array_keys( $this->taxes + $fee_taxes ) as $key ) { $this->taxes[ $key ] = ( isset( $fee_taxes[ $key ] ) ? $fee_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); } @@ -1795,10 +1672,6 @@ class WC_Cart extends WC_Legacy_Cart { } } - /* - * Get Formatted Totals. - */ - /** * Gets the order total (after calculation). * @@ -1824,7 +1697,6 @@ class WC_Cart extends WC_Legacy_Cart { /** * Gets the cart contents total (after calculation). * - * @todo deprecate? It's unused. * @return string formatted price */ public function get_cart_total() { @@ -1840,35 +1712,28 @@ class WC_Cart extends WC_Legacy_Cart { /** * Gets the sub total (after calculation). * - * @param bool $compound whether to include compound taxes + * @param bool $compound whether to include compound taxes. * @return string formatted price */ public function get_cart_subtotal( $compound = false ) { - // If the cart has compound tax, we want to show the subtotal as - // cart + shipping + non-compound taxes (after discount) + /** + * If the cart has compound tax, we want to show the subtotal as cart + shipping + non-compound taxes (after discount). + */ if ( $compound ) { - $cart_subtotal = wc_price( $this->cart_contents_total + $this->shipping_total + $this->get_taxes_total( false, false ) ); - // Otherwise we show cart items totals only (before discount) + } elseif ( 'excl' === $this->tax_display_cart ) { + $cart_subtotal = wc_price( $this->subtotal_ex_tax ); + + if ( $this->tax_total > 0 && $this->prices_include_tax ) { + $cart_subtotal .= ' ' . WC()->countries->ex_tax_or_vat() . ''; + } } else { + $cart_subtotal = wc_price( $this->subtotal ); - // Display varies depending on settings - if ( 'excl' === $this->tax_display_cart ) { - - $cart_subtotal = wc_price( $this->subtotal_ex_tax ); - - if ( $this->tax_total > 0 && $this->prices_include_tax ) { - $cart_subtotal .= ' ' . WC()->countries->ex_tax_or_vat() . ''; - } - } else { - - $cart_subtotal = wc_price( $this->subtotal ); - - if ( $this->tax_total > 0 && ! $this->prices_include_tax ) { - $cart_subtotal .= ' ' . WC()->countries->inc_tax_or_vat() . ''; - } + if ( $this->tax_total > 0 && ! $this->prices_include_tax ) { + $cart_subtotal .= ' ' . WC()->countries->inc_tax_or_vat() . ''; } } @@ -1878,7 +1743,7 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get the product row price per item. * - * @param WC_Product $product + * @param WC_Product $product Product object. * @return string formatted price */ public function get_product_price( $product ) { @@ -1897,16 +1762,14 @@ class WC_Cart extends WC_Legacy_Cart { * * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate. * - * @param WC_Product $product - * @param int $quantity + * @param WC_Product $product Product object. + * @param int $quantity Quantity being purchased. * @return string formatted price */ public function get_product_subtotal( $product, $quantity ) { - $price = $product->get_price(); - $taxable = $product->is_taxable(); + $price = $product->get_price(); - // Taxable - if ( $taxable ) { + if ( $product->is_taxable() ) { if ( 'excl' === $this->tax_display_cart ) { @@ -1925,13 +1788,9 @@ class WC_Cart extends WC_Legacy_Cart { $product_subtotal .= ' ' . WC()->countries->inc_tax_or_vat() . ''; } } - - // Non-taxable } else { - $row_price = $price * $quantity; $product_subtotal = wc_price( $row_price ); - } return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $product, $quantity, $this ); @@ -1950,7 +1809,8 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get a tax amount. - * @param string $tax_rate_id + * + * @param string $tax_rate_id ID of the tax rate to get taxes for. * @return float amount */ public function get_tax_amount( $tax_rate_id ) { @@ -1959,7 +1819,8 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get a tax amount. - * @param string $tax_rate_id + * + * @param string $tax_rate_id ID of the tax rate to get taxes for. * @return float amount */ public function get_shipping_tax_amount( $tax_rate_id ) { @@ -1969,8 +1830,8 @@ class WC_Cart extends WC_Legacy_Cart { /** * Get tax row amounts with or without compound taxes includes. * - * @param bool $compound True if getting compound taxes - * @param bool $display True if getting total to display + * @param bool $compound True if getting compound taxes. + * @param bool $display True if getting total to display. * @return float price */ public function get_taxes_total( $compound = true, $display = true ) { @@ -2017,68 +1878,6 @@ class WC_Cart extends WC_Legacy_Cart { * @return mixed formatted price or false if there are none */ public function get_total_discount() { - if ( $this->get_cart_discount_total() ) { - $total_discount = wc_price( $this->get_cart_discount_total() ); - } else { - $total_discount = false; - } - return apply_filters( 'woocommerce_cart_total_discount', $total_discount, $this ); - } - - /** - * Gets the total (product) discount amount - these are applied before tax. - * - * @deprecated Order discounts (after tax) removed in 2.3 so multiple methods for discounts are no longer required. - * @return mixed formatted price or false if there are none - */ - public function get_discounts_before_tax() { - wc_deprecated_function( 'get_discounts_before_tax', '2.3', 'get_total_discount' ); - if ( $this->get_cart_discount_total() ) { - $discounts_before_tax = wc_price( $this->get_cart_discount_total() ); - } else { - $discounts_before_tax = false; - } - return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this ); - } - - /** - * Get the total of all order discounts (after tax discounts). - * - * @deprecated Order discounts (after tax) removed in 2.3 - * @return int - */ - public function get_order_discount_total() { - wc_deprecated_function( 'get_order_discount_total', '2.3' ); - return 0; - } - - /** - * Function to apply cart discounts after tax. - * @deprecated Coupons can not be applied after tax - * - * @param $values - * @param $price - */ - public function apply_cart_discounts_after_tax( $values, $price ) { - wc_deprecated_function( 'apply_cart_discounts_after_tax', '2.3' ); - } - - /** - * Function to apply product discounts after tax. - * @deprecated Coupons can not be applied after tax - * - * @param $values - * @param $price - */ - public function apply_product_discounts_after_tax( $values, $price ) { - wc_deprecated_function( 'apply_product_discounts_after_tax', '2.3' ); - } - - /** - * Gets the order discount amount - these are applied after tax. - * @deprecated Coupons can not be applied after tax - */ - public function get_discounts_after_tax() { - wc_deprecated_function( 'get_discounts_after_tax', '2.3' ); + return apply_filters( 'woocommerce_cart_total_discount', $this->get_cart_discount_total() ? wc_price( $this->get_cart_discount_total() ) : false, $this ); } } diff --git a/includes/legacy/class-wc-legacy-cart.php b/includes/legacy/class-wc-legacy-cart.php index 24738857ad4..5a806b6b019 100644 --- a/includes/legacy/class-wc-legacy-cart.php +++ b/includes/legacy/class-wc-legacy-cart.php @@ -23,22 +23,128 @@ abstract class WC_Legacy_Cart { /** * Contains an array of coupon usage counts after they have been applied. * + * @deprecated 3.2.0 * @var array */ public $coupon_applied_count = array(); /** - * Store how many times each coupon is applied to cart/items. + * Function to apply discounts to a product and get the discounted price (before tax is applied). * - * @deprecated 3.2.0 - * @access private - * @param string $code - * @param int $count + * @deprecated Calculation and coupon logic is handled in WC_Cart_Totals. + * @param mixed $values Cart item. + * @param mixed $price Price of item. + * @param bool $add_totals Legacy. + * @return float price */ - protected function increase_coupon_applied_count( $code, $count = 1 ) { - if ( empty( $this->coupon_applied_count[ $code ] ) ) { - $this->coupon_applied_count[ $code ] = 0; + public function get_discounted_price( $values, $price, $add_totals = false ) { + wc_deprecated_function( 'WC_Cart::get_discounted_price', '3.2', '' ); + + $cart_item_key = $values['key']; + $cart_item = $this->cart_contents[ $cart_item_key ]; + + return $cart_item->get_line_total(); + } + + /** + * Gets the url to the cart page. + * + * @deprecated 2.5.0 in favor to wc_get_cart_url() + * @return string url to page + */ + public function get_cart_url() { + wc_deprecated_function( 'WC_Cart::get_cart_url', '2.5', 'wc_get_cart_url' ); + return wc_get_cart_url(); + } + + /** + * Gets the url to the checkout page. + * + * @deprecated 2.5.0 in favor to wc_get_checkout_url() + * @return string url to page + */ + public function get_checkout_url() { + wc_deprecated_function( 'WC_Cart::get_checkout_url', '2.5', 'wc_get_checkout_url' ); + return wc_get_checkout_url(); + } + + /** + * Sees if we need a shipping address. + * + * @deprecated 2.5.0 in favor to wc_ship_to_billing_address_only() + * @return bool + */ + public function ship_to_billing_address_only() { + wc_deprecated_function( 'WC_Cart::ship_to_billing_address_only', '2.5', 'wc_ship_to_billing_address_only' ); + return wc_ship_to_billing_address_only(); + } + + /** + * Coupons enabled function. Filterable. + * + * @deprecated 2.5.0 in favor to wc_coupons_enabled() + * @return bool + */ + public function coupons_enabled() { + return wc_coupons_enabled(); + } + + /** + * Gets the total (product) discount amount - these are applied before tax. + * + * @deprecated Order discounts (after tax) removed in 2.3 so multiple methods for discounts are no longer required. + * @return mixed formatted price or false if there are none. + */ + public function get_discounts_before_tax() { + wc_deprecated_function( 'get_discounts_before_tax', '2.3', 'get_total_discount' ); + if ( $this->get_cart_discount_total() ) { + $discounts_before_tax = wc_price( $this->get_cart_discount_total() ); + } else { + $discounts_before_tax = false; } - $this->coupon_applied_count[ $code ] += $count; + return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this ); + } + + /** + * Get the total of all order discounts (after tax discounts). + * + * @deprecated Order discounts (after tax) removed in 2.3. + * @return int + */ + public function get_order_discount_total() { + wc_deprecated_function( 'get_order_discount_total', '2.3' ); + return 0; + } + + /** + * Function to apply cart discounts after tax. + * + * @deprecated Coupons can not be applied after tax. + * @param $values + * @param $price + */ + public function apply_cart_discounts_after_tax( $values, $price ) { + wc_deprecated_function( 'apply_cart_discounts_after_tax', '2.3' ); + } + + /** + * Function to apply product discounts after tax. + * + * @deprecated Coupons can not be applied after tax. + * + * @param $values + * @param $price + */ + public function apply_product_discounts_after_tax( $values, $price ) { + wc_deprecated_function( 'apply_product_discounts_after_tax', '2.3' ); + } + + /** + * Gets the order discount amount - these are applied after tax. + * + * @deprecated Coupons can not be applied after tax. + */ + public function get_discounts_after_tax() { + wc_deprecated_function( 'get_discounts_after_tax', '2.3' ); } } From ceb191e73eac518e4f466362c73315aa35c53a9f Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 10:52:31 +0100 Subject: [PATCH 216/224] The world is not ready for WC_Cart_Item --- includes/class-wc-cart-item.php | 218 -------------------------------- 1 file changed, 218 deletions(-) delete mode 100644 includes/class-wc-cart-item.php diff --git a/includes/class-wc-cart-item.php b/includes/class-wc-cart-item.php deleted file mode 100644 index 694a87cd652..00000000000 --- a/includes/class-wc-cart-item.php +++ /dev/null @@ -1,218 +0,0 @@ - 0, - 'quantity' => 0, - 'variation' => array(), - ); - - /** - * Product this item represents. - * - * @var WC_Product - */ - protected $product = null; - - /** - * Constructor. - * - * @param array $data - */ - public function __construct( $data = array() ) { - $this->set_data( $data ); - } - - /** - * Gets price of the product. - * @return float - */ - public function get_price() { - return $this->get_product() ? $this->get_product()->get_price() : 0; - } - - /** - * Gets weight of the product. - * @return float - */ - public function get_weight() { - return $this->get_product() ? $this->get_product()->get_weight() : 0; - } - - /** - * Gets tax class of the product. - * @return float - */ - public function get_tax_class() { - return $this->get_product() ? $this->get_product()->get_tax_class() : ''; - } - - /** - * Set product. - * @param int $value - */ - public function set_product( $value ) { - $this->product = $value; - $this->data['product_id'] = is_callable( array( $this->product, 'get_variation_id' ) ) ? $this->product->get_variation_id() : $this->product->get_id(); - } - - /** - * Get product object. - * - * @return WC_Product - */ - public function get_product() { - return ! is_null( $this->product ) ? $this->product : ( $this->product = wc_get_product( $this->get_product_id() ) ); - } - - /** - * Get all item data. - * @return array - */ - public function get_data() { - return $this->data; - } - - /** - * Product or variation ID this item represents. - * @return int - */ - public function get_product_id() { - return $this->data['product_id']; - } - - /** - * Get quantity in cart. - * @return int - */ - public function get_quantity() { - return $this->data['quantity']; - } - - /** - * Get variation data. - * @return array - */ - public function get_variation() { - return $this->data['variation']; - } - - /** - * Set product ID. - * @param int $value - */ - public function set_product_id( $value ) { - $this->data['product_id'] = absint( $value ); - $this->product = null; - } - - /** - * Set Quantity. - * @param int $value - */ - public function set_quantity( $value ) { - $this->data['quantity'] = wc_stock_amount( $value ); - } - - /** - * Set variation data. - * @param array $value - */ - public function set_variation( $value ) { - $this->data['variation'] = (array) $value; - } - - /** - * Set all data. - * @param array $value - */ - public function set_data( $values ) { - if ( is_a( $values, 'WC_Cart_Item' ) ) { - $values = $values->get_data(); - } - foreach ( $values as $key => $value ) { - if ( in_array( $key, array( 'quantity', 'product_id', 'variation', 'product' ) ) ) { - $this->{ "set_$key" }( $value ); - } else { - $this->data[ $key ] = $value; - } - } - } - - /** - * ArrayAccess/Backwards compatibility. - * - * @param string $offset - * @return mixed - */ - public function offsetGet( $offset ) { - switch ( $offset ) { - case 'data' : - return $this->get_product(); - case 'variation_id' : - return is_callable( array( $this, 'get_variation_id' ) ) ? $this->get_product()->get_variation_id() : 0; - } - return isset( $this->data[ $offset ] ) ? $this->data[ $offset ] : ''; - } - - /** - * ArrayAccess/Backwards compatibility. - * - * @param string $offset - * @param mixed $value - */ - public function offsetSet( $offset, $value ) { - switch ( $offset ) { - case 'data' : - $this->set_product( $value ); - break; - case 'variation_id' : - $this->set_product( wc_get_product( $value ) ); - break; - default : - $this->data[ $offset ] = $value; - break; - } - } - - /** - * ArrayAccess/Backwards compatibility. - * - * @param string $offset - * @return bool - */ - public function offsetExists( $offset ) { - if ( in_array( $offset, array( 'data' ) ) || isset( $this->data[ $offset ] ) ) { - return true; - } - return false; - } - - /** - * ArrayAccess/Backwards compatibility. - * - * @param string $offset - */ - public function offsetUnset( $offset ) { - unset( $this->data[ $offset ] ); - } -} From e7ff4186eb0abfdbc54642cd29bd0c6b9c6190ba Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 10:52:54 +0100 Subject: [PATCH 217/224] remove include --- includes/class-woocommerce.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index 0708a7ee6fc..1ca61f52e45 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -405,7 +405,6 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-frontend-scripts.php' ); // Frontend Scripts. include_once( WC_ABSPATH . 'includes/class-wc-form-handler.php' ); // Form Handlers. include_once( WC_ABSPATH . 'includes/class-wc-cart.php' ); // The main cart class. - include_once( WC_ABSPATH . 'includes/class-wc-cart-item.php' ); include_once( WC_ABSPATH . 'includes/class-wc-tax.php' ); // Tax class. include_once( WC_ABSPATH . 'includes/class-wc-shipping-zones.php' ); // Shipping Zones class. include_once( WC_ABSPATH . 'includes/class-wc-customer.php' ); // Customer class. From 684a2b22ed94d887fd771fa2d1b3b855b163be1e Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 11:07:36 +0100 Subject: [PATCH 218/224] typo --- includes/wc-attribute-functions.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php index 984781414e5..f64e6400c34 100644 --- a/includes/wc-attribute-functions.php +++ b/includes/wc-attribute-functions.php @@ -479,7 +479,7 @@ function wc_create_attribute( $args ) { return new WP_Error( 'cannot_update_attribute', __( 'Could not update the attribute.', 'woocommerce' ), array( 'status' => 400 ) ); } - // Set old_slug to check for database chances. + // Set old_slug to check for database changes. $args['old_slug'] = ! empty( $args['old_slug'] ) ? $args['old_slug'] : $args['slug']; /** @@ -582,7 +582,7 @@ function wc_delete_attribute( $id ) { $taxonomy = wc_attribute_taxonomy_name( $name ); /** - * Before delete an attribute. + * Before deleting an attribute. * * @param int $id Attribute ID. * @param string $name Attribute name. @@ -599,7 +599,7 @@ function wc_delete_attribute( $id ) { } /** - * After delete an attribute. + * After deleting an attribute. * * @param int $id Attribute ID. * @param string $name Attribute name. From ee830d19b4588c5c40e070af61741b46cf363b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Kj=C3=A6r=20Eriksen?= Date: Tue, 8 Aug 2017 12:47:16 +0200 Subject: [PATCH 219/224] Store status_transition in a local variable and reset it before firing hooks --- includes/class-wc-order.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index d4db07492fd..0d665a6517e 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -303,25 +303,27 @@ class WC_Order extends WC_Abstract_Order { * Handle the status transition. */ protected function status_transition() { - if ( $this->status_transition ) { - do_action( 'woocommerce_order_status_' . $this->status_transition['to'], $this->get_id(), $this ); + $status_transition = $this->status_transition; - if ( ! empty( $this->status_transition['from'] ) ) { + // Reset status transition variable + $this->status_transition = false; + + if ( $status_transition ) { + do_action( 'woocommerce_order_status_' . $status_transition['to'], $this->get_id(), $this ); + + if ( ! empty( $status_transition['from'] ) ) { /* translators: 1: old order status 2: new order status */ - $transition_note = sprintf( __( 'Order status changed from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $this->status_transition['from'] ), wc_get_order_status_name( $this->status_transition['to'] ) ); + $transition_note = sprintf( __( 'Order status changed from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $status_transition['from'] ), wc_get_order_status_name( $status_transition['to'] ) ); - do_action( 'woocommerce_order_status_' . $this->status_transition['from'] . '_to_' . $this->status_transition['to'], $this->get_id(), $this ); - do_action( 'woocommerce_order_status_changed', $this->get_id(), $this->status_transition['from'], $this->status_transition['to'], $this ); + do_action( 'woocommerce_order_status_' . $status_transition['from'] . '_to_' . $status_transition['to'], $this->get_id(), $this ); + do_action( 'woocommerce_order_status_changed', $this->get_id(), $status_transition['from'], $status_transition['to'], $this ); } else { /* translators: %s: new order status */ - $transition_note = sprintf( __( 'Order status set to %s.', 'woocommerce' ), wc_get_order_status_name( $this->status_transition['to'] ) ); + $transition_note = sprintf( __( 'Order status set to %s.', 'woocommerce' ), wc_get_order_status_name( $status_transition['to'] ) ); } // Note the transition occurred - $this->add_order_note( trim( $this->status_transition['note'] . ' ' . $transition_note ), 0, $this->status_transition['manual'] ); - - // This has ran, so reset status transition variable - $this->status_transition = false; + $this->add_order_note( trim( $status_transition['note'] . ' ' . $transition_note ), 0, $status_transition['manual'] ); } } From 735a277e52fa96f7cb3b028103d679767d2dbacd Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 12:44:36 +0100 Subject: [PATCH 220/224] initial state --- assets/js/admin/meta-boxes-product.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/assets/js/admin/meta-boxes-product.js b/assets/js/admin/meta-boxes-product.js index 31a6882a76a..89ae7f04bbf 100644 --- a/assets/js/admin/meta-boxes-product.js +++ b/assets/js/admin/meta-boxes-product.js @@ -238,6 +238,15 @@ jQuery( function( $ ) { }).change(); // DATE PICKER FIELDS. + function date_picker_select( datepicker ) { + var option = $( datepicker ).next().is( '.hasDatepicker' ) ? 'minDate' : 'maxDate', + otherDateField = 'minDate' === option ? $( datepicker ).next() : $( datepicker ).prev(), + date = $( datepicker ).datepicker( 'getDate' ); + + $( otherDateField ).datepicker( 'option', option, date ); + $( datepicker ).change(); + } + $( '.sale_price_dates_fields' ).each( function() { $( this ).find( 'input' ).datepicker({ defaultDate: '', @@ -245,14 +254,10 @@ jQuery( function( $ ) { numberOfMonths: 1, showButtonPanel: true, onSelect: function() { - var option = $( this ).next().is('.hasDatepicker') ? 'minDate' : 'maxDate', - dates = $( this ).closest( '.sale_price_dates_fields' ).find( 'input' ), - date = $( this ).datepicker( 'getDate' ); - - dates.not( this ).datepicker( 'option', option, date ); - $( this ).change(); + date_picker_select( $( this ) ); } }); + $( this ).find( 'input' ).each( function() { date_picker_select( $( this ) ) } ); }); // ATTRIBUTE TABLES. From 2f6aaa85bb2e83bcd223d850b50b8175c5188901 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 13:04:27 +0100 Subject: [PATCH 221/224] Avoid using meta directly --- includes/class-wc-customer.php | 22 ++++++++++++++++++++++ includes/wc-account-functions.php | 30 +++++++++--------------------- templates/myaccount/my-address.php | 8 +++----- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/includes/class-wc-customer.php b/includes/class-wc-customer.php index e12916886a6..ae3e368cb3a 100644 --- a/includes/class-wc-customer.php +++ b/includes/class-wc-customer.php @@ -439,6 +439,17 @@ class WC_Customer extends WC_Legacy_Customer { return $value; } + /** + * Get billing. + * + * @since 3.2.0 + * @param string $context + * @return array + */ + public function get_billing( $context = 'view' ) { + return $this->get_prop( 'billing', $context ); + } + /** * Get billing_first_name. * @@ -559,6 +570,17 @@ class WC_Customer extends WC_Legacy_Customer { return $this->get_address_prop( 'phone', 'billing', $context ); } + /** + * Get shipping. + * + * @since 3.2.0 + * @param string $context + * @return array + */ + public function get_shipping( $context = 'view' ) { + return $this->get_prop( 'shipping', $context ); + } + /** * Get shipping_first_name. * diff --git a/includes/wc-account-functions.php b/includes/wc-account-functions.php index 20a35a3adbb..aabb09b45ba 100644 --- a/includes/wc-account-functions.php +++ b/includes/wc-account-functions.php @@ -244,30 +244,18 @@ function wc_get_account_payment_methods_types() { * Get account formatted address. * * @since 3.2.0 - * @param string $name + * @param string $address_type billing or shipping. * @return string */ -function wc_get_account_formatted_address( $name ) { - $customer_id = get_current_user_id(); - $meta_keys = [ - 'first_name', - 'last_name', - 'company', - 'address_1', - 'address_2', - 'city', - 'postcode', - 'country', - ]; - - $meta = []; - foreach ( $meta_keys as $key ) { - $meta[ $key ] = get_user_meta( $customer_id, $name . '_' . $key, true ); +function wc_get_account_formatted_address( $address_type ) { + $getter = "get_{$address_type}"; + $customer = new WC_Customer( get_current_user_id() ); + + if ( is_callable( array( $customer, $getter ) ) ) { + $address = $customer->$getter(); + unset( $address['email'], $address['tel'] ); } - - $address = apply_filters( 'woocommerce_my_account_my_address_formatted_address', $meta, $customer_id, $name ); - - return WC()->countries->get_formatted_address( $address ); + return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_my_account_my_address_formatted_address', $address, $customer->get_id(), $address_type ) ); } /** diff --git a/templates/myaccount/my-address.php b/templates/myaccount/my-address.php index 420c414ceca..6ef25a84e39 100644 --- a/templates/myaccount/my-address.php +++ b/templates/myaccount/my-address.php @@ -52,12 +52,10 @@ $col = 1;

                -
                - -
                + echo $address ? wp_kses_post( $address ) : esc_html_e( 'You have not set up this type of address yet.', 'woocommerce' ); + ?> From 90f0a835b10bdd0ba701ae95a76e92ee55a0b09b Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 15:00:03 +0100 Subject: [PATCH 222/224] Fix filename image handling --- includes/import/abstract-wc-product-importer.php | 14 ++++++++------ includes/import/class-wc-product-csv-importer.php | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php index e7ce0cfb359..d6404a8a370 100644 --- a/includes/import/abstract-wc-product-importer.php +++ b/includes/import/abstract-wc-product-importer.php @@ -504,8 +504,8 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { $base_url = $upload_dir['baseurl'] . '/'; // Check first if attachment is on WordPress uploads directory. - if ( false !== strpos( $url, $base_url ) ) { - // Search for yyyy/mm/slug.extension + if ( false === strpos( $url, $base_url ) ) { + // Search for yyyy/mm/slug.extension. $file = str_replace( $base_url, '', $url ); $args = array( 'post_type' => 'attachment', @@ -515,11 +515,10 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { array( 'value' => $file, 'compare' => 'LIKE', - 'key' => '_wp_attachment_metadata', + 'key' => '_wp_attached_file', ), ), ); - if ( $ids = get_posts( $args ) ) { $id = current( $ids ); } @@ -535,14 +534,13 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { ), ), ); - if ( $ids = get_posts( $args ) ) { $id = current( $ids ); } } // Upload if attachment does not exists. - if ( ! $id ) { + if ( ! $id && stristr( $url, '://' ) ) { $upload = wc_rest_upload_image_from_url( $url ); if ( is_wp_error( $upload ) ) { @@ -559,6 +557,10 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { update_post_meta( $id, '_wc_attachment_source', $url ); } + if ( ! $id ) { + throw new Exception( sprintf( __( 'Unable to use image "%s".', 'woocommerce' ), $url ), 400 ); + } + return $id; } diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php index aa69148a32e..fbc32877495 100644 --- a/includes/import/class-wc-product-csv-importer.php +++ b/includes/import/class-wc-product-csv-importer.php @@ -377,7 +377,7 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { } /** - * Parse images list from a CSV. + * Parse images list from a CSV. Images can be filenames or URLs. * * @param string $field Field value. * @return array @@ -387,7 +387,17 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { return array(); } - return array_map( 'esc_url_raw', $this->explode_values( $field ) ); + $images = array(); + + foreach ( $this->explode_values( $field ) as $image ) { + if ( stristr( $image, '://' ) ) { + $images[] = esc_url_raw( $image ); + } else { + $images[] = sanitize_file_name( $image ); + } + } + + return $images; } /** From 396faa19d73d1655712ce853e18456882792163b Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 8 Aug 2017 15:25:32 +0100 Subject: [PATCH 223/224] phpcs --- includes/class-wc-meta-data.php | 41 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/includes/class-wc-meta-data.php b/includes/class-wc-meta-data.php index 41595e1055a..b1830b5468b 100644 --- a/includes/class-wc-meta-data.php +++ b/includes/class-wc-meta-data.php @@ -1,43 +1,47 @@ current_data = $meta; $this->apply_changes(); } @@ -52,9 +56,8 @@ class WC_Meta_Data { /** * Creates or updates a property in the metadata object. * - * @param string $key - * @param mixed $value - * + * @param string $key Key to set. + * @param mixed $value Value to set. */ public function __set( $key, $value ) { $this->current_data[ $key ] = $value; @@ -64,7 +67,7 @@ class WC_Meta_Data { * Checks if a given key exists in our data. This is called internally * by `empty` and `isset`. * - * @param string $key + * @param string $key Key to check if set. */ public function __isset( $key ) { return array_key_exists( $key, $this->current_data ); @@ -73,8 +76,7 @@ class WC_Meta_Data { /** * Returns the value of any property. * - * @param string $key - * + * @param string $key Key to get. * @return mixed Property value or NULL if it does not exists */ public function __get( $key ) { @@ -96,7 +98,6 @@ class WC_Meta_Data { $changes[ $id ] = $value; } } - return $changes; } From 56f654eb4cc3f16219e9096f9f1b247ab7041e19 Mon Sep 17 00:00:00 2001 From: Claudio Sanches Date: Tue, 8 Aug 2017 12:08:06 -0300 Subject: [PATCH 224/224] Allow define customer ID in wc_get_account_formatted_address() This should help 3rd party integrations, like custom emails or invoices. Releated to #16165 --- includes/wc-account-functions.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/includes/wc-account-functions.php b/includes/wc-account-functions.php index dae308f5922..ce11e812f2f 100644 --- a/includes/wc-account-functions.php +++ b/includes/wc-account-functions.php @@ -283,17 +283,27 @@ function wc_get_account_orders_actions( $order ) { * Get account formatted address. * * @since 3.2.0 - * @param string $address_type billing or shipping. + * @param string $address_type Address type. + * Accepts: 'billing' or 'shipping'. + * Default to 'billing'. + * @param int $customer_id Customer ID. + * Default to 0. * @return string */ -function wc_get_account_formatted_address( $address_type ) { - $getter = "get_{$address_type}"; - $customer = new WC_Customer( get_current_user_id() ); +function wc_get_account_formatted_address( $address_type = 'billing', $customer_id = 0 ) { + $getter = "get_{$address_type}"; + + if ( 0 === $customer_id ) { + $customer_id = get_current_user_id(); + } + + $customer = new WC_Customer( $customer_id ); if ( is_callable( array( $customer, $getter ) ) ) { $address = $customer->$getter(); unset( $address['email'], $address['tel'] ); } + return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_my_account_my_address_formatted_address', $address, $customer->get_id(), $address_type ) ); }