diff --git a/package-lock.json b/package-lock.json index e74ced018..c3d813ec5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-germanized", - "version": "3.15.0", + "version": "3.18.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "woocommerce-germanized", - "version": "3.15.0", + "version": "3.18.4", "license": "GPL-3.0+", "dependencies": { "@wordpress/autop": "3.16.0", diff --git a/packages/woocommerce-eu-tax-helper/src/Helper.php b/packages/woocommerce-eu-tax-helper/src/Helper.php new file mode 100644 index 000000000..01ee79bf8 --- /dev/null +++ b/packages/woocommerce-eu-tax-helper/src/Helper.php @@ -0,0 +1,1338 @@ +queue() : false; + } + + private static function load() { + $callback = function () { + if ( $queue = self::get_queue() ) { + if ( self::enable_tax_rate_observer() ) { + // Schedule once per day at 0:00 in local timezone + if ( null === $queue->get_next( 'woocommerce_eu_tax_helper_rate_observer', array(), 'woocommerce_eu_tax_helper' ) ) { + /** + * Use WC helper method which calculates the date in current + * local timezone. + */ + $date = wc_string_to_datetime( 'tomorrow midnight' ); + $date->modify( '+1 second' ); + + $queue->cancel_all( 'woocommerce_eu_tax_helper_rate_observer', array(), 'woocommerce_eu_tax_helper' ); + + /** + * Action scheduler expects the time in UTC. + */ + $queue->schedule_recurring( $date->getTimestamp(), DAY_IN_SECONDS, 'woocommerce_eu_tax_helper_rate_observer', array(), 'woocommerce_eu_tax_helper' ); + } + } else { + $queue->cancel( 'woocommerce_eu_tax_helper_rate_observer', array(), 'woocommerce_eu_tax_helper' ); + } + } + }; + + if ( ! did_action( 'init' ) ) { + add_action( 'init', $callback, 10 ); + } else { + $callback(); + } + + add_action( + 'woocommerce_eu_tax_helper_rate_observer', + function () { + self::maybe_apply_tax_rate_changesets(); + }, + 10 + ); + } + + public static function apply_tax_rate_changesets() { + $changes = self::get_eu_tax_rate_changesets(); + $last_applied_changes_date = null; + $today = new \WC_DateTime(); + + if ( $last_applied_changes = get_option( 'woocommerce_eu_tax_helper_last_rate_changeset' ) ) { + $last_applied_changes_date = wc_string_to_datetime( $last_applied_changes . ' 00:00:00' ); + } + + self::log( sprintf( 'Checking for tax rate changes @ %1$s. Last applied changes: %2$s', $today->date_i18n( 'Y-m-d' ), ( $last_applied_changes_date ? $last_applied_changes_date->date_i18n( 'Y-m-d' ) : '-' ) ) ); + + foreach ( $changes as $date => $changeset ) { + $changeset_date = wc_string_to_datetime( $date . ' 00:00:00' ); + + if ( ! $last_applied_changes_date || $changeset_date > $last_applied_changes_date ) { + $is_oss = self::oss_procedure_is_enabled(); + $countries = array_keys( $changeset ); + + if ( $today >= $changeset_date ) { + self::log( sprintf( 'Updating tax rates changes for %1$s @ %2$s: %3$s', $changeset_date->date_i18n( 'Y-m-d' ), $today->date_i18n( 'Y-m-d' ), wc_print_r( $changeset, true ) ) ); + + if ( $is_oss ) { + foreach ( $countries as $country ) { + self::delete_tax_rates_by_country( $country ); + } + + $tax_rates = self::generate_tax_rates( true, array(), $changeset, false ); + + self::log( sprintf( 'New tax rates: %1$s', wc_print_r( $tax_rates, true ) ) ); + + foreach ( $tax_rates as $tax_class_type => $tax_rate_data ) { + $class = $tax_rate_data['tax_class']; + $rates = $tax_rate_data['rates']; + + self::import_rates( $rates, $class, $tax_class_type, false ); + } + } elseif ( in_array( self::get_base_country(), $countries, true ) ) { + $eu_rates = self::get_eu_tax_rates( false ); + + foreach ( $changeset as $country => $tax_rates ) { + $eu_rates[ $country ] = $tax_rates; + } + + $tax_rates = self::generate_tax_rates( false, array(), $eu_rates, false ); + + self::log( sprintf( 'New tax rates: %1$s', wc_print_r( $tax_rates, true ) ) ); + + foreach ( $tax_rates as $tax_class_type => $tax_rate_data ) { + $class = $tax_rate_data['tax_class']; + $rates = $tax_rate_data['rates']; + + self::import_rates( $rates, $class, $tax_class_type ); + } + } + + update_option( 'woocommerce_eu_tax_helper_last_rate_changeset', $date ); + } + } + } + } + + protected static function maybe_apply_tax_rate_changesets() { + if ( self::enable_tax_rate_observer() ) { + self::apply_tax_rate_changesets(); + } + } + + protected static function log( $message, $type = 'info' ) { + $logger = wc_get_logger(); + + if ( ! $logger || ! apply_filters( 'woocommerce_eu_tax_helper_enable_logging', true ) ) { + return; + } + + if ( ! is_callable( array( $logger, $type ) ) ) { + $type = 'info'; + } + + $logger->{$type}( $message, array( 'source' => 'woocommerce-eu-tax-helper' ) ); + } + + public static function enable_tax_rate_observer() { + return apply_filters( 'woocommerce_eu_tax_helper_enable_tax_rate_observer', true ); + } + + public static function oss_procedure_is_enabled() { + return apply_filters( 'woocommerce_eu_tax_helper_oss_procedure_is_enabled', false ); + } + + /** + * There are different opinions on how the entrepreneurial status must be proven. + * By default, deliveries without a valid VAT ID are classified as b2c deliveries + * and therefore as relevant for the OSS procedure. + * + * @return bool + */ + public static function exclude_b2b_without_vat_id_from_oss() { + return apply_filters( 'woocommerce_eu_tax_helper_exclude_b2b_without_vat_id_from_oss', false ); + } + + public static function get_eu_countries() { + if ( ! WC()->countries ) { + return array(); + } + + $countries = WC()->countries->get_european_union_countries(); + + return $countries; + } + + public static function get_eu_vat_countries() { + $vat_countries = WC()->countries ? WC()->countries->get_european_union_countries( 'eu_vat' ) : array(); + + return apply_filters( 'woocommerce_eu_tax_helper_eu_vat_countries', $vat_countries ); + } + + public static function is_northern_ireland( $country, $postcode = '' ) { + if ( 'GB' === $country && 'BT' === strtoupper( substr( trim( $postcode ), 0, 2 ) ) ) { + return true; + } elseif ( 'IX' === $country ) { + return true; + } + + return false; + } + + public static function is_eu_vat_country( $country, $postcode = '' ) { + $country = wc_strtoupper( $country ); + $postcode = wc_normalize_postcode( $postcode ); + $is_eu_vat_country = in_array( $country, self::get_eu_vat_countries(), true ); + + if ( self::is_northern_ireland( $country, $postcode ) ) { + $is_eu_vat_country = true; + } elseif ( self::is_eu_vat_postcode_exemption( $country, $postcode ) ) { + $is_eu_vat_country = false; + } + + return apply_filters( 'woocommerce_eu_tax_helper_is_eu_vat_country', $is_eu_vat_country, $country, $postcode ); + } + + public static function is_third_country( $country, $postcode = '' ) { + $is_third_country = true; + + /** + * In case the base country is within EU consider all non-EU VAT countries as third countries. + * In any other case consider every non-base-country as third country. + */ + if ( in_array( self::get_base_country(), self::get_eu_vat_countries(), true ) ) { + $is_third_country = ! self::is_eu_vat_country( $country, $postcode ); + } else { + $is_third_country = self::get_base_country() !== $country; + } + + return apply_filters( 'woocommerce_eu_tax_helper_is_third_country', $is_third_country, $country, $postcode ); + } + + public static function is_eu_country( $country ) { + return in_array( $country, self::get_eu_countries(), true ); + } + + public static function is_eu_vat_postcode_exemption( $country, $postcode = '' ) { + $country = wc_strtoupper( $country ); + $postcode = wc_normalize_postcode( $postcode ); + $exemptions = self::get_vat_postcode_exemptions_by_country(); + $is_exempt = false; + + if ( ! empty( $postcode ) && in_array( $country, self::get_eu_vat_countries(), true ) ) { + if ( array_key_exists( $country, $exemptions ) ) { + $wildcards = wc_get_wildcard_postcodes( $postcode, $country ); + + foreach ( $exemptions[ $country ] as $exempt_postcode ) { + if ( in_array( $exempt_postcode, $wildcards, true ) ) { + $is_exempt = true; + break; + } + } + } + } + + return $is_exempt; + } + + /** + * Get VAT exemptions (of EU countries) for certain postcodes (e.g. canary islands) + * + * @see https://www.hk24.de/produktmarken/beratung-service/recht-und-steuern/steuerrecht/umsatzsteuer-mehrwertsteuer/umsatzsteuer-mehrwertsteuer-international/verfahrensrecht/territoriale-besonderheiten-umsatzsteuer-zollrecht-1167674 + * @see https://github.com/woocommerce/woocommerce/issues/5143 + * @see https://ec.europa.eu/taxation_customs/business/vat/eu-vat-rules-topic/territorial-status-eu-countries-certain-territories_en + * + * @return \string[][] + */ + public static function get_vat_postcode_exemptions_by_country( $country = '' ) { + $country = wc_strtoupper( $country ); + $exemptions = self::get_vat_postcode_exemptions(); + + if ( empty( $country ) ) { + return $exemptions; + } elseif ( array_key_exists( $country, $exemptions ) ) { + return $exemptions[ $country ]; + } else { + return array(); + } + } + + public static function get_vat_postcode_exemptions() { + $exemptions = array( + 'DE' => array( + '27498', // Helgoland + '78266', // Büsingen am Hochrhein + ), + 'ES' => array( + '35*', // Canary Islands + '38*', // Canary Islands + '51*', // Ceuta + '52*', // Melilla + ), + 'GR' => array( + '63086', // Mount Athos + '63087', // Mount Athos + ), + 'FR' => array( + '971*', // Guadeloupe + '972*', // Martinique + '973*', // French Guiana + '974*', // Réunion + '976*', // Mayotte + ), + 'IT' => array( + '22060', // Livigno, Campione d’Italia + '23030', // Lake Lugano + ), + 'FI' => array( + '22*', // Aland islands + ), + ); + + return $exemptions; + } + + /** + * @param integer|\WC_Order $order + * + * @return array + */ + public static function get_order_taxable_location( $order ) { + $order = is_a( $order, 'WC_Order' ) ? $order : wc_get_order( $order ); + + $taxable_address = array( + self::get_base_country(), + self::get_base_state(), + self::get_base_postcode(), + self::get_base_city(), + ); + + if ( ! $order ) { + return $taxable_address; + } + + $tax_based_on = get_option( 'woocommerce_tax_based_on' ); + + if ( is_a( $order, 'WC_Order_Refund' ) ) { + $order = wc_get_order( $order->get_parent_id() ); + + if ( ! $order ) { + return $taxable_address; + } + } + + /** + * Shipping address data does not exist + */ + if ( 'shipping' === $tax_based_on && ! $order->get_shipping_country() ) { + $tax_based_on = 'billing'; + } + + $is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $order->get_meta( 'is_vat_exempt' ), $order ); + + /** + * In case the order is a VAT exempt, calculate net prices based on taxes from base country. + */ + if ( $is_vat_exempt ) { + $tax_based_on = 'base'; + } + + $country = 'shipping' === $tax_based_on ? $order->get_shipping_country() : $order->get_billing_country(); + + if ( 'base' !== $tax_based_on && ! empty( $country ) ) { + $taxable_address = array( + $country, + 'billing' === $tax_based_on ? $order->get_billing_state() : $order->get_shipping_state(), + 'billing' === $tax_based_on ? $order->get_billing_postcode() : $order->get_shipping_postcode(), + 'billing' === $tax_based_on ? $order->get_billing_city() : $order->get_shipping_city(), + ); + } + + return $taxable_address; + } + + public static function get_taxable_location() { + $is_admin_order_request = self::is_admin_order_request(); + + if ( $is_admin_order_request ) { + $taxable_address = array( + self::get_base_country(), + self::get_base_state(), + self::get_base_postcode(), + self::get_base_city(), + ); + + if ( isset( $_POST['order_id'] ) && ( $order = wc_get_order( absint( $_POST['order_id'] ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $taxable_address = self::get_order_taxable_location( $order ); + } + + return $taxable_address; + } else { + return \WC_Tax::get_tax_location(); + } + } + + public static function is_admin_order_ajax_request() { + $order_actions = array( 'woocommerce_calc_line_taxes', 'woocommerce_save_order_items', 'add_coupon_discount', 'refund_line_items', 'delete_refund' ); + + return isset( $_POST['action'], $_POST['order_id'] ) && ( strstr( wc_clean( wp_unslash( $_POST['action'] ) ), '_order_' ) || in_array( wc_clean( wp_unslash( $_POST['action'] ) ), $order_actions, true ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + public static function is_admin_order_request() { + return is_admin() && current_user_can( 'edit_shop_orders' ) && self::is_admin_order_ajax_request(); + } + + protected static function is_rest_api_request() { + if ( function_exists( 'WC' ) ) { + $wc = WC(); + + if ( is_callable( array( $wc, 'is_rest_api_request' ) ) ) { + return $wc->is_rest_api_request(); + } + } + + return false; + } + + protected static function get_current_request_value( $key ) { + $value = null; + $is_admin_order_request = self::is_admin_order_request(); + + if ( $is_admin_order_request ) { + if ( $order = wc_get_order( absint( $_POST['order_id'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated + $getter = "get_{$key}"; + + if ( is_callable( array( $order, $getter ) ) ) { + $value = $order->{ $getter }(); + } + } + } elseif ( did_action( 'woocommerce_checkout_update_order_review' ) ) { + if ( in_array( + $key, + array( + 'billing_country', + 'billing_state', + 'billing_postcode', + 'billing_city', + 'billing_address_1', + 'billing_address_2', + 'shipping_country', + 'shipping_state', + 'shipping_postcode', + 'shipping_city', + 'shipping_address_1', + 'shipping_address_2', + ), + true + ) ) { + $customer = WC()->customer; + $getter = "get_{$key}"; + + if ( $customer && is_callable( array( $customer, $getter ) ) ) { + $value = $customer->{ $getter }(); + } + } elseif ( isset( $_POST['post_data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $posted = array(); + + if ( is_string( $_POST['post_data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + parse_str( $_POST['post_data'], $posted ); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $posted = wc_clean( wp_unslash( $posted ) ); + } elseif ( is_array( $_POST['post_data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $posted = wc_clean( wp_unslash( $_POST['post_data'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + $value = isset( $posted[ $key ] ) ? $posted[ $key ] : null; + + /** + * Do only allow retrieving shipping-related data in case shipping address is activated + */ + if ( 'shipping_' === substr( $key, 0, 9 ) ) { + if ( ! isset( $posted['ship_to_different_address'] ) || ! $posted['ship_to_different_address'] || wc_ship_to_billing_address_only() ) { + return self::get_current_request_value( str_replace( 'shipping_', 'billing_', $key ) ); + } + } + } + } else { + $getter = "get_{$key}"; + $customer = WC()->customer; + + if ( $customer && is_callable( array( $customer, $getter ) ) ) { + $value = $customer->{ $getter }(); + } + } + + return apply_filters( 'woocommerce_eu_tax_helper_current_request_data', $value, $key ); + } + + public static function current_request_is_b2b() { + $is_admin_order_request = self::is_admin_order_request(); + $company = false; + + if ( $is_admin_order_request ) { + if ( $order = wc_get_order( absint( $_POST['order_id'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated + $company = $order->get_billing_company(); + + if ( $order->has_shipping_address() ) { + $company = $order->get_shipping_company(); + } + } + } else { + $use_shipping_address = self::get_current_request_value( 'shipping_address_1' ) || self::get_current_request_value( 'shipping_address_2' ); + $company = self::get_current_request_value( 'billing_company' ); + + if ( $use_shipping_address ) { + $company = self::get_current_request_value( 'shipping_company' ); + } + } + + return apply_filters( 'woocommerce_eu_tax_helper_current_request_is_b2b', ! empty( $company ) ); + } + + public static function current_request_has_vat_exempt() { + $is_admin_order_request = self::is_admin_order_request(); + $is_vat_exempt = false; + + if ( $is_admin_order_request ) { + if ( $order = wc_get_order( absint( $_POST['order_id'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated + $is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $order->get_meta( 'is_vat_exempt' ), $order ); + } + } elseif ( WC()->customer && WC()->customer->is_vat_exempt() ) { + $is_vat_exempt = true; + } + + return apply_filters( 'woocommerce_eu_tax_helper_current_request_has_vat_exempt', $is_vat_exempt ); + } + + public static function get_base_country() { + if ( WC()->countries ) { + $base_country = WC()->countries->get_base_country(); + } else { + $base_country = wc_get_base_location()['country']; + } + + return apply_filters( 'woocommerce_eu_tax_helper_base_country', $base_country ); + } + + public static function get_base_postcode() { + $base_postcode = ''; + + if ( WC()->countries ) { + $base_postcode = WC()->countries->get_base_postcode(); + } + + return apply_filters( 'woocommerce_eu_tax_helper_base_postcode', $base_postcode ); + } + + public static function get_base_state() { + $base_state = ''; + + if ( WC()->countries ) { + $base_state = WC()->countries->get_base_state(); + } + + return apply_filters( 'woocommerce_eu_tax_helper_base_state', $base_state ); + } + + public static function get_base_city() { + $base_city = ''; + + if ( WC()->countries ) { + $base_city = WC()->countries->get_base_city(); + } + + return apply_filters( 'woocommerce_eu_tax_helper_base_city', $base_city ); + } + + /** + * Returns a list of EU countries except base country. + * + * @return string[] + */ + public static function get_non_base_eu_countries( $include_gb = false ) { + $countries = self::get_eu_vat_countries(); + + /** + * Include GB to allow Northern Ireland + */ + if ( $include_gb && ! in_array( 'GB', $countries, true ) ) { + $countries = array_merge( $countries, array( 'GB' ) ); + } + + $base_country = self::get_base_country(); + $countries = array_diff( $countries, array( $base_country ) ); + + return $countries; + } + + public static function country_supports_eu_vat( $country, $postcode = '' ) { + return self::is_eu_vat_country( $country, $postcode ); + } + + public static function import_oss_tax_rates( $tax_class_slug_names = array() ) { + self::import_tax_rates_internal( true, $tax_class_slug_names ); + } + + public static function import_default_tax_rates( $tax_class_slug_names = array() ) { + self::import_tax_rates_internal( false, $tax_class_slug_names ); + } + + public static function import_tax_rates( $tax_class_slug_names = array() ) { + self::import_tax_rates_internal( self::oss_procedure_is_enabled(), $tax_class_slug_names ); + } + + protected static function parse_tax_class_slug_names( $tax_class_slug_names = array() ) { + return wp_parse_args( + $tax_class_slug_names, + array( + 'reduced' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_reduced_name', __( 'Reduced rate', 'woocommerce' ) ), // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + 'greater-reduced' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_greater_reduced_name', _x( 'Greater reduced rate', 'tax-helper-tax-class-name', 'woocommerce-germanized' ) ), + 'super-reduced' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_super_reduced_name', _x( 'Super reduced rate', 'tax-helper-tax-class-name', 'woocommerce-germanized' ) ), + 'zero' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_zero_name', __( 'Zero rate', 'woocommerce' ) ), // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + ) + ); + } + + public static function maybe_create_tax_classes( $tax_class_slug_names = array() ) { + $tax_class_slugs = self::get_tax_class_slugs( $tax_class_slug_names ); + $tax_class_slug_names = self::parse_tax_class_slug_names( $tax_class_slug_names ); + + foreach ( $tax_class_slugs as $tax_class_type => $class ) { + /** + * Maybe create missing tax classes + */ + if ( false === $class ) { + switch ( $tax_class_type ) { + case 'reduced': + \WC_Tax::create_tax_class( $tax_class_slug_names['reduced'] ); + break; + case 'greater-reduced': + \WC_Tax::create_tax_class( $tax_class_slug_names['greater-reduced'] ); + break; + case 'super-reduced': + \WC_Tax::create_tax_class( $tax_class_slug_names['super-reduced'] ); + break; + case 'zero': + \WC_Tax::create_tax_class( $tax_class_slug_names['zero'] ); + break; + } + } + } + } + + public static function generate_tax_rates( $is_oss = true, $tax_class_slug_names = array(), $eu_rates = array(), $add_zero_rates = true ) { + self::clear_cache(); + + $tax_class_slugs = self::get_tax_class_slugs( $tax_class_slug_names ); + $tax_class_slug_names = self::parse_tax_class_slug_names( $tax_class_slug_names ); + $eu_rates = empty( $eu_rates ) ? self::get_eu_tax_rates() : $eu_rates; + + self::maybe_create_tax_classes( $tax_class_slug_names ); + + $tax_rates = array(); + + foreach ( $tax_class_slugs as $tax_class_type => $class ) { + $new_rates = array(); + + if ( 'zero' === $tax_class_type ) { + if ( $add_zero_rates ) { + $new_rates = array( + array( + 'country' => '*', + 'rate' => 0.0, + 'name' => '', + ), + ); + } + } else { + foreach ( $eu_rates as $country => $rates_data ) { + /** + * Use base country rates in case OSS is disabled + */ + if ( ! $is_oss ) { + $base_country = self::get_base_country(); + + if ( isset( $eu_rates[ $base_country ] ) ) { + /** + * In case the country includes multiple rules (e.g. postcode exempts) by default + * do only use the last rule (which does not include exempts) to construct non-base country tax rules. + */ + if ( $base_country !== $country ) { + $base_country_base_rate = array_values( array_slice( $eu_rates[ $base_country ], - 1 ) )[0]; + + foreach ( $rates_data as $key => $rate_data ) { + $rates_data[ $key ] = array_replace_recursive( $rate_data, $base_country_base_rate ); + + foreach ( $tax_class_slugs as $tmp_class_type => $class_data ) { + /** + * Do not include tax classes which are not supported by the base country. + */ + if ( isset( $rates_data[ $key ][ $tmp_class_type ] ) && ! isset( $base_country_base_rate[ $tmp_class_type ] ) ) { + unset( $rates_data[ $key ][ $tmp_class_type ] ); + } elseif ( isset( $rates_data[ $key ][ $tmp_class_type ] ) ) { + /** + * Replace tax class data with base data to make sure that reduced + * classes have the same dimensions + */ + $rates_data[ $key ][ $tmp_class_type ] = $base_country_base_rate[ $tmp_class_type ]; + + /** + * In case this is an exempt make sure to replace with zero tax rates + */ + if ( isset( $rate_data['is_exempt'] ) && $rate_data['is_exempt'] ) { + if ( is_array( $rates_data[ $key ][ $tmp_class_type ] ) ) { + foreach ( $rates_data[ $key ][ $tmp_class_type ] as $k => $rate ) { + $rates_data[ $key ][ $tmp_class_type ][ $k ] = 0; + } + } else { + $rates_data[ $key ][ $tmp_class_type ] = 0; + } + } + } + } + } + } + } else { + continue; + } + } + + /** + * Each country may contain multiple tax rates + */ + foreach ( $rates_data as $rates ) { + $rates = wp_parse_args( + $rates, + array( + 'name' => '', + 'postcodes' => array(), + 'reduced' => array(), + ) + ); + + if ( ! empty( $rates['postcode'] ) ) { + foreach ( $rates['postcode'] as $postcode ) { + $tax_rate = self::get_single_tax_rate_data( $tax_class_type, $rates, $country, $postcode ); + + if ( false !== $tax_rate ) { + $new_rates[] = $tax_rate; + } + } + } else { + $tax_rate = self::get_single_tax_rate_data( $tax_class_type, $rates, $country ); + + if ( false !== $tax_rate ) { + $new_rates[] = $tax_rate; + } + } + } + } + } + + $tax_rates[ $tax_class_type ] = array( + 'tax_class' => $class, + 'rates' => $new_rates, + ); + } + + return $tax_rates; + } + + protected static function import_tax_rates_internal( $is_oss = true, $tax_class_slug_names = array() ) { + $tax_rates = self::generate_tax_rates( $is_oss, $tax_class_slug_names ); + + foreach ( $tax_rates as $tax_class_type => $tax_rate_data ) { + $class = $tax_rate_data['tax_class']; + $rates = $tax_rate_data['rates']; + + self::import_rates( $rates, $class, $tax_class_type ); + } + } + + private static function get_single_tax_rate_data( $tax_class_type, $rates, $country, $postcode = '' ) { + $rates = wp_parse_args( + $rates, + array( + 'name' => '', + 'reduced' => array(), + ) + ); + + $single_rate = array( + 'name' => $rates['name'], + 'rate' => false, + 'country' => $country, + 'postcode' => $postcode, + ); + + switch ( $tax_class_type ) { + case 'greater-reduced': + if ( count( $rates['reduced'] ) > 1 ) { + $single_rate['rate'] = $rates['reduced'][1]; + } + break; + case 'reduced': + if ( ! empty( $rates['reduced'] ) ) { + $single_rate['rate'] = $rates['reduced'][0]; + } + break; + default: + if ( isset( $rates[ $tax_class_type ] ) ) { + $single_rate['rate'] = $rates[ $tax_class_type ]; + } + break; + } + + if ( false === $single_rate['rate'] ) { + return false; + } + + return $single_rate; + } + + protected static function clear_cache() { + $cache_key = \WC_Cache_Helper::get_cache_prefix( 'taxes' ) . 'eu_tax_helper_tax_class_slugs'; + + wp_cache_delete( $cache_key, 'taxes' ); + } + + public static function get_tax_class_slugs( $tax_class_slug_names = array() ) { + $tax_class_slug_names = self::parse_tax_class_slug_names( $tax_class_slug_names ); + $cache_key = \WC_Cache_Helper::get_cache_prefix( 'taxes' ) . 'eu_tax_helper_tax_class_slugs'; + $slugs = wp_cache_get( $cache_key, 'taxes' ); + + if ( false === $slugs ) { + $reduced_tax_class = false; + $greater_reduced_tax_class = false; + $super_reduced_tax_class = false; + $zero_tax_class = false; + $tax_classes = \WC_Tax::get_tax_class_slugs(); + + /** + * Try to determine the reduced tax rate class + */ + foreach ( $tax_classes as $slug ) { + if ( strstr( $slug, 'virtual' ) ) { + continue; + } + + if ( ! $greater_reduced_tax_class && strstr( $slug, sanitize_title( 'Greater reduced rate' ) ) ) { + $greater_reduced_tax_class = $slug; + } elseif ( ! $greater_reduced_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['greater-reduced'] ) ) ) { + $greater_reduced_tax_class = $slug; + } elseif ( ! $super_reduced_tax_class && strstr( $slug, sanitize_title( 'Super reduced rate' ) ) ) { + $super_reduced_tax_class = $slug; + } elseif ( ! $super_reduced_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['super-reduced'] ) ) ) { + $super_reduced_tax_class = $slug; + } elseif ( ! $reduced_tax_class && strstr( $slug, sanitize_title( 'Reduced rate' ) ) ) { + $reduced_tax_class = $slug; + } elseif ( ! $reduced_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['reduced'] ) ) ) { // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + $reduced_tax_class = $slug; + } elseif ( ! $reduced_tax_class && strstr( $slug, 'reduced' ) ) { + $reduced_tax_class = $slug; + } elseif ( ! $zero_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['zero'] ) ) ) { + $zero_tax_class = $slug; + } elseif ( ! $zero_tax_class && strstr( $slug, 'zero' ) ) { + $zero_tax_class = $slug; + } + } + + $slugs = array( + 'reduced' => $reduced_tax_class, + 'greater-reduced' => $greater_reduced_tax_class, + 'super-reduced' => $super_reduced_tax_class, + 'standard' => '', + 'zero' => $zero_tax_class, + ); + + wp_cache_set( $cache_key, $slugs, 'taxes' ); + } + + return apply_filters( 'woocommerce_eu_tax_helper_tax_rate_class_slugs', $slugs ); + } + + public static function get_tax_type_by_country_rate( $rate_percentage, $country ) { + $country = strtoupper( $country ); + + /** + * Map northern ireland to GB + */ + if ( 'XI' === $country ) { + $country = 'GB'; + } + + $eu_rates = self::get_eu_tax_rates(); + $tax_type = 'standard'; + $rate_percentage = (float) $rate_percentage; + + if ( array_key_exists( $country, $eu_rates ) ) { + $rates = $eu_rates[ $country ]; + + foreach ( $rates as $rate ) { + foreach ( $rate as $tax_rate_type => $tax_rate_percent ) { + if ( ( is_array( $tax_rate_percent ) && in_array( $rate_percentage, $tax_rate_percent, true ) ) || (float) $tax_rate_percent === $rate_percentage ) { + $tax_type = $tax_rate_type; + break; + } + } + } + } + + return apply_filters( 'woocommerce_eu_tax_helper_country_rate_tax_type', $tax_type, $country, $rate_percentage ); + } + + public static function get_eu_tax_rate_changesets( $apply_postcode_exempts = true ) { + $changesets = array( + '2024-01-01' => array( + 'CZ' => array( + array( + 'standard' => 21, + 'reduced' => array( 12 ), + ), + ), + 'EE' => array( + array( + 'standard' => 22, + 'reduced' => array( 9 ), + ), + ), + ), + '2024-01-02' => array( + 'LU' => array( + array( + 'standard' => 17, + 'reduced' => array( 8 ), + 'super-reduced' => 3, + ), + ), + ), + ); + + if ( $apply_postcode_exempts ) { + foreach ( $changesets as $date => $tax_rates ) { + $changesets[ $date ] = self::apply_vat_postcode_exempts( $tax_rates ); + } + } + + return $changesets; + } + + public static function get_eu_tax_rates( $apply_changesets = true ) { + /** + * @see https://europa.eu/youreurope/business/taxation/vat/vat-rules-rates/index_en.htm + * + * Include Great Britain to allow including Norther Ireland + */ + $rates = array( + 'AT' => array( + array( + 'standard' => 20, + 'reduced' => array( 10, 13 ), + ), + ), + 'BE' => array( + array( + 'standard' => 21, + 'reduced' => array( 6, 12 ), + ), + ), + 'BG' => array( + array( + 'standard' => 20, + 'reduced' => array( 9 ), + ), + ), + 'CY' => array( + array( + 'standard' => 19, + 'reduced' => array( 5, 9 ), + ), + ), + 'CZ' => array( + array( + 'standard' => 21, + 'reduced' => array( 12 ), + ), + ), + 'DE' => array( + array( + 'standard' => 19, + 'reduced' => array( 7 ), + ), + ), + 'DK' => array( + array( + 'standard' => 25, + 'reduced' => array(), + ), + ), + 'EE' => array( + array( + 'standard' => 22, + 'reduced' => array( 9 ), + ), + ), + 'GR' => array( + array( + 'standard' => 24, + 'reduced' => array( 6, 13 ), + ), + ), + 'ES' => array( + array( + 'standard' => 21, + 'reduced' => array( 10 ), + 'super-reduced' => 4, + ), + ), + 'FI' => array( + array( + 'standard' => 24, + 'reduced' => array( 10, 14 ), + ), + ), + 'FR' => array( + array( + 'standard' => 20, + 'reduced' => array( 5.5, 10 ), + 'super-reduced' => 2.1, + ), + ), + 'HR' => array( + array( + 'standard' => 25, + 'reduced' => array( 5, 13 ), + ), + ), + 'HU' => array( + array( + 'standard' => 27, + 'reduced' => array( 5, 18 ), + ), + ), + 'IE' => array( + array( + 'standard' => 23, + 'reduced' => array( 9, 13.5 ), + 'super-reduced' => 4.8, + ), + ), + 'IT' => array( + array( + 'standard' => 22, + 'reduced' => array( 5, 10 ), + 'super-reduced' => 4, + ), + ), + 'LT' => array( + array( + 'standard' => 21, + 'reduced' => array( 5, 9 ), + ), + ), + 'LU' => array( + array( + 'standard' => 17, + 'reduced' => array( 8 ), + 'super-reduced' => 3, + ), + ), + 'LV' => array( + array( + 'standard' => 21, + 'reduced' => array( 12, 5 ), + ), + ), + 'MC' => array( + array( + 'standard' => 20, + 'reduced' => array( 5.5, 10 ), + 'super-reduced' => 2.1, + ), + ), + 'MT' => array( + array( + 'standard' => 18, + 'reduced' => array( 5, 7 ), + ), + ), + 'NL' => array( + array( + 'standard' => 21, + 'reduced' => array( 9 ), + ), + ), + 'PL' => array( + array( + 'standard' => 23, + 'reduced' => array( 5, 8 ), + ), + ), + 'PT' => array( + array( + // Madeira + 'postcode' => array( '90*', '91*', '92*', '93*', '94*' ), + 'standard' => 22, + 'reduced' => array( 5, 12 ), + 'name' => _x( 'Madeira', 'tax-helper', 'woocommerce-germanized' ), + ), + array( + // Acores + 'postcode' => array( '95*', '96*', '97*', '98*', '99*' ), + 'standard' => 18, + 'reduced' => array( 4, 9 ), + 'name' => _x( 'Acores', 'tax-helper', 'woocommerce-germanized' ), + ), + array( + 'standard' => 23, + 'reduced' => array( 6, 13 ), + ), + ), + 'RO' => array( + array( + 'standard' => 19, + 'reduced' => array( 5, 9 ), + ), + ), + 'SE' => array( + array( + 'standard' => 25, + 'reduced' => array( 6, 12 ), + ), + ), + 'SI' => array( + array( + 'standard' => 22, + 'reduced' => array( 9.5 ), + ), + ), + 'SK' => array( + array( + 'standard' => 20, + 'reduced' => array( 10 ), + ), + ), + 'GB' => array( + array( + 'standard' => 20, + 'reduced' => array( 5 ), + 'postcode' => array( 'BT*' ), + 'name' => _x( 'Northern Ireland', 'tax-helper', 'woocommerce-germanized' ), + ), + ), + ); + + if ( $apply_changesets ) { + $changesets = self::get_eu_tax_rate_changesets( false ); + $today = new \WC_DateTime(); + + foreach ( $changesets as $date => $changeset ) { + $changeset_date = wc_string_to_datetime( $date . ' 00:00:00' ); + + if ( $today >= $changeset_date ) { + foreach ( $changeset as $country => $tax_rates ) { + $rates[ $country ] = $tax_rates; + } + } + } + } + + $rates = self::apply_vat_postcode_exempts( $rates ); + + return $rates; + } + + protected static function apply_vat_postcode_exempts( $rates ) { + foreach ( self::get_vat_postcode_exemptions_by_country() as $country => $exempt_postcodes ) { + if ( array_key_exists( $country, $rates ) ) { + $default_rate = array_values( $rates[ $country ] )[0]; + + $postcode_exempt = array( + 'postcode' => $exempt_postcodes, + 'standard' => 0, + 'reduced' => count( $default_rate['reduced'] ) > 1 ? array( 0, 0 ) : array( 0 ), + 'name' => _x( 'Exempt', 'tax-helper-rate-import', 'woocommerce-germanized' ), + 'is_exempt' => true, + ); + + if ( array_key_exists( 'super-reduced', $default_rate ) ) { + $postcode_exempt['super-reduced'] = 0; + } + + // Prepend before other tax rates + $rates[ $country ] = array_merge( array( $postcode_exempt ), $rates[ $country ] ); + } + } + + return $rates; + } + + /** + * @param \stdClass $rate + * + * @return bool + */ + public static function tax_rate_is_northern_ireland( $rate ) { + if ( 'GB' === $rate->tax_rate_country && isset( $rate->postcode ) && ! empty( $rate->postcode ) ) { + foreach ( $rate->postcode as $postcode ) { + if ( self::is_northern_ireland( $rate->tax_rate_country, $postcode ) ) { + return true; + } + } + } + + return false; + } + + public static function delete_tax_rates_by_country( $country ) { + global $wpdb; + + $country = strtoupper( $country ); + + foreach ( self::get_tax_class_slugs() as $tax_class ) { + $tax_rates = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}woocommerce_tax_rates` WHERE `tax_rate_class` = %s AND `tax_rate_country` = %s;", $tax_class, $country ) ); + + foreach ( $tax_rates as $tax_rate ) { + \WC_Tax::_delete_tax_rate( $tax_rate->tax_rate_id ); + } + } + } + + public static function import_rates( $rates, $tax_class = '', $tax_class_type = '', $clean = true ) { + $eu_countries = self::get_eu_vat_countries(); + + /** + * Delete EU tax rates and make sure tax rate locations are deleted too + */ + if ( $clean ) { + foreach ( \WC_Tax::get_rates_for_tax_class( $tax_class ) as $rate_id => $rate ) { + if ( in_array( $rate->tax_rate_country, $eu_countries, true ) || self::tax_rate_is_northern_ireland( $rate ) || ( 'GB' === $rate->tax_rate_country && 'GB' !== self::get_base_country() ) ) { + \WC_Tax::_delete_tax_rate( $rate_id ); + } elseif ( 'zero' === $tax_class_type && empty( $rate->tax_rate_country ) ) { + \WC_Tax::_delete_tax_rate( $rate_id ); + } + } + } + + $count = 0; + + foreach ( $rates as $rate ) { + $rate = wp_parse_args( + $rate, + array( + 'rate' => 0, + 'country' => '', + 'postcode' => '', + 'name' => '', + ) + ); + + $iso = wc_strtoupper( $rate['country'] ); + $vat_desc = '*' !== $iso ? $iso : ''; + + if ( ! empty( $rate['name'] ) ) { + $vat_desc = ( ! empty( $vat_desc ) ? $vat_desc . ' ' : '' ) . $rate['name']; + } + + $vat_rate = wc_format_decimal( $rate['rate'], false, true ); + + $tax_rate_name = apply_filters( 'woocommerce_eu_tax_helper_import_tax_rate_name', sprintf( _x( 'VAT %1$s %% %2$s', 'tax-helper-rate-import', 'woocommerce-germanized' ), $vat_rate, $vat_desc ), $rate['rate'], $iso, $tax_class, $rate ); + + $_tax_rate = array( + 'tax_rate_country' => $iso, + 'tax_rate_state' => '', + 'tax_rate' => (string) number_format( (float) wc_clean( $rate['rate'] ), 4, '.', '' ), + 'tax_rate_name' => $tax_rate_name, + 'tax_rate_compound' => 0, + 'tax_rate_priority' => 1, + 'tax_rate_order' => $count++, + 'tax_rate_shipping' => ( strstr( $tax_class, 'virtual' ) ? 0 : 1 ), + 'tax_rate_class' => \WC_Tax::format_tax_rate_class( $tax_class ), + ); + + $new_tax_rate_id = \WC_Tax::_insert_tax_rate( $_tax_rate ); + + if ( ! empty( $rate['postcode'] ) ) { + \WC_Tax::_update_tax_rate_postcodes( $new_tax_rate_id, $rate['postcode'] ); + } + } + } + + /** + * @param $rate_id + * @param \WC_Order $order + */ + public static function get_tax_rate_percent( $rate_id, $order ) { + $taxes = $order->get_taxes(); + $percentage = null; + + foreach ( $taxes as $tax ) { + if ( (int) $tax->get_rate_id() === (int) $rate_id ) { + if ( is_callable( array( $tax, 'get_rate_percent' ) ) ) { + $percentage = $tax->get_rate_percent(); + } + } + } + + /** + * WC_Order_Item_Tax::get_rate_percent returns null by default. + * Fallback to global tax rates (DB) in case the percentage is not available within order data. + */ + if ( is_null( $percentage ) || '' === $percentage ) { + $rate_percentage = self::get_tax_rate_percentage( $rate_id ); + + if ( false !== $rate_percentage ) { + $percentage = $rate_percentage; + } + } + + if ( ! is_numeric( $percentage ) ) { + $percentage = 0; + } + + return $percentage; + } + + public static function get_tax_rate_percentage( $rate_id ) { + $percentage = false; + + if ( is_callable( array( 'WC_Tax', 'get_rate_percent_value' ) ) ) { + $percentage = \WC_Tax::get_rate_percent_value( $rate_id ); + } elseif ( is_callable( array( 'WC_Tax', 'get_rate_percent' ) ) ) { + $percentage = filter_var( \WC_Tax::get_rate_percent( $rate_id ), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION ); + } + + return $percentage; + } +} diff --git a/packages/woocommerce-germanized-dhl/assets/css/admin.scss b/packages/woocommerce-germanized-dhl/assets/css/admin.scss new file mode 100644 index 000000000..4ab852e27 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/admin.scss @@ -0,0 +1,62 @@ +.shipments-create-label { + .wc-gzd-shipment-im-additional-services { + p.label { + margin-top: 10px; + width: 100%; + display: block; + margin-bottom: 5px; + font-weight: bold; + } + } + + .wc-gzd-dhl-im-product-data { + margin-top: 2em; + min-width: 700px; + margin-left: -1rem !important; + margin-right: -1rem !important; + + .column { + padding-left: 1rem !important; + padding-right: 1rem !important; + + p:first-child { + margin-top: 1.5em !important; + } + } + + .wc-gzd-dhl-im-product-price { + background: #ffd633; + border-radius: 4px; + padding: .5em 1em; + + .amount { + font-size: 18px; + font-weight: bold; + } + .price-suffix { + display: block; + font-size: 11px; + line-height: 15px; + } + } + + .col-dimensions { + color: #999; + } + + .col-preview { + .image-preview { + img { + height: auto; + max-height: 140px; + } + } + } + + .wc-gzd-dhl-im-product-information-text, .wc-gzd-dhl-im-product-description { + font-size: 11px; + color: #999; + line-height: 1.5em; + } + } +} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.scss b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.scss new file mode 100644 index 000000000..9a7c9833b --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.scss @@ -0,0 +1,218 @@ +#dhl-parcel-finder-wrapper { + backface-visibility: hidden; + transform: translateZ(0); + width: 100%; + height: 100%; + left: 0; + position: fixed; + top: 0; + z-index: 99992; + visibility: hidden; + + * { + box-sizing: border-box; + } + + #dhl-parcel-finder-bg-overlay { + background: #1e1e1e; + opacity: 0; + transition-duration: inherit; + transition-property: opacity; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + } + + &.open { + visibility: visible; + + #dhl-parcel-finder-bg-overlay { + opacity: .87; + transition-timing-function: cubic-bezier(.22,.61,.36,1); + } + } + + #dhl-parcel-finder-inner { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + overflow: hidden; + z-index: 99994; + } + + #dhl-parcel-finder-inner-wrapper { + padding: 6px 6px 0; + display: block; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + height: 100%; + left: 0; + outline: none; + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + text-align: center; + top: 0; + transition-property: transform,opacity; + white-space: normal; + width: 100%; + z-index: 99994; + + &::before { + content: ""; + display: inline-block; + height: 100%; + margin-right: -.25em; + vertical-align: middle; + width: 0; + } + + #dhl-parcel-finder { + width: 95%; + height: 95%; + display: inline-block; + background: #fff; + margin: 0 0 6px; + max-width: 100%; + overflow: auto; + padding: 34px; + position: relative; + text-align: left; + vertical-align: middle; + } + } + + .dhl-parcel-finder-close { + background: transparent; + border: 0; + border-radius: 0; + color: #555; + cursor: pointer; + height: 44px; + margin: 0; + padding: 6px; + position: absolute; + right: 0; + top: 0; + width: 44px; + z-index: 10; + + svg { + fill: transparent; + opacity: .8; + stroke: currentColor; + stroke-width: 1.5; + transition: stroke .1s; + } + } + + #dhl-parcel-finder-map { + width: 100%; + height: 85%; + position: relative !important; + + #parcel-content { + + #bodyContent { + line-height: 1.5em; + } + + address { + margin-bottom: 0; + } + + .parcel-title { + padding-top: 0; + margin-bottom: .5em; + } + + .parcel-subtitle { + font-size: 0.8125rem; + color: #767676; + font-weight: 800; + letter-spacing: 0.15em; + text-transform: uppercase; + padding: 1em 0 0; + } + + .dhl-parcelshop-select-btn { + width: 100%; + margin-top: 10px; + } + } + } + + form#dhl-parcel-finder-form { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + + .form-field { + margin-right: 1.5em; + + &.large { + min-width: 20%; + } + + &.finder-pickup-type { + margin-right: 25px; + min-width: 150px; + display: flex; + align-items: center; + + &.hidden { + display: none; + } + + .icon { + width: 40px; + height: 40px; + display: inline-block; + background-repeat: no-repeat; + background-size: contain; + margin-left: 10px; + } + } + + &#dhl-search-button { + text-align: right; + margin-right: 0; + + .button { + width: 100%; + margin: 0; + } + } + } + } +} + +@media( max-width: 700px ) { + #dhl-parcel-finder-wrapper { + form#dhl-parcel-finder-form { + .form-field { + width: 100%; + margin-right: 0; + + &.packstation, &.parcelshop { + width: auto; + margin-right: 1.5em; + } + + &#dhl-search-button { + text-align: left; + + .button { + width: 100%; + margin: 0; + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/preferred-services.scss b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.scss new file mode 100644 index 000000000..72123a694 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.scss @@ -0,0 +1,230 @@ +#tiptip_holder { + display: none; + z-index: 8675309; + position: absolute; + top: 0; + + /*rtl:ignore*/ + left: 0; + + &.tip_top { + padding-bottom: 5px; + + #tiptip_arrow_inner { + margin-top: -7px; + margin-left: -6px; + border-top-color: #333; + } + } + + &.tip_bottom { + padding-top: 5px; + + #tiptip_arrow_inner { + margin-top: -5px; + margin-left: -6px; + border-bottom-color: #333; + } + } + + &.tip_right { + padding-left: 5px; + + #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -5px; + border-right-color: #333; + } + } + + &.tip_left { + padding-right: 5px; + + #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -7px; + border-left-color: #333; + } + } +} + +#tiptip_content { + color: #fff; + font-size: 0.8em; + max-width: 150px; + background: #333; + text-align: center; + border-radius: 3px; + padding: 0.618em 1em; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + + code { + padding: 1px; + background: #888; + } +} + +#tiptip_arrow, +#tiptip_arrow_inner { + position: absolute; + border-color: transparent; + border-style: solid; + border-width: 6px; + height: 0; + width: 0; +} + +.dhl-preferred-service-content { + margin-top: 1em; + + .dhl-hidden { + display: none; + } + + .dhl-preferred-service-cost { + font-size: .9em; + } + + .dhl-preferred-service-item { + margin-bottom: 1em; + + .dhl-preferred-service-logo { + img { + margin: 0; + padding: 0; + max-height: 100px; + max-width: 100px; + background: #FFCC00; + } + margin-bottom: 1em; + } + + .dhl-preferred-service-title { + font-weight: bold; + font-size: 1em; + margin-bottom: .5em; + } + + .dhl-preferred-service-cost { + margin-bottom: .5em; + } + + .dhl-preferred-service-desc { + font-size: .9em; + margin-bottom: .5em; + } + + .dhl-preferred-service-times { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0; + padding: 0; + + li { + flex-basis: 10%; + display: inline-block; + text-align: center; + padding: 10px 0 0; + margin: 0 8px 8px 0; + background-color: #e3e3e3; + + label { + position: relative; + display: flex; + flex-direction: column; + flex-wrap: wrap; + padding: 5px 10px; + font-size: .9em; + font-weight: bold; + background-color: #eef4f2; + cursor: pointer; + margin: 0; + color: #5f7285; + + .dhl-preferred-time-title { + font-size: 1.2em; + } + } + + input[type=radio] { + opacity: 0; + width: 1px; + height: 1px; + position: absolute; + + &:checked ~ label { + background-color: #FFCC00; + } + } + } + + &.dhl-preferred-service-time { + li { + flex-grow: inherit; + flex-basis: inherit; + + label { + .dhl-preferred-time-title { + font-size: 1em; + } + } + } + } + } + + .dhl-preferred-service-data { + input[type=text] { + width: 100%; + margin-bottom: .5em; + } + } + + .woocommerce-help-tip { + background: #ffcc00; + display: inline-block; + font-size: 1em; + font-style: normal; + height: 18px; + padding: 3px; + margin-top: -5px; + margin-left: 5px; + border-radius: 50%; + line-height: 18px; + position: relative; + vertical-align: middle; + width: 18px; + + &::after { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: center; + cursor: help; + content: "?"; + } + } + + .dhl-preferred-location-types, .dhl-preferred-delivery-types { + list-style: none; + margin: 0; + margin-bottom: 1em; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + + li { + margin-right: 1em; + } + } + + &.dhl-preferred-service-header { + .dhl-preferred-service-title { + font-size: 1.1em; + } + } + } +} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/img/dhl-official.png b/packages/woocommerce-germanized-dhl/assets/img/dhl-official.png new file mode 100755 index 000000000..18dab3cf6 Binary files /dev/null and b/packages/woocommerce-germanized-dhl/assets/img/dhl-official.png differ diff --git a/packages/woocommerce-germanized-dhl/assets/img/packstation.png b/packages/woocommerce-germanized-dhl/assets/img/packstation.png new file mode 100755 index 000000000..903d8388b Binary files /dev/null and b/packages/woocommerce-germanized-dhl/assets/img/packstation.png differ diff --git a/packages/woocommerce-germanized-dhl/assets/img/parcelshop.png b/packages/woocommerce-germanized-dhl/assets/img/parcelshop.png new file mode 100755 index 000000000..15fafae42 Binary files /dev/null and b/packages/woocommerce-germanized-dhl/assets/img/parcelshop.png differ diff --git a/packages/woocommerce-germanized-dhl/assets/img/post_office.png b/packages/woocommerce-germanized-dhl/assets/img/post_office.png new file mode 100755 index 000000000..097023dcd Binary files /dev/null and b/packages/woocommerce-germanized-dhl/assets/img/post_office.png differ diff --git a/packages/woocommerce-germanized-dhl/assets/img/wp-int-eu-preview.png b/packages/woocommerce-germanized-dhl/assets/img/wp-int-eu-preview.png new file mode 100644 index 000000000..a4438cd37 Binary files /dev/null and b/packages/woocommerce-germanized-dhl/assets/img/wp-int-eu-preview.png differ diff --git a/packages/woocommerce-germanized-dhl/assets/img/wp-int-preview.png b/packages/woocommerce-germanized-dhl/assets/img/wp-int-preview.png new file mode 100644 index 000000000..6ac0f7e34 Binary files /dev/null and b/packages/woocommerce-germanized-dhl/assets/img/wp-int-preview.png differ diff --git a/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/index.js b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/index.js new file mode 100644 index 000000000..59bb39b0d --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/index.js @@ -0,0 +1,2 @@ +import './slotfills/dom-watcher'; +import './slotfills/preferred-services'; \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/dom-watcher.js b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/dom-watcher.js new file mode 100644 index 000000000..2ce02ebb8 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/dom-watcher.js @@ -0,0 +1,39 @@ +import { ExperimentalOrderMeta } from '@woocommerce/blocks-checkout'; +import { registerPlugin } from '@wordpress/plugins'; +import { useEffect } from "@wordpress/element"; +import { dispatch, select } from '@wordpress/data'; +import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data'; + +const DomWatcher = ({ + extensions, + cart +}) => { + /** + * Use this little helper which sets the default checkout data in case it + * does not exist, e.g. the checkboxes block is missing to prevent extension errors. + * + * @see https://github.com/woocommerce/woocommerce-blocks/issues/11446 + */ + useEffect(() => { + const extensionsData = select( CHECKOUT_STORE_KEY ).getExtensionData(); + + if ( ! extensionsData.hasOwnProperty( 'woocommerce-gzd-dhl' ) ) { + dispatch( CHECKOUT_STORE_KEY ).__internalSetExtensionData( 'woocommerce-gzd-dhl', {} ); + } + }, [] ); + + return null; +}; + +const render = () => { + return ( + + + + ); +}; + +registerPlugin( 'woocommerce-gzd-dhl-checkout-order-meta', { + render, + scope: 'woocommerce-checkout', +} ); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/preferred-services.js b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/preferred-services.js new file mode 100644 index 000000000..8410c3c40 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/preferred-services.js @@ -0,0 +1,565 @@ +import { ExperimentalOrderShippingPackages } from '@woocommerce/blocks-checkout'; +import { registerPlugin } from '@wordpress/plugins'; +import { useEffect, useCallback, useState } from "@wordpress/element"; +import { useSelect, useDispatch, select, dispatch } from '@wordpress/data'; +import { extensionCartUpdate } from '@woocommerce/blocks-checkout'; +import classnames from 'classnames'; +import { getSetting } from '@woocommerce/settings'; +import { __, _x, sprintf } from '@wordpress/i18n'; +import { SVG } from '@wordpress/components'; +import _ from 'lodash'; +import { CART_STORE_KEY, CHECKOUT_STORE_KEY, PAYMENT_STORE_KEY, VALIDATION_STORE_KEY } from '@woocommerce/block-data'; +import { getCurrencyFromPriceResponse } from '@woocommerce/price-format'; +import { useDebouncedCallback, useDebounce } from 'use-debounce'; + +import { + __experimentalRadio as Radio, + __experimentalRadioGroup as RadioGroup, +} from 'wordpress-components'; + +import { + ValidatedTextInput, + ValidatedTextInputHandle, +} from '@woocommerce/blocks-checkout'; + +import './style.scss'; +import { + getSelectedShippingProviders, + hasShippingProvider, + hasPickupLocation, + RadioControlAccordion, + FormattedMonetaryAmount +} from '@woocommerceGzdShipments/blocks-checkout'; + +const getDhlCheckoutData = ( checkoutData ) => { + return checkoutData.hasOwnProperty( 'woocommerce-gzd-dhl' ) ? checkoutData['woocommerce-gzd-dhl'] : {}; +}; + +const setDhlCheckoutData = ( checkoutData ) => { + dispatch( CHECKOUT_STORE_KEY ).__internalSetExtensionData( 'woocommerce-gzd-dhl', checkoutData ); +}; + +const DhlPreferredDaySelect = ({ + preferredDays, + setPreferredOption, + preferredOptions, + preferredDayCost, + currency +}) => { + const preferredDay = preferredOptions.hasOwnProperty( 'preferred_day' ) ? preferredOptions['preferred_day'] : ''; + const costValue = parseInt( preferredDayCost, 10 ); + + return ( +
+

+ { _x( 'Choose a delivery day', 'dhl', 'woocommerce-germanized' ) } + + { costValue > 0 && + (+ ) + + } +

+
+ { preferredDays.map( ( preferred ) => { + const checked = preferredDay === preferred.date; + + return ( + { + setPreferredOption( 'preferred_day', preferred.date ); + } } + checked={ checked } + className={ classnames( + `wc-gzd-dhl-preferred-day`, + { + active: checked + } + ) } + > + + + { preferred.day } + + + { preferred.week_day } + + + + ); + } ) } +
+
+ ); +}; + +const DhlPreferredLocation = ( props ) => { + const { + setPreferredOption, + preferredOptions, + } = props; + + const location = preferredOptions.hasOwnProperty( 'preferred_location' ) ? preferredOptions['preferred_location'] : ''; + + return ( + <> + { _x( 'Choose a weather-protected and non-visible place on your property, where we can deposit the parcel in your absence.', 'dhl', 'woocommerce-germanized' ) } + + { + setPreferredOption( 'preferred_location', newValue ); + } } + /> + + ) +} + +const DhlPreferredNeighbor = ( props ) => { + const { + setPreferredOption, + preferredOptions, + } = props; + + const neighborName = preferredOptions.hasOwnProperty( 'preferred_location_neighbor_name' ) ? preferredOptions['preferred_location_neighbor_name'] : ''; + const neighborAddress = preferredOptions.hasOwnProperty( 'preferred_location_neighbor_address' ) ? preferredOptions['preferred_location_neighbor_address'] : ''; + + return ( + <> + { _x( 'Determine a person in your immediate neighborhood whom we can hand out your parcel in your absence. This person should live in the same building, directly opposite or next door.', 'dhl', 'woocommerce-germanized' ) } + + { + setPreferredOption( 'preferred_location_neighbor_name', newValue ); + } } + /> + { + setPreferredOption( 'preferred_location_neighbor_address', newValue ); + } } + /> + + + ) +} + +const DhlPreferredLocationSelect = ( props ) => { + const { + setPreferredOption, + preferredOptions, + preferredNeighborEnabled, + preferredLocationEnabled + } = props; + + const preferredLocationType = preferredOptions.hasOwnProperty( 'preferred_location_type' ) ? preferredOptions['preferred_location_type'] : ''; + + const options = [ + { + value: '', + label: _x( 'None', 'dhl location context', 'woocommerce-germanized' ), + content: '', + }, + preferredLocationEnabled ? + { + value: 'place', + label: _x( 'Drop-off location', 'dhl', 'woocommerce-germanized' ), + content: ( + + ), + } : {}, + preferredNeighborEnabled ? + { + value: 'neighbor', + label: _x( 'Neighbor', 'dhl', 'woocommerce-germanized' ), + content: ( + + ), + } : {}, + ].filter( value => Object.keys( value ).length !== 0 ); + + return ( +
+

{ _x( 'Choose a preferred location', 'dhl', 'woocommerce-germanized' ) }

+ { + setPreferredOption( 'preferred_location_type', value ); + } } + options={ options } + /> +
+ ); +}; + +const DhlCdpOptions = ( + props +) => { + const { preferredOptions, setPreferredOption, homeDeliveryCost, currency } = props; + const preferredDeliveryType = preferredOptions.hasOwnProperty( 'preferred_delivery_type' ) ? preferredOptions['preferred_delivery_type'] : ''; + const costValue = parseInt( homeDeliveryCost, 10 ); + + const options = [ + { + value: 'cdp', + label: _x( 'Shop', 'dhl', 'woocommerce-germanized' ), + content: ( + _x( 'Delivery to nearby parcel store/locker or to the front door.', 'dhl', 'woocommerce-germanized' ) + ), + secondaryLabel: costValue > 0 ? ( + + ) : '' + }, + { + value: 'home', + label: _x( 'Home Delivery', 'dhl', 'woocommerce-germanized' ), + content: ( + _x( 'Delivery usually to the front door.', 'dhl', 'woocommerce-germanized' ) + ), + secondaryLabel: costValue > 0 ? ( + + ) : '' + } + ]; + + return ( +
+

{ _x( 'Choose a delivery type', 'dhl', 'woocommerce-germanized' ) }

+ + { + setPreferredOption( 'preferred_delivery_type', value ); + } } + options={ options } + /> +
+ ); +}; + +const DhlPreferredOptions = ( + props +) => { + const { preferredDayEnabled, preferredLocationEnabled, preferredNeighborEnabled } = props; + + return ( +
+ { preferredDayEnabled ? ( + + ) : '' } + { preferredLocationEnabled || preferredNeighborEnabled ? ( + + ) : '' } +
+ ); +}; + +const DhlPreferredWrapper = ({ + props, + children +}) => { + return ( +
+

+ { _x( 'DHL Preferred Delivery. Delivered just as you wish.', 'dhl', 'woocommerce-germanized' ) } + + + + +

+ { children } +
+ ); +}; + +const DhlPreferredDeliveryOptions = ({ + extensions, + cart, + components +}) => { + const { + shippingRates, + needsShipping, + isLoadingRates, + isSelectingRate, + } = useSelect( ( select ) => { + const isEditor = !! select( 'core/editor' ); + const store = select( CART_STORE_KEY ); + const rates = isEditor + ? [] + : store.getShippingRates(); + return { + shippingRates: rates, + needsShipping: store.getNeedsShipping(), + isLoadingRates: store.isCustomerDataUpdating(), + isSelectingRate: store.isShippingRateBeingSelected(), + }; + } ); + + const [ needsFeeUpdate, setNeedsFeeUpdate ] = useState( false ); + const shippingProviders = getSelectedShippingProviders( shippingRates ); + const hasDhlProvider = hasShippingProvider( 'dhl', shippingProviders ); + const { __internalSetExtensionData } = useDispatch( CHECKOUT_STORE_KEY ); + + const { isCustomerDataUpdating } = useSelect( + ( select ) => { + return { + isCustomerDataUpdating: select( CART_STORE_KEY ).isCustomerDataUpdating(), + }; + } + ); + + const { + activePaymentMethod + } = useSelect( ( select ) => { + const store = select( PAYMENT_STORE_KEY ); + + return { + activePaymentMethod: store.getActivePaymentMethod() + }; + }, [] ); + + const { preferredOptions } = useSelect( ( select ) => { + const store = select( CHECKOUT_STORE_KEY ); + + return { + preferredOptions: getDhlCheckoutData( store.getExtensionData() ) + }; + } ); + + const dhlOptions = getDhlCheckoutData( extensions ); + const preferredDayCost = parseInt( dhlOptions.hasOwnProperty( 'preferred_day_cost' ) ? dhlOptions['preferred_day_cost'] : 0, 10 ); + const homeDeliveryCost = parseInt( dhlOptions.hasOwnProperty( 'preferred_home_delivery_cost' ) ? dhlOptions['preferred_home_delivery_cost'] : 0, 10 ); + + const setDhlOption = ( option, value, updateCart = true ) => { + const checkoutOptions = { ...preferredOptions }; + checkoutOptions[ option ] = value; + + setDhlCheckoutData( checkoutOptions ); + + if ( updateCart ) { + if ( 'preferred_day' === option && preferredDayCost > 0 ) { + extensionCartUpdate( { + namespace: 'woocommerce-gzd-dhl-checkout-fees', + data: checkoutOptions, + } ); + } else if ( 'preferred_delivery_type' === option && homeDeliveryCost > 0 ) { + extensionCartUpdate( { + namespace: 'woocommerce-gzd-dhl-checkout-fees', + data: checkoutOptions, + } ); + } + } + }; + + const setPreferredOption = useCallback( + ( option, value ) => { + setDhlOption( option, value ); + }, + [ setDhlOption, preferredOptions ] + ); + + const totalsCurrency = getCurrencyFromPriceResponse( cart.cartTotals ); + const excludedPaymentGateways = getSetting( 'dhlExcludedPaymentGateways', [] ); + const isGatewayExcluded = _.includes( excludedPaymentGateways, activePaymentMethod ); + + const preferredDayEnabled = dhlOptions.preferred_day_enabled && dhlOptions.preferred_days.length > 0; + const preferredLocationEnabled = dhlOptions.preferred_location_enabled && ! hasPickupLocation(); + const preferredNeighborEnabled = dhlOptions.preferred_neighbor_enabled && ! hasPickupLocation(); + const preferredDeliveryTypeEnabled = dhlOptions.preferred_delivery_type_enabled; + const cdpCountries = getSetting( 'dhlCdpCountries', [] ); + + const preferredOptionsAvailable = 'DE' === cart.shippingAddress.country && ( preferredDayEnabled || preferredNeighborEnabled || preferredLocationEnabled ); + const isCdpAvailable = preferredDeliveryTypeEnabled && _.includes( cdpCountries, cart.shippingAddress.country ); + const isFeeAvailable = hasDhlProvider && ! isGatewayExcluded && ( ( 'DE' === cart.shippingAddress.country && preferredDayEnabled && preferredDayCost > 0 ) || ( isCdpAvailable && homeDeliveryCost > 0 ) ); + + const isAvailable = hasDhlProvider && ! isGatewayExcluded && ( isCdpAvailable || preferredOptionsAvailable ); + + useEffect(() => { + if ( isAvailable ) { + const currentData = getDhlCheckoutData( select( CHECKOUT_STORE_KEY ).getExtensionData() ); + + if ( ! preferredOptionsAvailable ) { + // Reset data + const checkoutOptions = Object.keys( currentData ).reduce( + ( accumulator, current ) => { + accumulator[current] = ''; + return accumulator + }, {} ); + + if ( isCdpAvailable ) { + checkoutOptions['preferred_delivery_type'] = dhlOptions['preferred_delivery_type']; + } + + setDhlCheckoutData( checkoutOptions ); + } else { + const checkoutOptions = { ...currentData, + 'preferred_day': preferredOptionsAvailable && preferredDayEnabled ? dhlOptions['preferred_day'] : '', + 'preferred_delivery_type': '', + }; + + setDhlCheckoutData( checkoutOptions ); + } + } + }, [ + preferredOptionsAvailable, + isCdpAvailable, + __internalSetExtensionData + ] ); + + // Debounce re-disable since disabling process itself will incur additional mutations which should be ignored. + const maybeUpdateFee = useDebouncedCallback( () => { + const currentData = getDhlCheckoutData( select( CHECKOUT_STORE_KEY ).getExtensionData() ); + + if ( ! isCustomerDataUpdating ) { + extensionCartUpdate( { + namespace: 'woocommerce-gzd-dhl-checkout-fees', + data: currentData, + } ); + + setNeedsFeeUpdate( () => { return false } ); + } + }, 2000 ); + + useEffect(() => { + if ( isAvailable ) { + const currentData = getDhlCheckoutData( select( CHECKOUT_STORE_KEY ).getExtensionData() ); + + const checkoutOptions = { ...currentData, + 'preferred_day': preferredOptionsAvailable && preferredDayEnabled ? dhlOptions['preferred_day'] : '', + 'preferred_delivery_type': isCdpAvailable ? dhlOptions['preferred_delivery_type'] : '', + }; + + setDhlCheckoutData( checkoutOptions ); + } else { + const currentData = getDhlCheckoutData( select( CHECKOUT_STORE_KEY ).getExtensionData() ); + + // Reset data + const checkoutOptions = Object.keys( currentData ).reduce( + ( accumulator, current ) => { + accumulator[current] = ''; + return accumulator + }, {} ); + + setDhlCheckoutData( checkoutOptions ); + } + }, [ + isAvailable + ] ); + + useEffect(() => { + setNeedsFeeUpdate( () => { return true } ); + }, [ isFeeAvailable ] ); + + /** + * Maybe delay the extensionCartUpdate in case shipping data is invalid + * to prevent (dirty data) race conditions with the update-customer call as + * the update-customer call will only be triggered when the data is errorless. + * + * Be careful: Calling the extensionCartUpdate overrides the current checkout data + * with data from the customer session. If that data has not yet been persisted, the current + * checkout data will be replaced with old data. + */ + useEffect(() => { + if ( needsFeeUpdate ) { + maybeUpdateFee.cancel(); + + const validationStore = select( VALIDATION_STORE_KEY ); + const invalidProps = [ + ...Object.keys( cart.shippingAddress ).filter( ( key ) => { + return ( + validationStore.getValidationError( 'shipping_' + key ) !== undefined + ); + } ), + ].filter( Boolean ); + + if ( invalidProps.length === 0 ) { + // No errors found, lets update. + maybeUpdateFee(); + } + } + + return () => { + maybeUpdateFee.cancel(); + }; + }, [ + needsFeeUpdate, + setNeedsFeeUpdate, + cart.shippingAddress, + isAvailable, + maybeUpdateFee + ] ); + + if ( ! isAvailable || ( ! preferredOptionsAvailable && ! isCdpAvailable ) ) { + return null; + } + + return ( +
+ + { preferredOptionsAvailable && + + } + { isCdpAvailable && + + } + +
+ ); +}; + +const render = () => { + return ( + + + + ); +}; + +registerPlugin( 'woocommerce-gzd-dhl-preferred-services', { + render, + scope: 'woocommerce-checkout', +} ); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/style.scss b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/style.scss new file mode 100644 index 000000000..a39b3b04c --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/blocks/checkout/slotfills/style.scss @@ -0,0 +1,65 @@ +.wc-gzd-checkout-dhl { + .wc-gzd-dhl-preferred-days, + .wc-gzd-dhl-preferred-location { + margin-bottom: 1em; + } + + .wc-gzd-checkout-dhl-title { + display: flex; + flex-wrap: wrap; + align-items: center; + font-size: 1em; + + .dhl-icon { + width: 130px; + height: auto; + margin-left: auto; + } + } + + .wc-gzd-dhl-preferred-day-select { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0; + padding: 0; + + .wc-gzd-dhl-preferred-day { + color: inherit; + flex-basis: 10%; + flex-grow: 1; + text-align: center; + padding: 10px 0 0; + margin: 0 8px 8px 0; + background-color: #e3e3e3; + border: none; + + &:last-child { + margin-right: 0; + } + + .inner { + position: relative; + display: flex; + flex-direction: column; + flex-wrap: wrap; + padding: 5px 10px; + font-size: 1em; + background-color: #eef4f2; + cursor: pointer; + margin: 0; + line-height: 1.8em; + + .day { + font-size: 1.4em; + } + } + + &.active { + .inner { + background-color: #FFCC00; + } + } + } + } +} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/index.js b/packages/woocommerce-germanized-dhl/assets/js/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/woocommerce-germanized-dhl/assets/js/static/admin-deutsche-post-label.js b/packages/woocommerce-germanized-dhl/assets/js/static/admin-deutsche-post-label.js new file mode 100644 index 000000000..720b135a2 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/static/admin-deutsche-post-label.js @@ -0,0 +1,124 @@ +window.germanized = window.germanized || {}; +window.germanized.admin = window.germanized.admin || {}; + +( function( $, admin ) { + + /** + * Core + */ + admin.dhl_post_label = { + params: {}, + + init: function () { + var self = admin.dhl_post_label; + self.params = wc_gzd_admin_deutsche_post_label_params; + + $( document.body ).on( 'wc_gzd_shipments_admin_shipment_modal_after_load_success', self.onLoadLabelModal ) + }, + + onLoadLabelModal: function( e, data, modal ) { + var self = admin.dhl_post_label; + + modal.$modal.off( 'change.gzd-dp-fields' ); + modal.$modal.on( 'change.gzd-dp-fields', '#wc-gzd-shipment-label-admin-fields-deutsche_post #product_id', { adminShipmentModal: modal }, self.onChangeProduct ); + modal.$modal.on( 'change.gzd-dp-fields', '#wc-gzd-shipment-label-admin-fields-deutsche_post #product_id, #wc-gzd-shipment-label-admin-fields-deutsche_post #wc-gzd-shipment-label-wrapper-additional-services :input', { adminShipmentModal: modal }, self.onRefreshPreview ); + + if ( modal.$modal.find( '#wc-gzd-shipment-label-admin-fields-deutsche_post' ).length > 0 ) { + var event = new $.Event( 'change' ); + + event.data = { + 'adminShipmentModal': modal + } + + self.onRefreshPreview( event ); + } + }, + + getSelectedAdditionalServices: function() { + return $( "#wc-gzd-shipment-label-wrapper-additional-services :input:checked" ).map( function() { + return $( this ).attr( 'name' ).replace( 'service_', '' ); + }).get(); + }, + + onChangeProduct: function( event ) { + // Reset services before submitting preview to prevent invalid services being passed. + $( "#wc-gzd-shipment-label-wrapper-additional-services :input:checked" ).prop( 'checked', false ); + }, + + onRefreshPreview: function( event ) { + var self = admin.dhl_post_label, + modal = event.data.adminShipmentModal, + params = {}; + + params['security'] = self.params.refresh_label_preview_nonce; + params['product_id'] = self.getProductId(); + params['selected_services'] = self.getSelectedAdditionalServices(); + params['action'] = 'woocommerce_gzd_dhl_refresh_deutsche_post_label_preview'; + + modal.doAjax( params, self.onPreviewSuccess ); + }, + + onPreviewSuccess: function( data ) { + var self = admin.dhl_post_label, + $wrapper = $( '.wc-gzd-dhl-im-product-data .col-preview' ), + $img_wrapper = $( '.wc-gzd-dhl-im-product-data' ).find( '.image-preview' ); + + if ( data.is_wp_int ) { + $wrapper.parents( '.wc-gzd-shipment-create-label' ).find( '.page_format_field' ).hide(); + } else { + $wrapper.parents( '.wc-gzd-shipment-create-label' ).find( '.page_format_field' ).show(); + } + + if ( data.preview_url ) { + $wrapper.block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + + if ( $img_wrapper.find( '.stamp-preview' ).length <= 0 ) { + $img_wrapper.append( '' ); + } + + self.replaceProductData( data.preview_data ); + + $img_wrapper.find( '.stamp-preview' ).prop('src', data.preview_url ).on( 'load', function() { + $wrapper.unblock(); + $( this ).show(); + }); + } else { + $img_wrapper.html( '' ); + self.replaceProductData( {} ); + } + }, + + getProductId: function() { + return $( '#wc-gzd-shipment-label-admin-fields-deutsche_post #product_id' ).val(); + }, + + replaceProductData: function( productData ) { + var self = admin.dhl_post_label, + $wrapper = $( '.wc-gzd-shipment-create-label' ).find( '.wc-gzd-dhl-im-product-data' ); + + $wrapper.find( '.data-placeholder' ).html( '' ); + + $wrapper.find( '.data-placeholder' ).each( function() { + var replaceKey = $( this ).data( 'replace' ); + + if ( productData.hasOwnProperty( replaceKey ) ) { + $( this ).html( productData[ replaceKey ] ); + $( this ).show(); + } else { + $( this ).hide(); + } + } ); + } + }; + + $( document ).ready( function() { + germanized.admin.dhl_post_label.init(); + }); + +})( jQuery, window.germanized.admin ); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/static/admin-internetmarke.js b/packages/woocommerce-germanized-dhl/assets/js/static/admin-internetmarke.js new file mode 100644 index 000000000..cf3528bfc --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/static/admin-internetmarke.js @@ -0,0 +1,266 @@ +window.germanized = window.germanized || {}; +window.germanized.admin = window.germanized.admin || {}; + +/* + * http://www.myersdaily.org/joseph/javascript/md5-text.html + */ +( function (global) { + + var md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + + } + + var cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + } + + var ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + } + + var gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + } + + var hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + } + + var ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + } + + var md51 = function (s) { + var txt = '', + n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i; + for (i = 64; i <= s.length; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < s.length; i++) + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i++) tail[i] = 0; + } + tail[14] = n * 8; + md5cycle(state, tail); + return state; + } + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + var md5blk = function (s) { /* I figured global was faster. */ + var md5blks = [], + i; /* Andy King said do it this way. */ + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + } + + var hex_chr = '0123456789abcdef'.split(''); + + var rhex = function (n) { + var s = '', + j = 0; + for (; j < 4; j++) + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + return s; + } + + var hex = function (x) { + for (var i = 0; i < x.length; i++) + x[i] = rhex(x[i]); + return x.join(''); + } + + var md5 = global.md5 = function (s) { + return hex(md51(s)); + } + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + } + + if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') { + var add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + } + } +})(window); + +( function( $, admin, window ) { + + /** + * Core + */ + admin.dhl_internetmarke = { + + params: {}, + + init: function () { + var self = admin.dhl_internetmarke; + + $( document ).on( 'click', '#woocommerce_gzd_dhl_im_portokasse_charge', self.onCharge ); + }, + + onCharge: function() { + var $button = $( this ), + data = $button.data(), + amount = $( '#woocommerce_gzd_dhl_im_portokasse_charge_amount' ).val(); + + $form = $( '
' ).appendTo( 'body' ); + + $.each( data, function( index, value ) { + $form.append( '' ); + } ); + + var balance = parseInt( ( parseFloat( amount.replace( ',', '.') ).toFixed( 2 ) * 100 ).toFixed() ); + var wallet = parseInt( data['wallet'] ); + + /** + * Set min amount. + */ + if ( balance < 1000 || Number.isNaN( balance ) ) { + balance = 1000; + } + + var date = new Date(); + var timestamp = + ('0' + date.getDate()).slice(-2) + + ('0' + (date.getMonth() + 1)).slice(-2) + + date.getFullYear().toString() + + '-' + + ('0' + date.getHours()).slice(-2) + + ('0' + date.getMinutes()).slice(-2) + + ('0' + date.getSeconds()).slice(-2); + + var concat = [ + data['partner_id'], + timestamp, + data['success_url'], + data['cancel_url'], + data['user_token'], + wallet + balance, + data['schluessel_dpwn_partner'] + ].join( '::' ); + + $form.append( '' ); + $form.append( '' ); + $form.append( '' ); + + $form.submit(); + + return false; + } + }; + + $( document ).ready( function() { + germanized.admin.dhl_internetmarke.init(); + }); + +})( jQuery, window.germanized.admin, window ); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/static/parcel-finder.js b/packages/woocommerce-germanized-dhl/assets/js/static/parcel-finder.js new file mode 100644 index 000000000..a4dfc1f0f --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/static/parcel-finder.js @@ -0,0 +1,309 @@ +window.germanized = window.germanized || {}; +window.germanized.dhl_parcel_finder = window.germanized.dhl_parcel_finder || {}; + +( function( $, germanized ) { + + /** + * Core + */ + germanized.dhl_parcel_finder = { + + params: {}, + parcelShops: [], + wrapper: '', + + init: function () { + var self = germanized.dhl_parcel_finder; + self.params = wc_gzd_dhl_parcel_finder_params; + self.wrapper = self.params.wrapper; + + $( document ) + .on( 'click', '.gzd-dhl-parcel-shop-modal', self.openModal ) + .on( 'click', '#dhl-parcel-finder-wrapper .dhl-parcel-finder-close', self.closeModal ) + .on( 'submit', '#dhl-parcel-finder-wrapper #dhl-parcel-finder-form', self.onSubmit ) + .on( 'click', '#dhl-parcel-finder-wrapper .dhl-retry-search', self.onSubmit ) + .on( 'click', '#dhl-parcel-finder-wrapper .dhl-parcelshop-select-btn', self.onSelectShop ); + + $( document.body ).on( 'woocommerce_gzd_dhl_location_available_pickup_types_changed', self.onChangeAvailablePickupTypes ); + }, + + onChangeAvailablePickupTypes: function() { + var self = germanized.dhl_parcel_finder, + loc = germanized.dhl_parcel_locator, + $modal = self.getModal(), + method = loc.getShippingMethod(), + methodData = loc.getShippingMethodData( method ); + + if ( methodData ) { + + $modal.find( '.finder-pickup-type' ).addClass( 'hidden' ); + + $.each( methodData.supports, function( i, pickupType ) { + var $type = $modal.find( '.finder-pickup-type[data-pickup_type="' + pickupType + '"]' ); + + $type.find( 'input[type=checkbox]' ).prop( 'checked', true ); + $type.removeClass( 'hidden' ); + }); + } + }, + + openModal: function() { + var self = germanized.dhl_parcel_finder, + $modal = self.getModal(); + + var country = $( self.wrapper + ' #shipping_country' ).val().length > 0 ? $( self.wrapper + ' #shipping_country' ).val() : $( self.wrapper + ' #billing_country' ).val(); + $modal.find( '#dhl-parcelfinder-country').val( country ); + + var postcode = $( self.wrapper + ' #shipping_postcode' ).val().length > 0 ? $( self.wrapper + ' #shipping_postcode' ).val() : $( self.wrapper + ' #billing_postcode' ).val(); + $modal.find( '#dhl-parcelfinder-postcode' ).val( postcode ); + + var city = $( self.wrapper + ' #shipping_city' ).val().length > 0 ? $( self.wrapper + ' #shipping_city' ).val() : $( self.wrapper + ' #billing_city' ).val(); + $modal.find( '#dhl-parcelfinder-city' ).val( city ); + + $modal.addClass( 'open' ); + $modal.find( '#dhl-parcel-finder-form' ).submit(); + + return false; + }, + + closeModal: function() { + var self = germanized.dhl_parcel_finder; + self.getModal().removeClass( 'open' ); + + return false; + }, + + getModal: function() { + return $( '#dhl-parcel-finder-wrapper' ); + }, + + doAjax: function( params, $wrapper, cSuccess, cError ) { + var self = germanized.dhl_parcel_finder; + + cSuccess = cSuccess || self.onAjaxSuccess; + cError = cError || self.onAjaxError; + + if ( ! params.hasOwnProperty( 'security' ) ) { + params['security'] = self.params.parcel_finder_nonce; + } + + $wrapper.find( '#dhl-parcel-finder-map' ).block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + + $wrapper.find( '.notice-wrapper' ).empty(); + + $.ajax({ + type: "POST", + url: self.params.ajax_url, + data: params, + success: function( data ) { + if ( data.success ) { + $wrapper.find( '#dhl-parcel-finder-map' ).unblock(); + cSuccess.apply( $wrapper, [ data ] ); + } else { + cError.apply( $wrapper, [ data ] ); + $wrapper.find( '#dhl-parcel-finder-map' ).unblock(); + + if ( data.hasOwnProperty( 'message' ) ) { + self.addNotice( data.message, 'error', $wrapper ); + } else if( data.hasOwnProperty( 'messages' ) ) { + $.each( data.messages, function( i, message ) { + self.addNotice( message, 'error', $wrapper ); + }); + } + } + }, + error: function( data ) {}, + dataType: 'json' + }); + }, + + onAjaxSuccess: function( data ) {}, + + onAjaxError: function( data ) {}, + + getFormData: function( $form ) { + var data = {}; + + $.each( $form.serializeArray(), function( index, item ) { + if ( item.name.indexOf( '[]' ) !== -1 ) { + item.name = item.name.replace( '[]', '' ); + data[ item.name ] = $.makeArray( data[ item.name ] ); + data[ item.name ].push( item.value ); + } else { + data[ item.name ] = item.value; + } + }); + + return data; + }, + + onSubmit: function( e ) { + var self = germanized.dhl_parcel_finder, + loc = germanized.dhl_parcel_locator, + $modal = self.getModal(), + $content = $modal.find( '#dhl-parcel-finder' ), + $form = $content.find( 'form' ), + params = self.getFormData( $form ); + + params['action'] = 'woocommerce_gzd_dhl_parcelfinder_search'; + params['is_checkout'] = loc.isCheckout() ? 'yes' : 'no'; + + self.doAjax( params, $content, self.onSubmitSuccess ); + + return false; + }, + + onSubmitSuccess: function( data ) { + var self = germanized.dhl_parcel_finder; + + if ( data.parcel_shops ) { + self.parcelShops = data.parcel_shops; + + if ( typeof google === 'object' && typeof google.maps === 'object' ) { + self.updateMap(); + } else { + self.loadMapsAPI(); + } + } + }, + + loadMapsAPI: function() { + var self = germanized.dhl_parcel_finder; + + self.addScript( 'https://maps.googleapis.com/maps/api/js?key=' + self.params.api_key, self.updateMap ); + }, + + addScript: function( url, callback ) { + var script = document.createElement( 'script' ); + + if ( callback ) { + script.onload = callback; + } + + script.type = 'text/javascript'; + script.src = url; + document.body.appendChild( script ); + }, + + updateMap: function() { + var self = germanized.dhl_parcel_finder, + parcelShops = self.parcelShops; + + var uluru = { + lat: parcelShops[0].place.geo.latitude, + lng: parcelShops[0].place.geo.longitude + }; + + var map = new google.maps.Map( document.getElementById( 'dhl-parcel-finder-map' ), { + zoom: 13, + center: uluru + }); + + var infoWinArray = []; + + $.each( parcelShops, function( key, value ) { + + var uluru = { + lat: value.place.geo.latitude, + lng: value.place.geo.longitude + }; + + var markerIcon = self.params.packstation_icon, + shopLabel = self.params.i18n.packstation; + + switch ( value.gzd_type ) { + case 'parcelshop': + markerIcon = self.params.parcelshop_icon; + shopLabel = self.params.i18n.branch; + break; + case 'postoffice': + markerIcon = self.params.postoffice_icon; + shopLabel = self.params.i18n.branch; + break; + } + + var infowindow = new google.maps.InfoWindow({ + content: value.html_content, + maxWidth: 300 + }); + + infoWinArray.push( infowindow ); + + var marker = new google.maps.Marker({ + position : uluru, + map : map, + title : shopLabel, + animation: google.maps.Animation.DROP, + icon : markerIcon + }); + + marker.addListener('click', function() { + clearOverlays(); + infowindow.open( map, marker ); + }); + }); + + // Clear all info windows + function clearOverlays() { + for ( var i = 0; i < infoWinArray.length; i++ ) { + infoWinArray[i].close(); + } + } + }, + + onSelectShop: function() { + var self = germanized.dhl_parcel_finder, + parcelShopId = $( this ).attr( 'id' ), + $addressType = $( self.wrapper + ' #shipping_address_type' ), + country = $( self.wrapper + ' #shipping_country' ).val(), + fieldKey = germanized.dhl_parcel_locator.getPickupFieldKey( country ); + + $.each( self.parcelShops, function( key, value ) { + if ( value.gzd_result_id === parcelShopId ) { + var isPackstation = 'packstation' === value.gzd_type; + + $( self.wrapper + ' #shipping_first_name' ).val( $( self.wrapper + ' #shipping_first_name' ).val().length > 0 ? $( self.wrapper + ' #shipping_first_name' ).val() : $( self.wrapper + ' #billing_first_name' ).val() ); + $( self.wrapper + ' #shipping_last_name' ).val( $( self.wrapper + ' #shipping_last_name' ).val().length > 0 ? $( self.wrapper + ' #shipping_last_name' ).val() : $( self.wrapper + ' #billing_last_name' ).val() ); + + $( self.wrapper + ' #shipping_' + fieldKey ).val( value.gzd_name ); + + if ( 'DE' === country ) { + $( self.wrapper + ' #shipping_address_2' ).val( '' ); + } else { + $( self.wrapper + ' #shipping_address_1' ).val( value.place.address.streetAddress ); + } + + $( self.wrapper + ' #shipping_postcode' ).val( value.place.address.postalCode ); + $( self.wrapper + ' #shipping_city' ).val( value.place.address.addressLocality ); + + $addressType.val( 'dhl' ).trigger( 'change' ); + + self.closeModal(); + + if ( 'DE' === country ) { + if ( isPackstation && $( self.wrapper + ' #shipping_dhl_postnumber' ).val() === '' ) { + $( self.wrapper + ' #shipping_dhl_postnumber' ).focus(); + } + } + + return true; + } + }); + }, + + addNotice: function( message, noticeType, $wrapper ) { + $wrapper.find( '.notice-wrapper' ).append( '

' + message + '

' ); + } + }; + + $( document ).ready( function() { + germanized.dhl_parcel_finder.init(); + }); + +})( jQuery, window.germanized ); diff --git a/packages/woocommerce-germanized-dhl/assets/js/static/parcel-locator.js b/packages/woocommerce-germanized-dhl/assets/js/static/parcel-locator.js new file mode 100644 index 000000000..fa5a2fcdd --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/static/parcel-locator.js @@ -0,0 +1,505 @@ + +window.germanized = window.germanized || {}; +window.germanized.dhl_parcel_locator = window.germanized.dhl_parcel_locator || {}; + +( function( $, germanized ) { + + /** + * Core + */ + germanized.dhl_parcel_locator = { + + params: {}, + parcelShops: [], + wrapper: '', + + init: function () { + var self = germanized.dhl_parcel_locator; + self.params = wc_gzd_dhl_parcel_locator_params; + self.wrapper = self.params.wrapper; + + $( document ) + .on( 'change.dhl', self.wrapper + ' #shipping_address_type', self.refreshAddressType ) + .on( 'change.dhl', self.wrapper + ' #shipping_address_1', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #shipping_address_2', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #shipping_postcode', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #shipping_country', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #ship-to-different-address-checkbox', self.onChangeShipping ) + .on( 'change.dhl', self.wrapper + ' #shipping_country', self.refreshAvailability ) + .on( 'input.dhl validate.dhl change.dhl', self.wrapper + ' #shipping_dhl_postnumber', self.validatePostnumber ); + + $( document.body ).on( 'payment_method_selected', self.triggerCheckoutRefresh ); + $( document.body ).on( 'updated_checkout', self.afterRefreshCheckout ); + + if ( ! self.isCheckout() ) { + $( document.body ).on( 'country_to_state_changing', function() { + var self = germanized.dhl_parcel_locator; + + setTimeout( function() { + self.refreshAddressType(); + }, 500 ); + } ); + } + + self.refreshAvailability(); + self.refreshAddressType(); + }, + + validatePostnumber: function( e ) { + var $this = $( this ), + $parent = $this.closest( '.form-row' ), + eventType = e.type; + + if ( 'input' === eventType ) { + if ( $this.val() ) { + $this.val( $this.val().replace( /\D/g,'' ) ); + } + } + + if ( 'validate' === eventType || 'change' === eventType ) { + if ( $this.val() ) { + $this.val( $this.val().replace( /\D/g,'' ) ); + + if ( $this.val().toString().length < 6 || $this.val().toString().length > 12 ) { + $parent.removeClass( 'woocommerce-validated' ).addClass( 'woocommerce-invalid woocommerce-invalid-postnumber' ); + } + } + } + }, + + triggerCheckoutRefresh: function() { + $( document.body ).trigger( 'update_checkout' ); + }, + + isCheckout: function() { + var self = germanized.dhl_parcel_locator; + + return self.params.is_checkout; + }, + + afterRefreshCheckout: function() { + var self = germanized.dhl_parcel_locator; + + var params = { + 'security': self.params.parcel_locator_data_nonce, + 'action' : 'woocommerce_gzd_dhl_parcel_locator_refresh_shipping_data' + }; + + $.ajax({ + type: "POST", + url: self.params.ajax_url, + data: params, + success: function( data ) { + // Update shipping method data from session + self.params['methods'] = data.methods; + self.refreshAvailability(); + }, + error: function( data ) { + self.refreshAvailability(); + }, + dataType: 'json' + }); + }, + + refreshAvailability: function() { + var self = germanized.dhl_parcel_locator, + shippingMethod = self.getShippingMethod(), + methodData = self.getShippingMethodData( shippingMethod ); + + if ( ! self.isAvailable() ) { + $( self.wrapper + ' #shipping_address_type' ).val( 'regular' ).trigger( 'change' ); + $( self.wrapper + ' #shipping_address_type_field' ).hide(); + } else { + var $typeField = $( self.wrapper + ' #shipping_address_type' ); + var selected = $typeField.val(); + + if ( self.isCheckout() ) { + $typeField.html( '' ); + + if ( methodData ) { + $.each( methodData.address_type_options, function( name, title ) { + $typeField.append( $( '