+ { _x( 'Choose a delivery day', 'dhl', 'woocommerce-germanized' ) }
+
+ { costValue > 0 &&
+ (+
{ _x( 'Choose a preferred location', 'dhl', 'woocommerce-germanized' ) }
+{ _x( 'Choose a delivery type', 'dhl', 'woocommerce-germanized' ) }
+ +' + message + '
' + $addressInput.data( 'desc-dhl' ) + '
' ); + } + } + }, + + isEnabled: function() { + var self = germanized.dhl_parcel_locator; + + return self.isAvailable() && $( self.wrapper + ' #shipping_address_type' ).val() === 'dhl'; + }, + + getPaymentMethod: function() { + var $selected = $( '.payment_methods .input-radio:checked' ); + + if ( $selected ) { + return $selected.val(); + } + + return ''; + }, + + getShippingMethod: function( pwithInstanceId ) { + var current = ''; + var withInstanceId = pwithInstanceId ? pwithInstanceId : true; + + if ( $( 'select.shipping_method' ).length > 0 ) { + current = $( 'select.shipping_method' ).val(); + } else if ( $( 'input[name^="shipping_method"]:checked' ).length > 0 ) { + current = $( 'input[name^="shipping_method"]:checked' ).val(); + } else if ( $( 'input[name^="shipping_method"][type="hidden"]' ).length > 0 ) { + current = $( 'input[name^="shipping_method"][type="hidden"]' ).val(); + } + + if ( ! withInstanceId ) { + if ( 'undefined' !== typeof current && current.length > 0 ) { + var currentParts = current.split(':'); + + if ( currentParts.length > 0 ) { + current = currentParts[0]; + } + } + } else { + // In case an instance id is needed but missing - assume 0 as instance id + if ( 'undefined' !== typeof current && current.length > 0 ) { + var currentParts = current.split(':'); + + if ( currentParts.length <= 1 ) { + current = current + ':0'; + } + } + } + + return current; + }, + + pickupTypeIsAvailable: function( pickupType ) { + var self = germanized.dhl_parcel_locator, + shippingMethod = self.getShippingMethod(), + isAvailable = true; + + if ( ! self.shippingMethodSupportsPickupType( shippingMethod, pickupType ) ) { + isAvailable = false; + } + + return isAvailable; + }, + + isAvailable: function() { + var self = germanized.dhl_parcel_locator, + shippingCountry = $( self.wrapper + ' #shipping_country' ).val(), + shippingMethod = self.getShippingMethod(), + paymentMethod = self.getPaymentMethod(), + methodData = self.getShippingMethodData( shippingMethod ), + isAvailable = true; + + if ( $.inArray( paymentMethod, self.params.excluded_gateways ) !== -1 ) { + isAvailable = false; + } + + if ( $.inArray( shippingCountry, self.params.supported_countries ) === -1 ) { + isAvailable = false; + } + + if ( self.isCheckout() ) { + if ( ! methodData || methodData.supports.length === 0 ) { + isAvailable = false; + } + } + + return isAvailable; + } + }; + + $( document ).ready( function() { + germanized.dhl_parcel_locator.init(); + }); + +})( jQuery, window.germanized ); diff --git a/packages/woocommerce-germanized-dhl/assets/js/static/preferred-services.js b/packages/woocommerce-germanized-dhl/assets/js/static/preferred-services.js new file mode 100644 index 000000000..7b212fe78 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/static/preferred-services.js @@ -0,0 +1,79 @@ + +window.germanized = window.germanized || {}; +window.germanized.dhl_preferred_services = window.germanized.dhl_preferred_services || {}; + +( function( $, germanized ) { + + /** + * Core + */ + germanized.dhl_preferred_services = { + + params: {}, + + init: function () { + var self = germanized.dhl_preferred_services; + self.params = wc_gzd_dhl_preferred_services_params; + + $( document.body ).on( 'updated_checkout', self.afterRefreshCheckout ); + + $( document ) + .on( 'change', '.dhl-preferred-service-content .dhl-preferred-location-types input', self.onChangeLocationType ) + .on( 'change', '.woocommerce-checkout #billing_postcode', self.triggerCheckoutRefresh ) + .on( 'change', '.woocommerce-checkout #shipping_postcode', self.triggerCheckoutRefresh ) + .on( 'change', '.dhl-preferred-service-content .dhl-preferred-service-times input', self.triggerCheckoutRefresh ) + .on( 'change', '.dhl-preferred-service-content .dhl-preferred-delivery-types input', self.triggerCheckoutRefresh ); + + if ( self.params.payment_gateways_excluded ) { + $( document.body ).on( 'payment_method_selected', self.triggerCheckoutRefresh ); + } + + self.afterRefreshCheckout(); + }, + + triggerCheckoutRefresh: function() { + $( document.body ).trigger( 'update_checkout' ); + }, + + afterRefreshCheckout: function() { + var self = germanized.dhl_preferred_services; + + self.initTipTip(); + self.onChangeLocationType(); + }, + + onChangeLocationType: function() { + var self = germanized.dhl_preferred_services, + $box = $( '.dhl-preferred-service-content .dhl-preferred-location-types input:checked' ); + + $( '.dhl-preferred-service-content .dhl-preferred-service-location-data' ).hide(); + + if ( $box.length > 0 ) { + if ( 'place' === $box.val() ) { + $( '.dhl-preferred-service-content .dhl-preferred-service-location-place' ).show(); + } else if ( 'neighbor' === $box.val() ) { + $( '.dhl-preferred-service-content .dhl-preferred-service-location-neighbor' ).show(); + } + } + }, + + initTipTip: function() { + + // Remove any lingering tooltips + $( '#tiptip_holder' ).removeAttr( 'style' ); + $( '#tiptip_arrow' ).removeAttr( 'style' ); + + $( '.dhl-preferred-service-content .woocommerce-help-tip' ).tipTip({ + 'attribute': 'data-tip', + 'fadeIn': 50, + 'fadeOut': 50, + 'delay': 200 + }); + } + }; + + $( document ).ready( function() { + germanized.dhl_preferred_services.init(); + }); + +})( jQuery, window.germanized ); diff --git a/packages/woocommerce-germanized-dhl/assets/wsdl/OneClickForAppV3.wsdl b/packages/woocommerce-germanized-dhl/assets/wsdl/OneClickForAppV3.wsdl new file mode 100644 index 000000000..ee4fe0066 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/wsdl/OneClickForAppV3.wsdl @@ -0,0 +1,2743 @@ +
+ Element provides terms of trades, incoterms codes:
+
+ - DDP (Delivery Duty Paid)
+ - DXV (Delivery Duty Paid (excl. VAT))
+ - DAP (formerly DDU, Delivery At Place)
+ - DDX (Delivery Duty Paid (excl. Duties, taxes and VAT)
+ - CPT (Carriage Paid To (within EU only))
+
+ are vaild values.
+
+ '+e+"
'+i.data("desc-dhl")+"
")))},isEnabled:function(){var r=a.dhl_parcel_locator;return r.isAvailable()&&"dhl"===e(r.wrapper+" #shipping_address_type").val()},getPaymentMethod:function(){var a=e(".payment_methods .input-radio:checked");return a?a.val():""},getShippingMethod:function(a){var r,o="",p=a||!0;return e("select.shipping_method").length>0?o=e("select.shipping_method").val():e('input[name^="shipping_method"]:checked').length>0?o=e('input[name^="shipping_method"]:checked').val():e('input[name^="shipping_method"][type="hidden"]').length>0&&(o=e('input[name^="shipping_method"][type="hidden"]').val()),p?void 0!==o&&o.length>0&&(r=o.split(":")).length<=1&&(o+=":0"):void 0!==o&&o.length>0&&(r=o.split(":")).length>0&&(o=r[0]),o},pickupTypeIsAvailable:function(e){var r=a.dhl_parcel_locator,o=r.getShippingMethod(),p=!0;return r.shippingMethodSupportsPickupType(o,e)||(p=!1),p},isAvailable:function(){var r=a.dhl_parcel_locator,o=e(r.wrapper+" #shipping_country").val(),p=r.getShippingMethod(),t=r.getPaymentMethod(),d=r.getShippingMethodData(p),i=!0;return-1!==e.inArray(t,r.params.excluded_gateways)&&(i=!1),-1===e.inArray(o,r.params.supported_countries)&&(i=!1),r.isCheckout()&&(d&&0!==d.supports.length||(i=!1)),i}},e(document).ready((function(){a.dhl_parcel_locator.init()})),((window.germanizedShipments=window.germanizedShipments||{}).static=window.germanizedShipments.static||{})["parcel-locator"]={}}(); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.asset.php b/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.asset.php new file mode 100644 index 000000000..e150bc586 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.asset.php @@ -0,0 +1 @@ + array(), 'version' => '8e52d369588e281ba2f7'); diff --git a/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.css b/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.css new file mode 100644 index 000000000..9875538b3 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.css @@ -0,0 +1 @@ +#tiptip_holder{display:none;left:0;position:absolute;top:0;z-index:8675309}#tiptip_holder.tip_top{padding-bottom:5px}#tiptip_holder.tip_top #tiptip_arrow_inner{border-top-color:#333;margin-left:-6px;margin-top:-7px}#tiptip_holder.tip_bottom{padding-top:5px}#tiptip_holder.tip_bottom #tiptip_arrow_inner{border-bottom-color:#333;margin-left:-6px;margin-top:-5px}#tiptip_holder.tip_right{padding-left:5px}#tiptip_holder.tip_right #tiptip_arrow_inner{border-right-color:#333;margin-left:-5px;margin-top:-6px}#tiptip_holder.tip_left{padding-right:5px}#tiptip_holder.tip_left #tiptip_arrow_inner{border-left-color:#333;margin-left:-7px;margin-top:-6px}#tiptip_content{background:#333;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.2);color:#fff;font-size:.8em;max-width:150px;padding:.618em 1em;text-align:center}#tiptip_content code{background:#888;padding:1px}#tiptip_arrow,#tiptip_arrow_inner{border:6px solid transparent;height:0;position:absolute;width:0}.dhl-preferred-service-content{margin-top:1em}.dhl-preferred-service-content .dhl-hidden{display:none}.dhl-preferred-service-content .dhl-preferred-service-cost{font-size:.9em}.dhl-preferred-service-content .dhl-preferred-service-item,.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-logo{margin-bottom:1em}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-logo img{background:#fc0;margin:0;max-height:100px;max-width:100px;padding:0}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-title{font-size:1em;font-weight:700;margin-bottom:.5em}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-cost{margin-bottom:.5em}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-desc{font-size:.9em;margin-bottom:.5em}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times{display:flex;flex-direction:row;flex-wrap:wrap;margin:0;padding:0}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li{background-color:#e3e3e3;display:inline-block;flex-basis:10%;margin:0 8px 8px 0;padding:10px 0 0;text-align:center}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li label{background-color:#eef4f2;color:#5f7285;cursor:pointer;display:flex;flex-direction:column;flex-wrap:wrap;font-size:.9em;font-weight:700;margin:0;padding:5px 10px;position:relative}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li label .dhl-preferred-time-title{font-size:1.2em}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li input[type=radio]{height:1px;opacity:0;position:absolute;width:1px}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li input[type=radio]:checked~label{background-color:#fc0}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times.dhl-preferred-service-time li{flex-basis:inherit;flex-grow:inherit}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times.dhl-preferred-service-time li label .dhl-preferred-time-title{font-size:1em}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-data input[type=text]{margin-bottom:.5em;width:100%}.dhl-preferred-service-content .dhl-preferred-service-item .woocommerce-help-tip{background:#fc0;border-radius:50%;display:inline-block;font-size:1em;font-style:normal;height:18px;line-height:18px;margin-left:5px;margin-top:-5px;padding:3px;position:relative;vertical-align:middle;width:18px}.dhl-preferred-service-content .dhl-preferred-service-item .woocommerce-help-tip:after{content:"?";cursor:help;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-delivery-types,.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-location-types{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start;list-style:none;margin:0 0 1em}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-delivery-types li,.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-location-types li{margin-right:1em}.dhl-preferred-service-content .dhl-preferred-service-item.dhl-preferred-service-header .dhl-preferred-service-title{font-size:1.1em} diff --git a/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.js b/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.js new file mode 100644 index 000000000..4bf49343d --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/static/preferred-services-styles.js @@ -0,0 +1 @@ +!function(){"use strict";var e={};(function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})})(e),((window.germanizedShipments=window.germanizedShipments||{}).static=window.germanizedShipments.static||{})["preferred-services-styles"]=e}(); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/build/static/preferred-services.asset.php b/packages/woocommerce-germanized-dhl/build/static/preferred-services.asset.php new file mode 100644 index 000000000..d83d8fc4b --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/static/preferred-services.asset.php @@ -0,0 +1 @@ + array(), 'version' => '004db9f9bf1c616872db'); diff --git a/packages/woocommerce-germanized-dhl/build/static/preferred-services.js b/packages/woocommerce-germanized-dhl/build/static/preferred-services.js new file mode 100644 index 000000000..9c62e9885 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/static/preferred-services.js @@ -0,0 +1 @@ +!function(){var e,r;window.germanized=window.germanized||{},window.germanized.dhl_preferred_services=window.germanized.dhl_preferred_services||{},e=jQuery,(r=window.germanized).dhl_preferred_services={params:{},init:function(){var t=r.dhl_preferred_services;t.params=wc_gzd_dhl_preferred_services_params,e(document.body).on("updated_checkout",t.afterRefreshCheckout),e(document).on("change",".dhl-preferred-service-content .dhl-preferred-location-types input",t.onChangeLocationType).on("change",".woocommerce-checkout #billing_postcode",t.triggerCheckoutRefresh).on("change",".woocommerce-checkout #shipping_postcode",t.triggerCheckoutRefresh).on("change",".dhl-preferred-service-content .dhl-preferred-service-times input",t.triggerCheckoutRefresh).on("change",".dhl-preferred-service-content .dhl-preferred-delivery-types input",t.triggerCheckoutRefresh),t.params.payment_gateways_excluded&&e(document.body).on("payment_method_selected",t.triggerCheckoutRefresh),t.afterRefreshCheckout()},triggerCheckoutRefresh:function(){e(document.body).trigger("update_checkout")},afterRefreshCheckout:function(){var e=r.dhl_preferred_services;e.initTipTip(),e.onChangeLocationType()},onChangeLocationType:function(){r.dhl_preferred_services;var t=e(".dhl-preferred-service-content .dhl-preferred-location-types input:checked");e(".dhl-preferred-service-content .dhl-preferred-service-location-data").hide(),t.length>0&&("place"===t.val()?e(".dhl-preferred-service-content .dhl-preferred-service-location-place").show():"neighbor"===t.val()&&e(".dhl-preferred-service-content .dhl-preferred-service-location-neighbor").show())},initTipTip:function(){e("#tiptip_holder").removeAttr("style"),e("#tiptip_arrow").removeAttr("style"),e(".dhl-preferred-service-content .woocommerce-help-tip").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200})}},e(document).ready((function(){r.dhl_preferred_services.init()})),((window.germanizedShipments=window.germanizedShipments||{}).static=window.germanizedShipments.static||{})["preferred-services"]={}}(); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/build/style-checkout.css b/packages/woocommerce-germanized-dhl/build/style-checkout.css new file mode 100644 index 000000000..824d09846 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/style-checkout.css @@ -0,0 +1 @@ +.wc-gzd-checkout-dhl .wc-gzd-checkout-dhl-title{display:flex;flex-wrap:wrap;align-items:center;font-size:1em}.wc-gzd-checkout-dhl .wc-gzd-checkout-dhl-title .dhl-icon{width:130px;height:auto;margin-left:auto}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option{position:relative}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option .wc-block-components-radio-control__option-layout{font-size:.875em}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option .wc-block-components-radio-control-accordion-content{font-size:.875em}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option .wc-block-components-radio-control__option{border-bottom:none;padding-left:56px;padding-right:16px;margin:0;padding-bottom:1em;padding-top:1em}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option .wc-block-components-radio-control__option::after{border-style:solid;border-width:1px 1px 0;bottom:0;content:"";display:block;left:0;opacity:.3;pointer-events:none;position:absolute;right:0;top:0}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option .wc-block-components-radio-control__option .wc-block-components-radio-control__input{left:16px}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option .wc-block-components-radio-control__option::after{border-width:0}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option::after{border-style:solid;border-width:1px 1px 0;bottom:0;content:"";display:block;left:0;opacity:.3;pointer-events:none;position:absolute;right:0;top:0}.wc-gzd-checkout-dhl .wc-block-components-radio-control-accordion-option:last-child::after{border-width:1px}.wc-gzd-checkout-dhl .wc-gzd-dhl-preferred-day-select{display:flex;flex-direction:row;flex-wrap:wrap;margin:0;padding:0}.wc-gzd-checkout-dhl .wc-gzd-dhl-preferred-day-select .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}.wc-gzd-checkout-dhl .wc-gzd-dhl-preferred-day-select .wc-gzd-dhl-preferred-day:last-child{margin-right:0}.wc-gzd-checkout-dhl .wc-gzd-dhl-preferred-day-select .wc-gzd-dhl-preferred-day .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}.wc-gzd-checkout-dhl .wc-gzd-dhl-preferred-day-select .wc-gzd-dhl-preferred-day .inner .day{font-size:1.4em}.wc-gzd-checkout-dhl .wc-gzd-dhl-preferred-day-select .wc-gzd-dhl-preferred-day.active .inner{background-color:#fc0} diff --git a/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl-style.asset.php b/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl-style.asset.php new file mode 100644 index 000000000..3ced3c40e --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl-style.asset.php @@ -0,0 +1 @@ + array(), 'version' => '2303301f334e9e994f3c'); diff --git a/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl.asset.php b/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl.asset.php new file mode 100644 index 000000000..3ced3c40e --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl.asset.php @@ -0,0 +1 @@ + array(), 'version' => '2303301f334e9e994f3c'); diff --git a/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl.js b/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl.js new file mode 100644 index 000000000..c3d6d1e81 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/build/wc-gzd-shipments-blocks-dhl.js @@ -0,0 +1 @@ +((window.wcShipments=window.wcShipments||{}).blocks=window.wcShipments.blocks||{})["wc-gzd-shipments-blocks-dhl"]={}; \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/i18n/holidays.php b/packages/woocommerce-germanized-dhl/i18n/holidays.php new file mode 100644 index 000000000..285922ad5 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/i18n/holidays.php @@ -0,0 +1,57 @@ + array( + '2024-01-01', + '2024-01-06', + '2024-03-08', + '2024-03-28', + '2024-03-29', + '2024-03-31', + '2024-04-01', + '2024-05-01', + '2024-05-09', + '2024-05-19', + '2024-05-20', + '2024-05-30', + '2024-08-08', + '2024-08-15', + '2024-09-20', + '2024-10-03', + '2024-10-31', + '2024-11-01', + '2024-11-20', + '2024-12-25', + '2024-12-26', + '2025-01-01', + '2025-01-06', + '2025-03-08', + '2025-04-17', + '2025-04-18', + '2025-04-20', + '2025-04-21', + '2025-05-01', + '2025-05-29', + '2025-06-08', + '2025-06-09', + '2025-06-19', + '2025-08-08', + '2025-08-15', + '2025-09-20', + '2025-10-03', + '2025-10-31', + '2025-11-01', + '2025-11-19', + '2025-12-25', + '2025-12-26', + ), +); diff --git a/packages/woocommerce-germanized-dhl/i18n/iso.php b/packages/woocommerce-germanized-dhl/i18n/iso.php new file mode 100644 index 000000000..75cc005be --- /dev/null +++ b/packages/woocommerce-germanized-dhl/i18n/iso.php @@ -0,0 +1,260 @@ + 'AFG', + 'AX' => 'ALA', + 'AL' => 'ALB', + 'DZ' => 'DZA', + 'AS' => 'ASM', + 'AD' => 'AND', + 'AO' => 'AGO', + 'AI' => 'AIA', + 'AQ' => 'ATA', + 'AG' => 'ATG', + 'AR' => 'ARG', + 'AM' => 'ARM', + 'AW' => 'ABW', + 'AU' => 'AUS', + 'AT' => 'AUT', + 'AZ' => 'AZE', + 'BS' => 'BHS', + 'BH' => 'BHR', + 'BD' => 'BGD', + 'BB' => 'BRB', + 'BY' => 'BLR', + 'BE' => 'BEL', + 'BZ' => 'BLZ', + 'BJ' => 'BEN', + 'BM' => 'BMU', + 'BT' => 'BTN', + 'BO' => 'BOL', + 'BA' => 'BIH', + 'BW' => 'BWA', + 'BV' => 'BVT', + 'BR' => 'BRA', + 'VG' => 'VGB', + 'IO' => 'IOT', + 'BN' => 'BRN', + 'BG' => 'BGR', + 'BF' => 'BFA', + 'BI' => 'BDI', + 'KH' => 'KHM', + 'CM' => 'CMR', + 'CA' => 'CAN', + 'CV' => 'CPV', + 'KY' => 'CYM', + 'CF' => 'CAF', + 'TD' => 'TCD', + 'CL' => 'CHL', + 'CN' => 'CHN', + 'HK' => 'HKG', + 'MO' => 'MAC', + 'CX' => 'CXR', + 'CC' => 'CCK', + 'CO' => 'COL', + 'KM' => 'COM', + 'CG' => 'COG', + 'CD' => 'COD', + 'CK' => 'COK', + 'CR' => 'CRI', + 'CI' => 'CIV', + 'HR' => 'HRV', + 'CU' => 'CUB', + 'CY' => 'CYP', + 'CZ' => 'CZE', + 'DK' => 'DNK', + 'DJ' => 'DJI', + 'DM' => 'DMA', + 'DO' => 'DOM', + 'EC' => 'ECU', + 'EG' => 'EGY', + 'SV' => 'SLV', + 'GQ' => 'GNQ', + 'ER' => 'ERI', + 'EE' => 'EST', + 'ET' => 'ETH', + 'FK' => 'FLK', + 'FO' => 'FRO', + 'FJ' => 'FJI', + 'FI' => 'FIN', + 'FR' => 'FRA', + 'GF' => 'GUF', + 'PF' => 'PYF', + 'TF' => 'ATF', + 'GA' => 'GAB', + 'GM' => 'GMB', + 'GE' => 'GEO', + 'DE' => 'DEU', + 'GH' => 'GHA', + 'GI' => 'GIB', + 'GR' => 'GRC', + 'GL' => 'GRL', + 'GD' => 'GRD', + 'GP' => 'GLP', + 'GU' => 'GUM', + 'GT' => 'GTM', + 'GG' => 'GGY', + 'GN' => 'GIN', + 'GW' => 'GNB', + 'GY' => 'GUY', + 'HT' => 'HTI', + 'HM' => 'HMD', + 'VA' => 'VAT', + 'HN' => 'HND', + 'HU' => 'HUN', + 'IS' => 'ISL', + 'IN' => 'IND', + 'ID' => 'IDN', + 'IR' => 'IRN', + 'IQ' => 'IRQ', + 'IE' => 'IRL', + 'IM' => 'IMN', + 'IL' => 'ISR', + 'IT' => 'ITA', + 'JM' => 'JAM', + 'JP' => 'JPN', + 'JE' => 'JEY', + 'JO' => 'JOR', + 'KZ' => 'KAZ', + 'KE' => 'KEN', + 'KI' => 'KIR', + 'KP' => 'PRK', + 'KR' => 'KOR', + 'KW' => 'KWT', + 'KG' => 'KGZ', + 'LA' => 'LAO', + 'LV' => 'LVA', + 'LB' => 'LBN', + 'LS' => 'LSO', + 'LR' => 'LBR', + 'LY' => 'LBY', + 'LI' => 'LIE', + 'LT' => 'LTU', + 'LU' => 'LUX', + 'MK' => 'MKD', + 'MG' => 'MDG', + 'MW' => 'MWI', + 'MY' => 'MYS', + 'MV' => 'MDV', + 'ML' => 'MLI', + 'MT' => 'MLT', + 'MH' => 'MHL', + 'MQ' => 'MTQ', + 'MR' => 'MRT', + 'MU' => 'MUS', + 'YT' => 'MYT', + 'MX' => 'MEX', + 'FM' => 'FSM', + 'MD' => 'MDA', + 'MC' => 'MCO', + 'MN' => 'MNG', + 'ME' => 'MNE', + 'MS' => 'MSR', + 'MA' => 'MAR', + 'MZ' => 'MOZ', + 'MM' => 'MMR', + 'NA' => 'NAM', + 'NR' => 'NRU', + 'NP' => 'NPL', + 'NL' => 'NLD', + 'AN' => 'ANT', + 'NC' => 'NCL', + 'NZ' => 'NZL', + 'NI' => 'NIC', + 'NE' => 'NER', + 'NG' => 'NGA', + 'NU' => 'NIU', + 'NF' => 'NFK', + 'MP' => 'MNP', + 'NO' => 'NOR', + 'OM' => 'OMN', + 'PK' => 'PAK', + 'PW' => 'PLW', + 'PS' => 'PSE', + 'PA' => 'PAN', + 'PG' => 'PNG', + 'PY' => 'PRY', + 'PE' => 'PER', + 'PH' => 'PHL', + 'PN' => 'PCN', + 'PL' => 'POL', + 'PT' => 'PRT', + 'PR' => 'PRI', + 'QA' => 'QAT', + 'RE' => 'REU', + 'RO' => 'ROU', + 'RU' => 'RUS', + 'RW' => 'RWA', + 'BL' => 'BLM', + 'SH' => 'SHN', + 'KN' => 'KNA', + 'LC' => 'LCA', + 'MF' => 'MAF', + 'PM' => 'SPM', + 'VC' => 'VCT', + 'WS' => 'WSM', + 'SM' => 'SMR', + 'ST' => 'STP', + 'SA' => 'SAU', + 'SN' => 'SEN', + 'RS' => 'SRB', + 'SC' => 'SYC', + 'SL' => 'SLE', + 'SG' => 'SGP', + 'SK' => 'SVK', + 'SI' => 'SVN', + 'SB' => 'SLB', + 'SO' => 'SOM', + 'ZA' => 'ZAF', + 'GS' => 'SGS', + 'SS' => 'SSD', + 'ES' => 'ESP', + 'LK' => 'LKA', + 'SD' => 'SDN', + 'SR' => 'SUR', + 'SJ' => 'SJM', + 'SZ' => 'SWZ', + 'SE' => 'SWE', + 'CH' => 'CHE', + 'SY' => 'SYR', + 'TW' => 'TWN', + 'TJ' => 'TJK', + 'TZ' => 'TZA', + 'TH' => 'THA', + 'TL' => 'TLS', + 'TG' => 'TGO', + 'TK' => 'TKL', + 'TO' => 'TON', + 'TT' => 'TTO', + 'TN' => 'TUN', + 'TR' => 'TUR', + 'TM' => 'TKM', + 'TC' => 'TCA', + 'TV' => 'TUV', + 'UG' => 'UGA', + 'UA' => 'UKR', + 'AE' => 'ARE', + 'GB' => 'GBR', + 'US' => 'USA', + 'UM' => 'UMI', + 'UY' => 'URY', + 'UZ' => 'UZB', + 'VU' => 'VUT', + 'VE' => 'VEN', + 'VN' => 'VNM', + 'VI' => 'VIR', + 'WF' => 'WLF', + 'EH' => 'ESH', + 'YE' => 'YEM', + 'ZM' => 'ZMB', + 'ZW' => 'ZWE', +); diff --git a/packages/woocommerce-germanized-dhl/includes/wc-gzd-dhl-core-functions.php b/packages/woocommerce-germanized-dhl/includes/wc-gzd-dhl-core-functions.php new file mode 100644 index 000000000..8efbce54a --- /dev/null +++ b/packages/woocommerce-germanized-dhl/includes/wc-gzd-dhl-core-functions.php @@ -0,0 +1,639 @@ +get_shipment() ) { + return false; + } + + $customs_data = $label->get_customs_data( $max_desc_length ); + + foreach ( $customs_data['items'] as $key => $item ) { + if ( $shipment_item = $shipment->get_item( $key ) ) { + /** + * Apply legacy filters + */ + $item['description'] = apply_filters( 'woocommerce_gzd_dhl_customs_item_description', $item['description'], $shipment_item, $label, $shipment ); + $item['category'] = apply_filters( 'woocommerce_gzd_dhl_customs_item_category', $item['category'], $shipment_item, $label, $shipment ); + + $customs_data['items'][ $key ] = apply_filters( 'woocommerce_gzd_dhl_customs_item', $item, $shipment_item, $shipment, $label ); + } + } + + return apply_filters( 'woocommerce_gzd_dhl_customs_data', $customs_data, $label, $shipment ); +} + +/** + * @param false|Shipment $shipment + * + * @return array + */ +function wc_gzd_dhl_get_label_payment_ref_placeholder( $shipment = false ) { + return apply_filters( + 'woocommerce_gzd_dhl_label_payment_ref_placeholder', + array( + '{shipment_id}' => $shipment ? $shipment->get_shipment_number() : '', + '{order_id}' => $shipment ? $shipment->get_order_number() : '', + '{email}' => $shipment ? $shipment->get_email() : '', + ) + ); +} + +function wc_gzd_dhl_get_preferred_days_select_options( $days, $current = '' ) { + $preferred_days = array( 0 => _x( 'None', 'dhl day context', 'woocommerce-germanized' ) ); + + if ( ! empty( $days ) ) { + $days = array_keys( $days ); + + foreach ( $days as $day ) { + if ( empty( $day ) ) { + continue; + } + + $date = new \WC_DateTime( $day ); + $date->setTimezone( new DateTimeZone( 'Europe/Berlin' ) ); + + $formatted_day = $date->date_i18n( wc_date_format() ); + $preferred_days = array_merge( $preferred_days, array( $day => $formatted_day ) ); + } + } + + if ( ! empty( $current ) ) { + $date = new \WC_DateTime( $current ); + $date->setTimezone( new DateTimeZone( 'Europe/Berlin' ) ); + + $preferred_days[ $current ] = $date->date_i18n( wc_date_format() ); + } + + return $preferred_days; +} + +function wc_gzd_dhl_get_duties() { + $duties = array( + 'DDP' => _x( 'Delivery Duty Paid', 'dhl', 'woocommerce-germanized' ), + 'DAP' => _x( 'Delivery At Place', 'dhl', 'woocommerce-germanized' ), + 'DXV' => _x( 'Delivery Duty Paid (excl. VAT )', 'dhl', 'woocommerce-germanized' ), + 'DDX' => _x( 'Delivery Duty Paid (excl. Duties, taxes and VAT)', 'dhl', 'woocommerce-germanized' ), + ); + + return $duties; +} + +function wc_gzd_dhl_is_valid_visual_min_age( $min_age ) { + $ages = wc_gzd_dhl_get_visual_min_ages(); + + if ( empty( $min_age ) || ( ! array_key_exists( $min_age, $ages ) && ! in_array( $min_age, $ages, true ) ) ) { + return false; + } + + return true; +} + +function wc_gzd_dhl_is_valid_ident_min_age( $min_age ) { + $ages = wc_gzd_dhl_get_ident_min_ages(); + + if ( empty( $min_age ) || ( ! array_key_exists( $min_age, $ages ) && ! in_array( $min_age, $ages, true ) ) ) { + return false; + } + + return true; +} + +function wc_gzd_dhl_get_visual_min_ages() { + $visual_age = array( + '0' => _x( 'None', 'age context', 'woocommerce-germanized' ), + 'A16' => _x( 'Minimum age of 16', 'dhl', 'woocommerce-germanized' ), + 'A18' => _x( 'Minimum age of 18', 'dhl', 'woocommerce-germanized' ), + ); + + return $visual_age; +} + +function wc_gzd_dhl_get_ident_min_ages() { + return wc_gzd_dhl_get_visual_min_ages(); +} + +function wc_gzd_dhl_get_label_reference( $reference_text, $placeholders = array() ) { + return str_replace( array_keys( $placeholders ), array_values( $placeholders ), $reference_text ); +} + +/** + * @param Label\Label $label + * @param Shipment $shipment + * + * @return string + */ +function wc_gzd_dhl_get_label_customer_reference( $label, $shipment ) { + /** + * Filter to adjust the customer reference field placed on the DHL label. Maximum characeter length: 35. + * + * @param string $text The customer reference text. + * @param Label\Label $label The label instance. + * @param SimpleShipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $ref = apply_filters( + 'woocommerce_gzd_dhl_label_customer_reference', + wc_gzd_dhl_get_label_reference( + _x( 'Shipment #{shipment_id} to order {order_id}', 'dhl', 'woocommerce-germanized' ), + array( + '{shipment_id}' => $shipment->get_shipment_number(), + '{order_id}' => $shipment->get_order_number(), + ) + ), + $label, + $shipment + ); + + return sanitize_text_field( substr( $ref, 0, 35 ) ); +} + +function wc_gzd_dhl_get_endorsement_types() { + return array( + 'return' => _x( 'Return shipment', 'dhl', 'woocommerce-germanized' ), + 'abandon' => _x( 'Abandon shipment', 'dhl', 'woocommerce-germanized' ), + ); +} + +/** + * @param Label\DHL $label + * @param $shipment + * @param string $api_type + * + * @return string + */ +function wc_gzd_dhl_get_label_endorsement_type( $label, $shipment, $api_type = 'default' ) { + $type = $label->get_endorsement(); + + /** + * Filter to adjust the endorsement type for internation shipments. + * + * @param string $text The endorsement type: return or abandon. + * @param Label\Label $label The label instance. + * @param SimpleShipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $type = strtolower( apply_filters( 'woocommerce_gzd_dhl_label_endorsement_type', $type, $label, $shipment ) ); + + /** + * SOAP Label API was using IMMEDIATE instead of RETURN + */ + if ( 'immediate' === $type ) { + $type = 'return'; + } elseif ( 'abandonment' === $type ) { + $type = 'abandon'; + } + + if ( ! in_array( $type, array_keys( wc_gzd_dhl_get_endorsement_types() ), true ) ) { + $type = 'return'; + } + + /** + * The SOAP API uses abandonment instead of abandon and + * immediate instead of return. + */ + if ( 'default' === $api_type ) { + if ( 'abandon' === $type ) { + $type = 'abandonment'; + } else { + $type = 'immediate'; + } + } + + return strtoupper( $type ); +} + +function wc_gzd_dhl_get_return_label_customer_reference( $label, $shipment ) { + /** + * Filter to adjust the customer reference field placed on the DHL return label. Maximum characeter length: 30. + * + * @param string $text The customer reference text. + * @param Label\Label $label The label instance. + * @param ReturnShipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $ref = apply_filters( + 'woocommerce_gzd_dhl_return_label_customer_reference', + wc_gzd_dhl_get_label_reference( + _x( 'Return #{shipment_id} to order {order_id}', 'dhl', 'woocommerce-germanized' ), + array( + '{shipment_id}' => $shipment->get_id(), + '{order_id}' => $shipment->get_order_number(), + ) + ), + $label, + $shipment + ); + + return sanitize_text_field( substr( $ref, 0, 30 ) ); +} + +function wc_gzd_dhl_get_inlay_return_label_reference( $label, $shipment ) { + /** + * Filter to adjust the inlay return reference field placed on the DHL label. Maximum characeter length: 35. + * + * @param string $text The customer reference text. + * @param Label\Label $label The label instance. + * @param SimpleShipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $ref = apply_filters( + 'woocommerce_gzd_dhl_inlay_return_label_reference', + wc_gzd_dhl_get_label_reference( + _x( 'Return shipment #{shipment_id} to order #{order_id}', 'dhl', 'woocommerce-germanized' ), + array( + '{shipment_id}' => $shipment->get_id(), + '{order_id}' => $shipment->get_order_number(), + ) + ), + $label, + $shipment + ); + + return sanitize_text_field( substr( $ref, 0, 35 ) ); +} + +/** + * @return \Vendidero\Germanized\Shipments\ShippingMethod\ProviderMethod|false + */ +function wc_gzd_dhl_get_current_shipping_method() { + if ( $current = wc_gzd_get_current_shipping_method_id() ) { + return wc_gzd_get_shipping_provider_method( $current ); + } + + return false; +} + +/** + * @param $instance_id + * + * @return \Vendidero\Germanized\Shipments\ShippingMethod\ProviderMethod + */ +function wc_gzd_dhl_get_shipping_method( $instance_id ) { + return wc_gzd_get_shipping_provider_method( $instance_id ); +} + +function wc_gzd_dhl_get_deutsche_post_shipping_method( $instance_id ) { + return wc_gzd_dhl_get_shipping_method( $instance_id ); +} + +function wc_gzd_dhl_get_pickup_types() { + return array( + 'packstation' => _x( 'Packstation', 'dhl', 'woocommerce-germanized' ), + 'postoffice' => _x( 'Postfiliale', 'dhl', 'woocommerce-germanized' ), + 'parcelshop' => _x( 'Postfiliale', 'dhl', 'woocommerce-germanized' ), + ); +} + +function wc_gzd_dhl_is_pickup_type( $maybe_type, $type = 'packstation' ) { + $label = wc_gzd_dhl_get_pickup_type( $type ); + + if ( ! $label ) { + return false; + } + + $label = strtolower( trim( $label ) ); + $maybe_type = strtolower( trim( $maybe_type ) ); + + if ( strpos( $maybe_type, $label ) !== false ) { + return true; + } + + return false; +} + +function wc_gzd_dhl_get_excluded_working_days() { + $work_days = array( + 'mon', + 'tue', + 'wed', + 'thu', + 'fri', + 'sat', + ); + + $excluded = array(); + + foreach ( $work_days as $value ) { + if ( ParcelServices::is_preferred_day_excluded( $value ) ) { + $excluded[] = $value; + } + } + + return $excluded; +} + +function wc_gzd_dhl_order_has_pickup( $order ) { + return ParcelLocator::order_has_pickup( $order ); +} + +function wc_gzd_dhl_get_pickup_type( $type ) { + $types = wc_gzd_dhl_get_pickup_types(); + + if ( array_key_exists( $type, $types ) ) { + return $types[ $type ]; + } elseif ( in_array( $type, $types, true ) ) { + return $type; + } else { + return false; + } +} + +/** + * @param WP_Error $error + * + * @return bool + */ +function wc_gzd_dhl_wp_error_has_errors( $error ) { + return wc_gzd_shipment_wp_error_has_errors( $error ); +} + +function wc_gzd_dhl_is_valid_datetime( $maybe_datetime, $format = 'Y-m-d' ) { + if ( ! is_a( $maybe_datetime, 'DateTime' && ! is_numeric( $maybe_datetime ) ) ) { + if ( ! DateTime::createFromFormat( $format, $maybe_datetime ) ) { + return false; + } + } + + return true; +} + +function wc_gzd_dhl_format_label_state( $state, $country ) { + // If not USA or Australia, then change state from ISO code to name + if ( ! in_array( $country, array( 'US', 'AU' ), true ) ) { + // Get all states for a country + $states = WC()->countries->get_states( $country ); + + // If the state is empty, it was entered as free text + if ( ! empty( $states ) && ! empty( $state ) ) { + // Change the state to be the name and not the code + $state = $states[ $state ]; + + // Remove anything in parentheses (e.g. TH) + $ind = strpos( $state, ' (' ); + + if ( false !== $ind ) { + $state = substr( $state, 0, $ind ); + } + } + } + + return $state; +} + +/** + * @param $the_product + * + * @return \Vendidero\Germanized\Shipments\Product + */ +function wc_gzd_dhl_get_product( $the_product ) { + return wc_gzd_shipments_get_product( $the_product ); +} + +/** + * @param Shipment $shipment + */ +function wc_gzd_dhl_get_label_shipment_address_addition( $shipment ) { + return wc_gzd_get_shipment_address_addition( $shipment ); +} + +/** + * @param Shipment $shipment + * + * @return mixed + */ +function wc_gzd_dhl_get_label_shipment_street_number( $shipment ) { + $street_number = $shipment->get_address_street_number(); + + if ( ! Package::is_shipping_domestic( $shipment->get_country(), $shipment->get_postcode() ) ) { + + if ( empty( $street_number ) ) { + /** + * Filter to adjust the placeholder used as street number for the DHL API in case + * the shipment is not domestic (inner Germnany) and a street number was not provided. + * + * @param string $placeholder The placeholder to use - default 0 as advised by DHL support. + * + * @since 3.1.0 + * @package Vendidero/Germanized/DHL + */ + $street_number = apply_filters( 'woocommerce_gzd_dhl_label_shipment_street_number_placeholder', '0' ); + } + } + + return $street_number; +} + +/** + * @param \Vendidero\Germanized\DHL\Label\ReturnLabel $label + */ +function wc_gzd_dhl_get_return_label_sender_street_number( $label ) { + $street_number = $label->get_sender_street_number(); + + if ( ! Package::is_shipping_domestic( $label->get_sender_country(), $label->get_sender_postcode() ) ) { + if ( empty( $street_number ) ) { + /** + * This filter is documented in includes/wc-gzd-dhl-core-functions.php + */ + $street_number = apply_filters( 'woocommerce_gzd_dhl_label_shipment_street_number_placeholder', '0' ); + } + } + + return $street_number; +} + +/** + * @param Label\DHL $label + * @param string $type + * + * @return mixed|string|void + * @see https://entwickler.dhl.de/group/ep/grundlagen2 + */ +function wc_gzd_dhl_get_custom_label_format( $label, $type = '' ) { + $shipment = $label->get_shipment(); + $available = $shipment ? $shipment->get_shipping_provider_instance()->get_print_formats()->filter( array( 'product_id' => $label->get_product_id() ) )->as_options() : array(); + $label_format = 'default' === $label->get_print_format() ? '' : $label->get_print_format(); + + /** + * This filter allows adjusting the default label format (GUI) to a custom format e.g. 910-300-700. + * The following formats are available: + * + *easily create DHL labels to your shipments.', 'dhl', 'woocommerce-germanized' ), esc_url( 'https://vendidero.de/dokument/dhl-labels-zu-sendungen-erstellen' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
+'EUR' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
++ | + | + |
---|---|---|
+ | + + | ++ + | +
' . esc_html_x( 'This label has been generated by the DHL for WooCommerce Plugin and is shown for legacy purposes.', 'dhl', 'woocommerce-germanized' ) . '
'; + echo '' . esc_html_x( 'Download label', 'dhl', 'woocommerce-germanized' ) . ''; + } + } + + public static function get_legacy_label_download_url( $order_id ) { + $url = add_query_arg( + array( + 'action' => 'wc-gzd-dhl-download-legacy-label', + 'order_id' => $order_id, + 'force' => 'yes', + ), + wp_nonce_url( admin_url(), 'dhl-download-legacy-label' ) + ); + + return esc_url_raw( $url ); + } + + public static function download_legacy_label() { + if ( isset( $_GET['action'] ) && 'wc-gzd-dhl-download-legacy-label' === $_GET['action'] && isset( $_REQUEST['_wpnonce'] ) ) { + if ( isset( $_GET['order_id'] ) && wp_verify_nonce( wp_unslash( $_REQUEST['_wpnonce'] ), 'dhl-download-legacy-label' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $order_id = absint( $_GET['order_id'] ); + $args = \Vendidero\Germanized\Shipments\Labels\DownloadHandler::parse_args( + array( + 'force' => wc_string_to_bool( isset( $_GET['force'] ) ? wc_clean( wp_unslash( $_GET['force'] ) ) : false ), + ) + ); + + if ( current_user_can( 'edit_shop_orders' ) ) { + if ( $order = wc_get_order( $order_id ) ) { + $meta = (array) $order->get_meta( '_pr_shipment_dhl_label_tracking' ); + + if ( ! empty( $meta ) ) { + $path = $meta['label_path']; + + if ( file_exists( $path ) ) { + $filename = basename( $path ); + + \Vendidero\Germanized\Shipments\Labels\DownloadHandler::download( $path, $filename, $args['force'] ); + } + } + } + } + } + } + } + + public static function admin_scripts() { + global $post; + + $screen = get_current_screen(); + $screen_id = $screen ? $screen->id : ''; + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + + wp_register_script( 'wc-gzd-admin-dhl-internetmarke', Package::get_assets_build_url( 'static/admin-internetmarke.js' ), array( 'jquery' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + wp_register_script( 'wc-gzd-admin-deutsche-post-label', Package::get_assets_build_url( 'static/admin-deutsche-post-label.js' ), array( 'wc-gzd-admin-shipment-modal' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + + if ( wp_script_is( 'wc-gzd-admin-shipment-modal', 'enqueued' ) ) { + wp_enqueue_script( 'wc-gzd-admin-deutsche-post-label' ); + + wp_localize_script( + 'wc-gzd-admin-deutsche-post-label', + 'wc_gzd_admin_deutsche_post_label_params', + array( + 'refresh_label_preview_nonce' => wp_create_nonce( 'wc-gzd-dhl-refresh-deutsche-post-label-preview' ), + ) + ); + } + + // Shipping zone methods + if ( 'woocommerce_page_wc-settings' === $screen_id && isset( $_GET['provider'] ) && 'deutsche_post' === $_GET['provider'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + wp_enqueue_script( 'wc-gzd-admin-dhl-internetmarke' ); + } + } + + public static function admin_styles() { + $screen = get_current_screen(); + $screen_id = $screen ? $screen->id : ''; + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + + // Register admin styles. + wp_register_style( 'woocommerce_gzd_dhl_admin', Package::get_assets_build_url( 'static/admin-styles.css' ), array( 'woocommerce_admin_styles' ), Package::get_version() ); + + // Admin styles for WC pages only. + if ( in_array( $screen_id, self::get_screen_ids(), true ) ) { + wp_enqueue_style( 'woocommerce_gzd_dhl_admin' ); + } + } + + public static function get_screen_ids() { + return \Vendidero\Germanized\Shipments\Admin\Admin::get_screen_ids(); + } +} diff --git a/packages/woocommerce-germanized-dhl/src/Admin/Importer/DHL.php b/packages/woocommerce-germanized-dhl/src/Admin/Importer/DHL.php new file mode 100644 index 000000000..56d6fe33b --- /dev/null +++ b/packages/woocommerce-germanized-dhl/src/Admin/Importer/DHL.php @@ -0,0 +1,195 @@ +get_customer_number(); + } + + return ( ( ! empty( $options ) && empty( $user ) && 'yes' !== $imported && Package::base_country_is_supported() ) ? true : false ); + } + + public static function is_plugin_enabled() { + return class_exists( 'PR_DHL_WC' ) ? true : false; + } + + public static function import_settings() { + $old_settings = (array) get_option( 'woocommerce_pr_dhl_paket_settings' ); + $dhl = Package::get_dhl_shipping_provider(); + + if ( ! $dhl ) { + return false; + } + + $settings_mapping = array( + 'account_num' => 'account_number', + 'participation_V01PAK' => 'participation_V01PAK', + 'participation_V01PRIO' => 'participation_V01PRIO', + 'participation_V06PAK' => 'participation_V06PAK', + 'participation_V55PAK' => 'participation_V55PAK', + 'participation_V54EPAK' => 'participation_V54EPAK', + 'participation_V53WPAK' => 'participation_V53WPAK', + 'participation_V62WP' => 'participation_V62WP', + 'participation_V66WPI' => 'participation_V66WPI', + 'participation_return' => 'participation_return', + 'api_user' => 'api_username', + 'api_pwd' => 'api_password', + 'default_product_dom' => 'label_default_product_dom', + 'default_product_int' => 'label_default_product_int', + 'default_print_codeable' => 'label_address_codeable_only', + 'bank_holder' => 'bank_holder', + 'bank_name' => 'bank_name', + 'bank_iban' => 'bank_iban', + 'bank_bic' => 'bank_bic', + 'bank_ref' => 'bank_ref', + 'bank_ref_2' => 'bank_ref_2', + 'preferred_day' => 'PreferredDay_enable', + 'preferred_day_cost' => 'PreferredDay_cost', + 'preferred_day_cutoff' => 'PreferredDay_cutoff_time', + 'preferred_exclusion_mon' => 'PreferredDay_exclusion_mon', + 'preferred_exclusion_tue' => 'PreferredDay_exclusion_tue', + 'preferred_exclusion_wed' => 'PreferredDay_exclusion_wed', + 'preferred_exclusion_thu' => 'PreferredDay_exclusion_thu', + 'preferred_exclusion_fri' => 'PreferredDay_exclusion_fri', + 'preferred_exclusion_sat' => 'PreferredDay_exclusion_sat', + 'preferred_location' => 'PreferredLocation_enable', + 'preferred_neighbour' => 'PreferredNeighbour_enable', + 'payment_gateway' => 'preferred_payment_gateways_excluded', + 'display_packstation' => 'parcel_pickup_packstation_enable', + 'display_parcelshop' => 'parcel_pickup_parcelshop_enable', + 'display_post_office' => 'parcel_pickup_postoffice_enable', + 'parcel_limit' => 'parcel_pickup_map_max_results', + 'google_maps_api_key' => 'parcel_pickup_map_api_password', + ); + + // Bulk update settings + foreach ( $settings_mapping as $setting_old_key => $setting_new_key ) { + if ( isset( $old_settings[ 'dhl_' . $setting_old_key ] ) && ! empty( $old_settings[ 'dhl_' . $setting_old_key ] ) ) { + $dhl->update_setting( $setting_new_key, $old_settings[ 'dhl_' . $setting_old_key ] ); + } + } + + /** + * Default address update + */ + foreach ( array( 'shipper', 'return' ) as $address_type ) { + $plain_address = array( + 'company' => 'company', + 'address_city' => 'city', + 'address_zip' => 'postcode', + 'phone' => 'phone', + 'email' => 'email', + ); + + foreach ( $plain_address as $prop => $new_prop ) { + $prop_name = $address_type . '_' . $prop; + + if ( ! empty( $old_settings[ 'dhl_' . $prop_name ] ) ) { + update_option( "woocommerce_gzd_shipments_{$address_type}_address_{$new_prop}", $old_settings[ 'dhl_' . $prop_name ] ); + } + } + + if ( ! empty( $old_settings[ "dhl_{$address_type}_address" ] ) ) { + $address_1 = $old_settings[ "dhl_{$address_type}_address" ] . ' ' . ( isset( $old_settings[ "dhl_{$address_type}_address_no" ] ) ? $old_settings[ "dhl_{$address_type}_address_no" ] : '' ); + + update_option( "woocommerce_gzd_shipments_{$address_type}_address_address_1", $address_1 ); + } + + if ( ! empty( $old_settings[ "dhl_{$address_type}_name" ] ) ) { + $name = explode( ' ', $old_settings[ "dhl_{$address_type}_name" ] ); + $name_first = $name; + $first_name = implode( ' ', array_splice( $name_first, 0, ( count( $name ) - 1 ) ) ); + $last_name = $name[ count( $name ) - 1 ]; + + update_option( "woocommerce_gzd_shipments_{$address_type}_address_first_name", $first_name ); + update_option( "woocommerce_gzd_shipments_{$address_type}_address_last_name", $last_name ); + } + } + + // Enable maps if API key exists + if ( isset( $settings['dhl_google_maps_api_key'] ) && ! empty( $settings['dhl_google_maps_api_key'] ) ) { + $dhl->update_setting( 'parcel_pickup_map_enable', 'yes' ); + } + + // Shipper state to country ISO mapping + $countries = WC()->countries; + $shipper_country = ( isset( $old_settings['dhl_shipper_address_state'] ) && ! empty( $old_settings['dhl_shipper_address_state'] ) ) ? $old_settings['dhl_shipper_address_state'] : ''; + $return_country = ( isset( $old_settings['dhl_return_address_state'] ) && ! empty( $old_settings['dhl_return_address_state'] ) ) ? $old_settings['dhl_return_address_state'] : ''; + $isos = ( $countries ) ? $countries->get_countries() : array(); + + if ( ! empty( $shipper_country ) && ! empty( $isos ) ) { + if ( ( $key = array_search( $shipper_country, $isos, true ) ) !== false ) { + update_option( 'woocommerce_gzd_shipments_shipper_address_country', $key ); + } + } + + if ( ! empty( $return_country ) && ! empty( $isos ) ) { + if ( ( $key = array_search( $return_country, $isos, true ) ) !== false ) { + update_option( 'woocommerce_gzd_shipments_return_address_country', $key ); + } + } + + $dhl->save(); + + return true; + } + + public static function import_order_data( $limit = 10, $offset = 0 ) { + + $orders = wc_get_orders( + array( + 'limit' => $limit, + 'offset' => $offset, + 'orderby' => 'date', + 'order' => 'DESC', + 'type' => 'shop_order', + ) + ); + + if ( ! empty( $orders ) ) { + foreach ( $orders as $order ) { + + if ( ! $order->get_meta( '_shipping_address_type' ) ) { + + // Update order pickup type from official DHL plugin + if ( self::order_has_pickup( $order ) ) { + + $order->update_meta_data( '_shipping_address_type', 'dhl' ); + $order->update_meta_data( '_shipping_dhl_postnumber', $order->get_meta( '_shipping_dhl_postnum' ) ); + + // Remove data to make sure we do not show data twice + $order->delete_meta_data( '_shipping_dhl_address_type' ); + $order->delete_meta_data( '_shipping_dhl_postnum' ); + + $order->save(); + } + } + } + } + } + + protected static function order_has_pickup( $order ) { + $pos_ps = stripos( $order->get_shipping_address_1(), 'Packstation' ); + $pos_fl = stripos( $order->get_shipping_address_1(), 'Postfiliale' ); + + if ( false !== $pos_ps || false !== $pos_fl ) { + return true; + } + + return false; + } +} diff --git a/packages/woocommerce-germanized-dhl/src/Admin/Importer/Internetmarke.php b/packages/woocommerce-germanized-dhl/src/Admin/Importer/Internetmarke.php new file mode 100644 index 000000000..26abfb1de --- /dev/null +++ b/packages/woocommerce-germanized-dhl/src/Admin/Importer/Internetmarke.php @@ -0,0 +1,54 @@ +get_api_username(); + } + + return ( ( ! empty( $options ) && empty( $user ) && 'yes' !== $imported && Package::base_country_is_supported() ) ? true : false ); + } + + public static function is_plugin_enabled() { + return defined( 'WCDPI_PLUGIN_FILE' ) ? true : false; + } + + public static function import_settings() { + $old_settings = array_merge( (array) get_option( '_wcdpi_settings_portokasse' ), (array) get_option( '_wcdpi_settings_internetmarke_1c4a' ) ); + + $settings_mapping = array( + '_wcdpi_portokasse_email' => 'api_username', + '_wcdpi_portokasse_password' => 'api_password', + ); + + $deutsche_post = Package::get_deutsche_post_shipping_provider(); + + if ( ! $deutsche_post ) { + return false; + } + + // Bulk update settings + foreach ( $settings_mapping as $setting_old_key => $setting_new_key ) { + if ( isset( $old_settings[ $setting_old_key ] ) ) { + $deutsche_post->update_setting( $setting_new_key, $old_settings[ $setting_old_key ] ); + } + } + + $deutsche_post->save(); + return true; + } +} diff --git a/packages/woocommerce-germanized-dhl/src/Admin/Status.php b/packages/woocommerce-germanized-dhl/src/Admin/Status.php new file mode 100644 index 000000000..953d07589 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/src/Admin/Status.php @@ -0,0 +1,118 @@ + ++ + | +||
---|---|---|
|
+ + '; + } else { + echo ' ' . esc_html_x( 'Unable to connect to the URL. Please make sure that your webhost allows outgoing connections to that specific URL.', 'dhl', 'woocommerce-germanized' ) . ''; + } + ?> + | +
+ + | +||
---|---|---|
: | ++ | ' : '–' ); ?> | +
SOAPClient is required. Please contact your host and make sure that SOAPClient is installed.', 'dhl', 'woocommerce-germanized' ), 'https://www.php.net/manual/class.soapclient.php', admin_url( 'admin.php?page=wc-status' ) ) ); ?>
+' . esc_html( $ref_placeholders_str ) . '
' ) . '' . esc_html( $ref_placeholders_str ) . '
' ) . '' . esc_html_x( 'Insert your DHL business customer number (EKP) here. If you are not yet a business customer you might want to create a new account first.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'api' => array( + 'target' => Package::is_debug_mode() ? '#api_sandbox_username' : '#api_username', + 'next' => '', + 'next_url' => add_query_arg( array( 'tutorial' => 'yes' ), $this->get_edit_link( 'label' ) ), + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'To create labels and embed DHL services, our software needs access to the API. You will need to fill out the username and password fields accordingly.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + ), + ); + } elseif ( 'label' === $section ) { + $pointers = array( + 'pointers' => array( + 'inlay' => array( + 'target' => '#label_auto_inlay_return_label-toggle', + 'next' => 'retoure', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'If you want to provide your customers with inlay return labels for your shipments you might enable this feature by default here.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'retoure' => array( + 'target' => '#label_retoure_enable-toggle', + 'next' => 'age_check', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'If you want to create DHL labels to returns you should activate this feature. Make sure that you have DHL Online Retoure activated in your contract.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'age_check' => array( + 'target' => '#label_auto_age_check_sync-toggle', + 'next' => '', + 'next_url' => add_query_arg( array( 'tutorial' => 'yes' ), $this->get_edit_link( 'automation' ) ), + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'Use this feature to sync the Germanized age verification checkbox with the DHL visual minimum age verification service. As soon as applicable products are contained within the shipment, the service will be booked by default.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + ), + ); + } elseif ( 'automation' === $section ) { + $pointers = array( + 'pointers' => array( + 'auto' => array( + 'target' => '#label_auto_enable-toggle', + 'next' => '', + 'next_url' => add_query_arg( array( 'tutorial' => 'yes' ), $this->get_edit_link( 'preferred' ) ), + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'You might want to save some time and let Germanized generate labels automatically as soon as a shipment switches to a certain status.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + ), + ); + } elseif ( 'preferred' === $section ) { + $pointers = array( + 'pointers' => array( + 'day' => array( + 'target' => '#PreferredDay_enable-toggle', + 'next' => 'fee', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'Let your customers choose a delivery day (if the service is available at the customer\'s location) of delivery within your checkout.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'fee' => array( + 'target' => '#PreferredDay_cost', + 'next' => 'location', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'Optionally charge your customers an additional fee for preferred services like delivery day.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'location' => array( + 'target' => '#PreferredLocation_enable-toggle', + 'next' => '', + 'next_url' => add_query_arg( array( 'tutorial' => 'yes' ), $this->get_edit_link( 'pickup' ) ), + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'Allow your customers to send their parcels to a drop-off location e.g. a neighbor. This service is free of charge for DHL shipments.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + ), + ); + } elseif ( 'pickup' === $section ) { + $pointers = array( + 'pointers' => array( + 'day' => array( + 'target' => '#parcel_pickup_packstation_enable-toggle', + 'next' => 'map', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'Allow your customers to choose packstation (and/or other DHL location types as configured below) as shipping address.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'map' => array( + 'target' => '#parcel_pickup_map_enable-toggle', + 'next' => '', + 'next_url' => ProviderSettings::get_next_pointers_link( $this->get_name() ), + 'next_trigger' => array(), + 'options' => array( + 'content' => '' . esc_html_x( 'This option adds a map overlay view to let your customers choose a DHL location from a map nearby. You\'ll need a valid Google Maps API key to enable the map view.', 'dhl', 'woocommerce-germanized' ) . '
', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + ), + ); + } + + return $pointers; + } + + public function get_supported_label_config_set_shipment_types() { + return array( 'simple' ); + } +} diff --git a/packages/woocommerce-germanized-dhl/src/ShippingProvider/DeutschePost.php b/packages/woocommerce-germanized-dhl/src/ShippingProvider/DeutschePost.php new file mode 100644 index 000000000..cb047a035 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/src/ShippingProvider/DeutschePost.php @@ -0,0 +1,623 @@ +get_shipping_country() ) { + return false; + } + + return parent::supports_customer_returns( $order ); + } + + public function supports_labels( $label_type, $shipment = false ) { + $label_types = array( 'simple', 'return' ); + + /** + * Return labels are only supported for DE + */ + if ( 'return' === $label_type && $shipment && 'return' === $shipment->get_type() && 'DE' !== $shipment->get_sender_country() ) { + return false; + } + + return in_array( $label_type, $label_types, true ); + } + + public function get_title( $context = 'view' ) { + return _x( 'Deutsche Post', 'dhl', 'woocommerce-germanized' ); + } + + public function get_name( $context = 'view' ) { + return 'deutsche_post'; + } + + public function get_description( $context = 'view' ) { + return _x( 'Integration for products of the Deutsche Post through Internetmarke.', 'dhl', 'woocommerce-germanized' ); + } + + public function get_default_tracking_url_placeholder() { + return 'https://deutschepost.de/de/s/sendungsverfolgung.html?piececode={tracking_id}'; + } + + public function get_api_username( $context = 'view' ) { + return $this->get_meta( 'api_username', true, $context ); + } + + public function set_api_username( $username ) { + $this->update_meta_data( 'api_username', strtolower( $username ) ); + } + + protected function get_available_base_countries() { + return Package::get_available_countries(); + } + + protected function get_connection_status_html( $maybe_error ) { + return '' . ( sprintf( _x( 'Status: %1$s', 'dhl', 'woocommerce-germanized' ), ( is_wp_error( $maybe_error ) ? $maybe_error->get_error_message() : _x( 'Connected', 'dhl', 'woocommerce-germanized' ) ) ) ) . ''; + } + + protected function get_printing_settings() { + $settings = parent::get_printing_settings(); + $settings_url = $this->get_edit_link( '' ); + + $settings = array_merge( + array( + array( + 'title' => _x( 'Printing', 'dhl', 'woocommerce-germanized' ), + 'type' => 'title', + 'id' => 'shipping_provider_label_printing_options', + 'desc' => '
+ composer install',
+ '' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '
'
+ );
+ ?>
+
' + message + '
' + message + '
'+e+'
'+e+'
+ + +
++ + + + has_packaging() ? 'disabled="disabled"' : '' ); ?> size="6" class="wc_input_decimal wc-gzd-shipment-dimension has_packaging() ? 'disabled' : '' ); ?>" value="get_length( 'edit' ) ) ); ?>" name="shipment_length[get_id() ); ?>]" id="shipment-length-get_id() ); ?>" placeholder="get_content_length() ) ); ?>" /> + has_packaging() ? 'disabled="disabled"' : '' ); ?> size="6" class="wc_input_decimal wc-gzd-shipment-dimension has_packaging() ? 'disabled' : '' ); ?>" value="get_width( 'edit' ) ) ); ?>" name="shipment_width[get_id() ); ?>]" id="shipment-width-get_id() ); ?>" placeholder="get_content_width() ) ); ?>" /> + has_packaging() ? 'disabled="disabled"' : '' ); ?> size="6" class="wc_input_decimal wc-gzd-shipment-dimension has_packaging() ? 'disabled' : '' ); ?>" value="get_height( 'edit' ) ) ); ?>" name="shipment_height[get_id() ); ?>]" id="shipment-height-get_id() ); ?>" placeholder="get_content_height() ) ); ?>" /> + +
++ + + +
++ + +
+ + get_available_shipping_methods() ) > 1 ) : ?> ++ + +
+ + ++ + +
+ ++ + +
+ + ++ | + | + | + | + |
---|---|---|---|---|
+
+
+
+
+
+ |
+ + get_title() ); ?> + + | +
+ get_description() ); ?> + |
+ + + | ++ get_help_link() ) : ?> + + + + + | +
' . esc_html( implode( '
, ', array_keys( $this->placeholders ) ) ) . '
' );
+
+ $this->form_fields = array(
+ 'enabled' => array(
+ 'title' => _x( 'Enable/Disable', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'checkbox',
+ 'label' => _x( 'Enable this email notification', 'shipments', 'woocommerce-germanized' ),
+ 'default' => 'yes',
+ ),
+ 'subject_full' => array(
+ 'title' => _x( 'Full shipment subject', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => $placeholder_text,
+ 'placeholder' => $this->get_default_subject(),
+ 'default' => '',
+ ),
+ 'subject_partial' => array(
+ 'title' => _x( 'Partial shipment subject', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => $placeholder_text,
+ 'placeholder' => $this->get_default_subject( true ),
+ 'default' => '',
+ ),
+ 'heading_full' => array(
+ 'title' => _x( 'Full shipment email heading', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => $placeholder_text,
+ 'placeholder' => $this->get_default_heading(),
+ 'default' => '',
+ ),
+ 'heading_partial' => array(
+ 'title' => _x( 'Partial shipment email heading', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => $placeholder_text,
+ 'placeholder' => $this->get_default_heading( true ),
+ 'default' => '',
+ ),
+ 'additional_content' => array(
+ 'title' => _x( 'Additional content', 'shipments', 'woocommerce-germanized' ),
+ 'description' => _x( 'Text to appear below the main email content.', 'shipments', 'woocommerce-germanized' ) . ' ' . $placeholder_text,
+ 'css' => 'width:400px; height: 75px;',
+ 'placeholder' => _x( 'N/A', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'textarea',
+ 'default' => $this->get_default_additional_content(),
+ 'desc_tip' => true,
+ ),
+ 'email_type' => array(
+ 'title' => _x( 'Email type', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'select',
+ 'description' => _x( 'Choose which format of email to send.', 'shipments', 'woocommerce-germanized' ),
+ 'default' => 'html',
+ 'class' => 'email_type wc-enhanced-select',
+ 'options' => $this->get_email_type_options(),
+ 'desc_tip' => true,
+ ),
+ );
+ }
+ }
+
+endif;
+
+return new WC_GZD_Email_Customer_Shipment();
diff --git a/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-new-return-shipment-request.php b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-new-return-shipment-request.php
new file mode 100644
index 000000000..447beca08
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-new-return-shipment-request.php
@@ -0,0 +1,221 @@
+id = 'new_return_shipment_request';
+ $this->title = _x( 'New order return request', 'shipments', 'woocommerce-germanized' );
+ $this->description = _x( 'New order return request emails are sent to chosen recipient(s) when a new return is requested.', 'shipments', 'woocommerce-germanized' );
+
+ $this->template_html = 'emails/admin-new-return-shipment-request.php';
+ $this->template_plain = 'emails/plain/admin-new-return-shipment-request.php';
+ $this->template_base = Package::get_path() . '/templates/';
+
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{shipment_number}' => '',
+ '{order_number}' => '',
+ '{order_date}' => '',
+ );
+
+ // Triggers for this email.
+ add_action( 'woocommerce_gzd_new_customer_return_shipment_request', array( $this, 'trigger' ), 10 );
+
+ // Call parent constructor.
+ parent::__construct();
+
+ // Other settings.
+ $this->recipient = $this->get_option( 'recipient', get_option( 'admin_email' ) );
+ }
+
+ /**
+ * Get email subject.
+ *
+ * @since 3.1.0
+ * @return string
+ */
+ public function get_default_subject() {
+ return _x( '[{site_title}]: New return request to #{order_number}', 'shipments', 'woocommerce-germanized' );
+ }
+
+ /**
+ * Get email heading.
+ *
+ * @since 3.1.0
+ * @return string
+ */
+ public function get_default_heading() {
+ return _x( 'New return request to: #{order_number}', 'shipments', 'woocommerce-germanized' );
+ }
+
+ /**
+ * Trigger.
+ *
+ * @param int|ReturnShipment $shipment_id Shipment ID.
+ */
+ public function trigger( $shipment_id ) {
+ $this->setup_locale();
+
+ if ( $this->shipment = wc_gzd_get_shipment( $shipment_id ) ) {
+
+ if ( 'return' !== $this->shipment->get_type() ) {
+ return;
+ }
+
+ $this->placeholders['{shipment_number}'] = $this->shipment->get_shipment_number();
+
+ if ( $order_shipment = wc_gzd_get_shipment_order( $this->shipment->get_order() ) ) {
+ $this->object = $this->shipment->get_order();
+ $this->placeholders['{order_date}'] = wc_format_datetime( $order_shipment->get_order()->get_date_created() );
+ $this->placeholders['{order_number}'] = $order_shipment->get_order()->get_order_number();
+ }
+ }
+
+ if ( $this->is_enabled() && $this->get_recipient() ) {
+ $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
+ }
+
+ $this->restore_locale();
+ }
+
+ /**
+ * Return content from the additional_content field.
+ *
+ * Displayed above the footer.
+ *
+ * @since 2.0.4
+ * @return string
+ */
+ public function get_additional_content() {
+ if ( is_callable( 'parent::get_additional_content' ) ) {
+ return parent::get_additional_content();
+ }
+
+ return '';
+ }
+
+ /**
+ * Get content html.
+ *
+ * @return string
+ */
+ public function get_content_html() {
+ return wc_get_template_html(
+ $this->template_html,
+ array(
+ 'shipment' => $this->shipment,
+ 'order' => $this->object,
+ 'email_heading' => $this->get_heading(),
+ 'additional_content' => $this->get_additional_content(),
+ 'sent_to_admin' => true,
+ 'plain_text' => false,
+ 'email' => $this,
+ )
+ );
+ }
+
+ /**
+ * Get content plain.
+ *
+ * @return string
+ */
+ public function get_content_plain() {
+ return wc_get_template_html(
+ $this->template_plain,
+ array(
+ 'shipment' => $this->shipment,
+ 'order' => $this->object,
+ 'email_heading' => $this->get_heading(),
+ 'additional_content' => $this->get_additional_content(),
+ 'sent_to_admin' => true,
+ 'plain_text' => true,
+ 'email' => $this,
+ )
+ );
+ }
+
+ public function get_attachments() {
+ $attachments = array();
+
+ if ( $this->shipment->has_label() ) {
+ $label = $this->shipment->get_label();
+
+ if ( $file = $label->get_file() ) {
+ $attachments[] = $file;
+ }
+ }
+
+ return apply_filters( 'woocommerce_email_attachments', $attachments, $this->id, $this->object, $this );
+ }
+
+ /**
+ * Default content to show below main email content.
+ *
+ * @since 1.0.1
+ * @return string
+ */
+ public function get_default_additional_content() {
+ return '';
+ }
+
+ /**
+ * Initialise settings form fields.
+ */
+ public function init_form_fields() {
+ parent::init_form_fields();
+
+ $this->form_fields = array_merge(
+ $this->form_fields,
+ array(
+ 'recipient' => array(
+ 'title' => _x( 'Recipient(s)', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'text',
+ /* translators: %s: WP admin email */
+ 'description' => sprintf( _x( 'Enter recipients (comma separated) for this email. Defaults to %s.', 'shipments', 'woocommerce-germanized' ), '' . esc_attr( get_option( 'admin_email' ) ) . '
' ),
+ 'placeholder' => '',
+ 'default' => '',
+ 'desc_tip' => true,
+ ),
+ )
+ );
+ }
+ }
+
+endif;
+
+return new WC_GZD_Email_New_Return_Shipment_Request();
diff --git a/packages/woocommerce-germanized-shipments/includes/wc-gzd-label-functions.php b/packages/woocommerce-germanized-shipments/includes/wc-gzd-label-functions.php
new file mode 100644
index 000000000..a7b71c78b
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/includes/wc-gzd-label-functions.php
@@ -0,0 +1,152 @@
+get_labels();
+}
+
+function wc_gzd_get_label_type_by_shipment( $shipment ) {
+ $type = is_a( $shipment, '\Vendidero\Germanized\Shipments\Shipment' ) ? $shipment->get_type() : $shipment;
+
+ return apply_filters( 'woocommerce_gzd_shipment_label_type', $type, $shipment );
+}
+
+function wc_gzd_get_shipment_label_types() {
+ return apply_filters(
+ 'woocommerce_gzd_shipment_label_types',
+ array(
+ 'simple',
+ 'return',
+ )
+ );
+}
+
+function wc_gzd_get_label_by_shipment( $the_shipment, $type = '' ) {
+ $shipment_id = \Vendidero\Germanized\Shipments\ShipmentFactory::get_shipment_id( $the_shipment );
+ $label = false;
+
+ if ( $shipment_id ) {
+ $args = array(
+ 'shipment_id' => $shipment_id,
+ 'limit' => 1,
+ );
+
+ if ( ! empty( $type ) ) {
+ $args['type'] = $type;
+ }
+
+ $labels = wc_gzd_get_shipment_labels( $args );
+
+ if ( ! empty( $labels ) ) {
+ $label = $labels[0];
+ }
+ }
+
+ return apply_filters( 'woocommerce_gzd_shipment_label_for_shipment', $label, $the_shipment );
+}
+
+/**
+ * @param false $the_label
+ * @param string $shipping_provider
+ * @param string $type
+ *
+ * @return \Vendidero\Germanized\Shipments\Interfaces\ShipmentLabel|boolean
+ */
+function wc_gzd_get_shipment_label( $the_label = false, $shipping_provider = '', $type = 'simple' ) {
+ return apply_filters( 'woocommerce_gzd_shipment_label', \Vendidero\Germanized\Shipments\Labels\Factory::get_label( $the_label, $shipping_provider, $type ), $the_label, $shipping_provider, $type );
+}
+
+/**
+ * @param \Vendidero\Germanized\Shipments\Shipment $shipment
+ * @param bool $net_weight
+ * @param string $unit
+ *
+ * @return float
+ */
+function wc_gzd_get_shipment_label_weight( $shipment, $net_weight = false, $unit = 'kg' ) {
+ $shipment_weight = $shipment->get_total_weight();
+ $shipment_content_weight = $shipment->get_weight();
+ $shipment_packaging_weight = $shipment->get_packaging_weight();
+
+ if ( ! empty( $shipment_weight ) ) {
+ $shipment_weight = wc_get_weight( $shipment_weight, $unit, $shipment->get_weight_unit() );
+ }
+
+ if ( ! empty( $shipment_content_weight ) ) {
+ $shipment_content_weight = wc_get_weight( $shipment_content_weight, $unit, $shipment->get_weight_unit() );
+ }
+
+ if ( ! empty( $shipment_packaging_weight ) ) {
+ $shipment_packaging_weight = wc_get_weight( $shipment_packaging_weight, $unit, $shipment->get_weight_unit() );
+ }
+
+ /**
+ * The net weight does not include packaging weight.
+ */
+ if ( $net_weight ) {
+ $shipment_packaging_weight = 0;
+ $shipment_weight = $shipment_content_weight;
+ }
+
+ if ( $provider = $shipment->get_shipping_provider_instance() ) {
+ $min_weight = wc_get_weight( $provider->get_label_minimum_shipment_weight(), $unit, 'kg' );
+ $default_weight = wc_get_weight( $provider->get_label_default_shipment_weight(), $unit, 'kg' );
+
+ if ( empty( $shipment_content_weight ) ) {
+ $shipment_weight = $default_weight;
+
+ if ( ! $net_weight ) {
+ $shipment_weight += $shipment_packaging_weight;
+ }
+ }
+
+ if ( $shipment_weight < $min_weight ) {
+ $shipment_weight = $min_weight;
+ }
+ }
+
+ $shipment_weight = wc_format_decimal( $shipment_weight, 3 );
+
+ return apply_filters( 'woocommerce_gzd_shipment_label_weight', $shipment_weight, $shipment, $unit );
+}
+
+/**
+ * @param \Vendidero\Germanized\Shipments\Shipment $shipment
+ * @param string $dimension
+ * @param string $unit
+ */
+function wc_gzd_get_shipment_label_dimensions( $shipment, $unit = 'cm' ) {
+ $dimensions = array(
+ 'length' => 0,
+ 'width' => 0,
+ 'height' => 0,
+ );
+
+ if ( $shipment->has_dimensions() ) {
+ $dimensions = $shipment->get_package_dimensions();
+
+ foreach ( $dimensions as $key => $data ) {
+ $dimensions[ $key ] = wc_get_dimension( $data, $unit, $shipment->get_dimension_unit() );
+ }
+ }
+
+ return apply_filters( 'woocommerce_gzd_shipment_label_dimensions', $dimensions, $shipment, $unit );
+}
diff --git a/packages/woocommerce-germanized-shipments/includes/wc-gzd-packaging-functions.php b/packages/woocommerce-germanized-shipments/includes/wc-gzd-packaging-functions.php
new file mode 100644
index 000000000..b234733ce
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/includes/wc-gzd-packaging-functions.php
@@ -0,0 +1,60 @@
+ _x( 'Cardboard', 'shipments', 'woocommerce-germanized' ),
+ 'letter' => _x( 'Letter', 'shipments', 'woocommerce-germanized' ),
+ );
+
+ return apply_filters( 'woocommerce_gzd_packaging_types', $types );
+}
+
+/**
+ * @return \Vendidero\Germanized\Shipments\Packaging[] $packaging_list
+ */
+function wc_gzd_get_packaging_list( $args = array() ) {
+ $data_store = \WC_Data_Store::load( 'packaging' );
+ $list = $data_store->get_packaging_list( $args );
+
+ return $list;
+}
+
+function wc_gzd_get_packaging_weight_unit() {
+ return apply_filters( 'woocommerce_gzd_packaging_weight_unit', 'kg' );
+}
+
+function wc_gzd_get_packaging_dimension_unit() {
+ return apply_filters( 'woocommerce_gzd_packaging_dimension_unit', 'cm' );
+}
+
+function wc_gzd_get_packaging_select( $args = array() ) {
+ $list = wc_gzd_get_packaging_list( $args );
+ $select = array(
+ '' => _x( 'None', 'shipments-packaging', 'woocommerce-germanized' ),
+ );
+
+ foreach ( $list as $packaging ) {
+ $select[ $packaging->get_id() ] = $packaging->get_title();
+ }
+
+ return $select;
+}
diff --git a/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-functions.php b/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-functions.php
new file mode 100644
index 000000000..5efc9d61d
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-functions.php
@@ -0,0 +1,1563 @@
+countries ? WC()->countries->get_states( $country ) : array();
+ $formatted_state = ( $states && isset( $states[ $state ] ) ) ? $states[ $state ] : $state;
+
+ return $formatted_state;
+}
+
+function wc_gzd_country_to_alpha3( $country ) {
+ return Package::get_country_iso_alpha3( $country );
+}
+
+function wc_gzd_country_to_alpha2( $country ) {
+ return Package::get_country_iso_alpha2( $country );
+}
+
+function wc_gzd_get_shipment_order( $order ) {
+ if ( is_numeric( $order ) ) {
+ $order = wc_get_order( $order );
+ }
+
+ if ( is_a( $order, 'WC_Order' ) ) {
+ try {
+ return new Vendidero\Germanized\Shipments\Order( $order );
+ } catch ( Exception $e ) {
+ wc_caught_exception( $e, __FUNCTION__, array( $order ) );
+ return false;
+ }
+ } elseif ( is_a( $order, 'Vendidero\Germanized\Shipments\Order' ) ) {
+ return $order;
+ }
+
+ return false;
+}
+
+function wc_gzd_get_shipment_label_title( $type, $plural = false ) {
+ $type_data = wc_gzd_get_shipment_type_data( $type );
+
+ return ( ! $plural ? $type_data['labels']['singular'] : $type_data['labels']['plural'] );
+}
+
+function wc_gzd_get_shipping_label_zones() {
+ return apply_filters(
+ 'woocommerce_gzd_shipments_shipping_label_zones',
+ array(
+ 'dom' => _x( 'Domestic', 'shipments', 'woocommerce-germanized' ),
+ 'eu' => _x( 'EU', 'shipments', 'woocommerce-germanized' ),
+ 'int' => _x( 'International', 'shipments', 'woocommerce-germanized' ),
+ )
+ );
+}
+
+function wc_gzd_get_shipping_label_zone_title( $zone ) {
+ $zones = wc_gzd_get_shipping_label_zones();
+ $title = array_key_exists( $zone, $zones ) ? $zones[ $zone ] : '';
+
+ return apply_filters( 'woocommerce_gzd_shipments_shipping_label_zone_title', $title, $zone );
+}
+
+function wc_gzd_get_shipping_shipments_label_zone_title( $zone ) {
+ $title = _x( '%1$s shipments', 'shipments-zone-title', 'woocommerce-germanized' );
+
+ $zones = array(
+ 'dom' => _x( 'Domestic Shipments', 'shipments', 'woocommerce-germanized' ),
+ 'eu' => _x( 'EU Shipments', 'shipments', 'woocommerce-germanized' ),
+ 'int' => _x( 'International Shipments', 'shipments', 'woocommerce-germanized' ),
+ );
+
+ $title = array_key_exists( $zone, $zones ) ? $zones[ $zone ] : $title;
+
+ return apply_filters( 'woocommerce_gzd_shipments_shipping_shipments_label_zone_title', $title, $zone );
+}
+
+function wc_gzd_get_shipment_types() {
+ return array_keys( wc_gzd_get_shipment_type_data( false ) );
+}
+
+/**
+ * Get shipment type data by type.
+ *
+ * @param string $type type name.
+ * @return bool|array Details about the shipment type.
+ *
+ * @package Vendidero/Germanized/Shipments
+ */
+function wc_gzd_get_shipment_type_data( $type = false ) {
+ $types = apply_filters(
+ 'woocommerce_gzd_shipment_type_data',
+ array(
+ 'simple' => array(
+ 'class_name' => '\Vendidero\Germanized\Shipments\SimpleShipment',
+ 'labels' => array(
+ 'singular' => _x( 'Shipment', 'shipments', 'woocommerce-germanized' ),
+ 'plural' => _x( 'Shipments', 'shipments', 'woocommerce-germanized' ),
+ ),
+ ),
+ 'return' => array(
+ 'class_name' => '\Vendidero\Germanized\Shipments\ReturnShipment',
+ 'labels' => array(
+ 'singular' => _x( 'Return', 'shipments', 'woocommerce-germanized' ),
+ 'plural' => _x( 'Returns', 'shipments', 'woocommerce-germanized' ),
+ ),
+ ),
+ )
+ );
+
+ if ( $type && array_key_exists( $type, $types ) ) {
+ return $types[ $type ];
+ } elseif ( false === $type ) {
+ return $types;
+ } else {
+ return $types['simple'];
+ }
+}
+
+function wc_gzd_get_shipments_by_order( $order ) {
+ $shipments = array();
+
+ if ( $order_shipment = wc_gzd_get_shipment_order( $order ) ) {
+ $shipments = $order_shipment->get_shipments();
+ }
+
+ return $shipments;
+}
+
+function wc_gzd_get_shipment_order_shipping_statuses() {
+ $shipment_statuses = array(
+ 'gzd-not-shipped' => _x( 'Not shipped', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-partially-shipped' => _x( 'Partially shipped', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-shipped' => _x( 'Shipped', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-partially-delivered' => _x( 'Partially delivered', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-delivered' => _x( 'Delivered', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-no-shipping-needed' => _x( 'No shipping needed', 'shipments', 'woocommerce-germanized' ),
+ );
+
+ /**
+ * Filter to adjust or add order shipping statuses.
+ * An order might retrieve a shipping status e.g. not shipped.
+ *
+ * @param array $shipment_statuses Available order shipping statuses.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_order_shipping_statuses', $shipment_statuses );
+}
+
+function wc_gzd_get_shipment_order_return_statuses() {
+ $shipment_statuses = array(
+ 'gzd-open' => _x( 'Open', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-partially-returned' => _x( 'Partially returned', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-returned' => _x( 'Returned', 'shipments', 'woocommerce-germanized' ),
+ );
+
+ /**
+ * Filter to adjust or add order return statuses.
+ * An order might retrieve a shipping status e.g. not shipped.
+ *
+ * @param array $shipment_statuses Available order return statuses.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_order_return_statuses', $shipment_statuses );
+}
+
+/**
+ * @param $instance_id
+ *
+ * @return \Vendidero\Germanized\Shipments\ShippingMethod\ProviderMethod|false
+ */
+function wc_gzd_get_shipping_provider_method( $instance_id ) {
+ return \Vendidero\Germanized\Shipments\ShippingMethod\MethodHelper::get_provider_method( $instance_id );
+}
+
+/**
+ * Returns the current shipping method rate id.
+ *
+ * @return false|string
+ */
+function wc_gzd_get_current_shipping_method_id() {
+ $chosen_shipping_methods = WC()->session ? WC()->session->get( 'chosen_shipping_methods' ) : array();
+
+ if ( ! empty( $chosen_shipping_methods ) ) {
+ return reset( $chosen_shipping_methods );
+ }
+
+ return false;
+}
+
+function wc_gzd_get_current_shipping_provider_method() {
+ if ( $current = wc_gzd_get_current_shipping_method_id() ) {
+ return wc_gzd_get_shipping_provider_method( $current );
+ }
+
+ return false;
+}
+
+function wc_gzd_get_shipment_order_shipping_status_name( $status ) {
+ if ( 'gzd-' !== substr( $status, 0, 4 ) ) {
+ $status = 'gzd-' . $status;
+ }
+
+ $status_name = '';
+ $statuses = wc_gzd_get_shipment_order_shipping_statuses();
+
+ if ( array_key_exists( $status, $statuses ) ) {
+ $status_name = $statuses[ $status ];
+ }
+
+ /**
+ * Filter to adjust the status name for a certain order shipping status.
+ *
+ * @see wc_gzd_get_shipment_order_shipping_statuses()
+ *
+ * @param string $status_name The status name.
+ * @param string $status The shipping status.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_order_shipping_status_name', $status_name, $status );
+}
+
+function wc_gzd_get_shipment_order_return_status_name( $status ) {
+ if ( 'gzd-' !== substr( $status, 0, 4 ) ) {
+ $status = 'gzd-' . $status;
+ }
+
+ $status_name = '';
+ $statuses = wc_gzd_get_shipment_order_return_statuses();
+
+ if ( array_key_exists( $status, $statuses ) ) {
+ $status_name = $statuses[ $status ];
+ }
+
+ /**
+ * Filter to adjust the status name for a certain order return status.
+ *
+ * @see wc_gzd_get_shipment_order_return_statuses()
+ *
+ * @param string $status_name The status name.
+ * @param string $status The return status.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_order_return_status_name', $status_name, $status );
+}
+
+/**
+ * Standard way of retrieving shipments based on certain parameters.
+ *
+ * @param array $args Array of args (above).
+ *
+ * @return Shipment[] The shipments found.
+ *@since 3.0.0
+ */
+function wc_gzd_get_shipments( $args ) {
+ $query = new Vendidero\Germanized\Shipments\ShipmentQuery( $args );
+
+ return $query->get_shipments();
+}
+
+function wc_gzd_get_shipment_customer_visible_statuses( $shipment_type = 'simple' ) {
+ $statuses = array_keys( wc_gzd_get_shipment_statuses() );
+ $statuses = array_diff( $statuses, array( 'gzd-draft' ) );
+
+ /**
+ * Filter to decide which shipment statuses should be visible to customers
+ * e.g. whether a shipment of a certain status should be shown or not.
+ *
+ * @param array $shipment_statuses The available shipment statuses.
+ * @param string $shipment_type The shipment type.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipment_customer_visible_statuses', $statuses, $shipment_type );
+}
+
+/**
+ * Main function for returning shipments.
+ *
+ * @param mixed $the_shipment Object or shipment id.
+ *
+ * @return bool|SimpleShipment|ReturnShipment|Shipment
+ */
+function wc_gzd_get_shipment( $the_shipment ) {
+ return ShipmentFactory::get_shipment( $the_shipment );
+}
+
+/**
+ * Get all shipment statuses.
+ *
+ * @return array
+ */
+function wc_gzd_get_shipment_statuses() {
+ $shipment_statuses = array(
+ 'gzd-draft' => _x( 'Draft', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-processing' => _x( 'Processing', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-shipped' => _x( 'Shipped', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-delivered' => _x( 'Delivered', 'shipments', 'woocommerce-germanized' ),
+ 'gzd-requested' => _x( 'Requested', 'shipments', 'woocommerce-germanized' ),
+ );
+
+ /**
+ * Add or adjust available Shipment statuses.
+ *
+ * @param array $shipment_statuses The available shipment statuses.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipment_statuses', $shipment_statuses );
+}
+
+/**
+ * @param Shipment $shipment
+ *
+ * @return mixed|void
+ */
+function wc_gzd_get_shipment_selectable_statuses( $shipment ) {
+ $shipment_statuses = wc_gzd_get_shipment_statuses();
+
+ if ( ! $shipment->has_status( 'requested' ) && isset( $shipment_statuses['gzd-requested'] ) ) {
+ unset( $shipment_statuses['gzd-requested'] );
+ }
+
+ /**
+ * Add or remove selectable shipment statuses for a certain shipment and/or shipment type.
+ *
+ * @param array $shipment_statuses The available shipment statuses.
+ * @param string $type The shipment type e.g. return.
+ * @param Shipment $shipment The shipment instance.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipment_selectable_statuses', $shipment_statuses, $shipment->get_type(), $shipment );
+}
+
+/**
+ * @param Order $order_shipment
+ * @param array $args
+ *
+ * @return ReturnShipment|WP_Error
+ */
+function wc_gzd_create_return_shipment( $order_shipment, $args = array() ) {
+ try {
+
+ if ( ! $order_shipment || ! is_a( $order_shipment, 'Vendidero\Germanized\Shipments\Order' ) ) {
+ throw new Exception( _x( 'Invalid order.', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ if ( ! $order_shipment->needs_return() ) {
+ throw new Exception( _x( 'This order is already fully returned.', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ $args = wp_parse_args(
+ $args,
+ array(
+ 'items' => array(),
+ 'props' => array(),
+ )
+ );
+
+ $shipment = ShipmentFactory::get_shipment( false, 'return' );
+
+ if ( ! $shipment ) {
+ throw new Exception( _x( 'Error while creating the shipment instance', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ // Make sure shipment knows its parent
+ $shipment->set_order_shipment( $order_shipment );
+ $shipment->sync( $args['props'] );
+ $shipment->sync_items( $args );
+ $shipment->save();
+
+ } catch ( Exception $e ) {
+ return new WP_Error( 'error', $e->getMessage() );
+ }
+
+ return $shipment;
+}
+
+/**
+ * @param Order $order_shipment
+ * @param array $args
+ *
+ * @return Shipment|WP_Error
+ */
+function wc_gzd_create_shipment( $order_shipment, $args = array() ) {
+ try {
+ if ( ! $order_shipment || ! is_a( $order_shipment, 'Vendidero\Germanized\Shipments\Order' ) ) {
+ throw new Exception( _x( 'Invalid shipment order', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ if ( ! $order = $order_shipment->get_order() ) {
+ throw new Exception( _x( 'Invalid shipment order', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ $args = wp_parse_args(
+ $args,
+ array(
+ 'items' => array(),
+ 'props' => array(),
+ )
+ );
+
+ $shipment = ShipmentFactory::get_shipment( false, 'simple' );
+
+ if ( ! $shipment ) {
+ throw new Exception( _x( 'Error while creating the shipment instance', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ $shipment->set_order_shipment( $order_shipment );
+ $shipment->sync( $args['props'] );
+ $shipment->sync_items( $args );
+ $shipment->save();
+ } catch ( Exception $e ) {
+ return new WP_Error( 'error', $e->getMessage() );
+ }
+
+ return $shipment;
+}
+
+function wc_gzd_create_shipment_item( $shipment, $order_item, $args = array() ) {
+ try {
+
+ if ( ! $order_item || ! is_a( $order_item, 'WC_Order_Item' ) ) {
+ throw new Exception( _x( 'Invalid order item', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ $item = new Vendidero\Germanized\Shipments\ShipmentItem();
+
+ $item->set_order_item_id( $order_item->get_id() );
+ $item->set_shipment( $shipment );
+ $item->sync( $args );
+ $item->save();
+
+ } catch ( Exception $e ) {
+ return new WP_Error( 'error', $e->getMessage() );
+ }
+
+ return $item;
+}
+
+function wc_gzd_allow_customer_return_empty_return_reason( $order ) {
+ return apply_filters( 'woocommerce_gzd_allow_customer_return_empty_return_reason', true, $order );
+}
+
+/**
+ * @param bool $allow_none
+ * @param bool|WC_Order_Item $order_item
+ *
+ * @return ReturnReason[]
+ */
+function wc_gzd_get_return_shipment_reasons( $order_item = false ) {
+ $reasons = Package::get_setting( 'return_reasons' );
+
+ if ( ! is_array( $reasons ) ) {
+ $reasons = array();
+ } else {
+ $reasons = array_filter( $reasons );
+ }
+
+ /**
+ * Filter that allows adjusting raw return reasons for a specific shipment (e.g. array containing reason data with code, reason and order).
+ *
+ * @param array $reasons Available return reasons.
+ * @param WC_Order_Item|false $order_item The order item object if available to further filter reasons.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ $reasons = apply_filters( 'woocommerce_gzd_return_shipment_reasons_raw', $reasons, $order_item );
+ $instances = array();
+
+ foreach ( $reasons as $reason ) {
+ $instances[] = new ReturnReason( $reason );
+ }
+
+ usort( $instances, '_wc_gzd_sort_return_shipment_reasons' );
+
+ /**
+ * Filter that allows to adjust available return reasons for a specific shipment.
+ *
+ * @param ReturnReason[] $reasons Available return reasons.
+ * @param WC_Order_Item|false $order_item The order item object if available to further filter reasons.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_return_shipment_reasons', $instances, $order_item );
+}
+
+function wc_gzd_return_shipment_reason_exists( $maybe_reason, $shipment = false ) {
+ $reasons = wc_gzd_get_return_shipment_reasons( $shipment );
+ $exists = false;
+
+ foreach ( $reasons as $reason ) {
+
+ if ( $reason->get_code() === $maybe_reason ) {
+ $exists = true;
+ break;
+ }
+ }
+
+ return $exists;
+}
+
+/**
+ * @param ReturnReason $a
+ * @param ReturnReason $b
+ */
+function _wc_gzd_sort_return_shipment_reasons( $a, $b ) {
+ if ( $a->get_order() === $b->get_order() ) {
+ return 0;
+ } elseif ( $a->get_order() > $b->get_order() ) {
+ return 1;
+ } else {
+ return -1;
+ }
+}
+
+/**
+ * @param WP_Error $error
+ *
+ * @return bool
+ */
+function wc_gzd_shipment_wp_error_has_errors( $error ) {
+ if ( is_callable( array( $error, 'has_errors' ) ) ) {
+ return $error->has_errors();
+ } else {
+ $errors = $error->errors;
+
+ return ( ! empty( $errors ) ? true : false );
+ }
+}
+
+/**
+ * @param Shipment $shipment
+ * @param ShipmentItem $shipment_item
+ * @param array $args
+ *
+ * @return ShipmentReturnItem|WP_Error
+ */
+function wc_gzd_create_return_shipment_item( $shipment, $shipment_item, $args = array() ) {
+
+ try {
+
+ if ( ! $shipment_item || ! is_a( $shipment_item, '\Vendidero\Germanized\Shipments\ShipmentItem' ) ) {
+ throw new Exception( _x( 'Invalid shipment item', 'shipments', 'woocommerce-germanized' ) );
+ }
+
+ $item = new Vendidero\Germanized\Shipments\ShipmentReturnItem();
+ $item->set_order_item_id( $shipment_item->get_order_item_id() );
+ $item->set_shipment( $shipment );
+ $item->sync( $args );
+ $item->save();
+
+ } catch ( Exception $e ) {
+ return new WP_Error( 'error', $e->getMessage() );
+ }
+
+ return $item;
+}
+
+function wc_gzd_get_shipment_editable_statuses() {
+ /**
+ * Filter that allows to adjust Shipment statuses which decide upon whether
+ * a Shipment is editable or not.
+ *
+ * @param array $statuses Statuses which should be considered as editable.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipment_editable_statuses', array( 'draft', 'requested', 'processing' ) );
+}
+
+/**
+ * @param Shipment $shipment
+ */
+function wc_gzd_get_shipment_address_addition( $shipment ) {
+ $addition = $shipment->get_address_2();
+ $street_addition = $shipment->get_address_street_addition();
+
+ if ( ! empty( $street_addition ) ) {
+ $addition = $street_addition . ( ! empty( $addition ) ? ' ' . $addition : '' );
+ }
+
+ return trim( $addition );
+}
+
+function wc_gzd_split_shipment_street( $street_str ) {
+ $return = array(
+ 'street' => $street_str,
+ 'number' => '',
+ 'addition' => '',
+ 'addition_2' => '',
+ );
+
+ try {
+ $split = AddressSplitter::split_address( $street_str );
+
+ $return['street'] = $split['streetName'];
+ $return['number'] = $split['houseNumber'];
+ /**
+ * e.g. 5. OG
+ */
+ $return['addition'] = isset( $split['additionToAddress2'] ) ? $split['additionToAddress2'] : '';
+ /**
+ * E.g. details to the location prefixed to the street name
+ */
+ $return['addition_2'] = isset( $split['additionToAddress1'] ) ? $split['additionToAddress1'] : '';
+ } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
+ }
+
+ return $return;
+}
+
+/**
+ * @return ShippingProvider\Auto[]|ShippingProvider\Simple[]
+ */
+function wc_gzd_get_shipping_providers() {
+ return ShippingProvider\Helper::instance()->get_shipping_providers();
+}
+
+function wc_gzd_get_available_shipping_providers() {
+ return ShippingProvider\Helper::instance()->get_available_shipping_providers();
+}
+
+function wc_gzd_get_shipping_provider( $name ) {
+ return ShippingProvider\Helper::instance()->get_shipping_provider( $name );
+}
+
+function wc_gzd_get_default_shipping_provider() {
+ $default = Package::get_setting( 'default_shipping_provider' );
+
+ /**
+ * Filter to adjust the default shipping provider used as a fallback for shipments
+ * for which no provider could be determined automatically (e.g. by the chosen shipping methid).
+ *
+ * @param string $title The shipping provider slug.
+ *
+ * @since 3.0.6
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_default_shipping_provider', $default );
+}
+
+function wc_gzd_get_shipping_provider_select( $include_none = true ) {
+ $providers = wc_gzd_get_shipping_providers();
+ $select = $include_none ? array(
+ '' => _x( 'None', 'shipments', 'woocommerce-germanized' ),
+ ) : array();
+
+ foreach ( $providers as $provider ) {
+ if ( ! $provider->is_activated() ) {
+ continue;
+ }
+ $select[ $provider->get_name() ] = $provider->get_title();
+ }
+
+ return $select;
+}
+
+function wc_gzd_get_shipping_provider_title( $slug ) {
+ $providers = wc_gzd_get_shipping_providers();
+
+ if ( array_key_exists( $slug, $providers ) ) {
+ $title = $providers[ $slug ]->get_title();
+ } else {
+ $title = $slug;
+ }
+
+ /**
+ * Filter to adjust the title of a certain shipping provider e.g. DHL.
+ *
+ * @param string $title The shipping provider title.
+ * @param string $slug The shipping provider slug.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipping_provider_title', $title, $slug );
+}
+
+/**
+ * @param Shipment $shipment
+ */
+function wc_gzd_get_shipment_shipping_provider_title( $shipment ) {
+ $title = $shipment->get_shipping_provider_title();
+
+ if ( empty( $title ) ) {
+ $title = apply_filters( 'woocommerce_gzd_shipping_provider_unknown_title', _x( 'Unknown', 'shipments-shipping-provider', 'woocommerce-germanized' ) );
+ }
+
+ return $title;
+}
+
+function wc_gzd_get_shipping_provider_service_locations() {
+ return array( 'settings', 'shipping_provider_settings', 'shipping_method_settings', 'packaging_settings', 'labels', 'label_services' );
+}
+
+function wc_gzd_get_shipping_provider_slug( $provider ) {
+ $providers = wc_gzd_get_shipping_providers();
+
+ if ( in_array( $provider, $providers, true ) ) {
+ $slug = array_search( $provider, $providers, true );
+ } elseif ( array_key_exists( $provider, $providers ) ) {
+ $slug = $provider;
+ } else {
+ $slug = sanitize_key( $provider );
+ }
+
+ return $slug;
+}
+
+function _wc_gzd_shipments_keep_force_filename( $new_filename ) {
+ return isset( $GLOBALS['gzd_shipments_unique_filename'] ) ? $GLOBALS['gzd_shipments_unique_filename'] : $new_filename;
+}
+
+function wc_gzd_shipments_upload_data( $filename, $bits, $relative = true ) {
+ try {
+ Package::set_upload_dir_filter();
+ $GLOBALS['gzd_shipments_unique_filename'] = $filename;
+ add_filter( 'wp_unique_filename', '_wc_gzd_shipments_keep_force_filename', 10, 1 );
+
+ $tmp = wp_upload_bits( $filename, null, $bits );
+
+ unset( $GLOBALS['gzd_shipments_unique_filename'] );
+ remove_filter( 'wp_unique_filename', '_wc_gzd_shipments_keep_force_filename', 10 );
+ Package::unset_upload_dir_filter();
+
+ if ( isset( $tmp['file'] ) ) {
+ $path = $tmp['file'];
+
+ if ( $relative ) {
+ $path = Package::get_relative_upload_dir( $path );
+ }
+
+ return $path;
+ } else {
+ throw new Exception( _x( 'Error while uploading file.', 'shipments', 'woocommerce-germanized' ) );
+ }
+ } catch ( Exception $e ) {
+ return false;
+ }
+}
+
+function wc_gzd_get_shipment_setting_default_address_fields( $type = 'shipper' ) {
+ $address_fields = array(
+ 'first_name' => _x( 'First Name', 'shipments', 'woocommerce-germanized' ),
+ 'last_name' => _x( 'Last Name', 'shipments', 'woocommerce-germanized' ),
+ 'full_name' => _x( 'Full Name', 'shipments', 'woocommerce-germanized' ),
+ 'company' => _x( 'Company', 'shipments', 'woocommerce-germanized' ),
+ 'address_1' => _x( 'Address 1', 'shipments', 'woocommerce-germanized' ),
+ 'address_2' => _x( 'Address 2', 'shipments', 'woocommerce-germanized' ),
+ 'street' => _x( 'Street', 'shipments', 'woocommerce-germanized' ),
+ 'street_number' => _x( 'House Number', 'shipments', 'woocommerce-germanized' ),
+ 'postcode' => _x( 'Postcode', 'shipments', 'woocommerce-germanized' ),
+ 'city' => _x( 'City', 'shipments', 'woocommerce-germanized' ),
+ 'country' => _x( 'Country', 'shipments', 'woocommerce-germanized' ),
+ 'state' => _x( 'State', 'shipments', 'woocommerce-germanized' ),
+ 'phone' => _x( 'Phone', 'shipments', 'woocommerce-germanized' ),
+ 'email' => _x( 'Email', 'shipments', 'woocommerce-germanized' ),
+ 'customs_reference_number' => _x( 'Customs Reference Number', 'shipments', 'woocommerce-germanized' ),
+ 'customs_uk_vat_id' => _x( 'UK VAT ID (HMRC)', 'shipments', 'woocommerce-germanized' ),
+ );
+
+ return apply_filters( 'woocommerce_gzd_shipment_default_address_fields', $address_fields, $type );
+}
+
+/**
+ * @return array
+ */
+function wc_gzd_get_shipment_setting_address_fields( $address_type = 'shipper' ) {
+ $default_address_fields = array_keys( wc_gzd_get_shipment_setting_default_address_fields( $address_type ) );
+ $default_address_data = array();
+
+ if ( 'return' === $address_type ) {
+ $default_address_data = wc_gzd_get_shipment_setting_address_fields( 'shipper' );
+ }
+
+ foreach ( $default_address_fields as $prop ) {
+ $key = "woocommerce_gzd_shipments_{$address_type}_address_{$prop}";
+ $value = get_option( $key, '' );
+
+ if ( '' === $value && array_key_exists( $prop, $default_address_data ) ) {
+ $value = $default_address_data[ $prop ];
+ }
+
+ $address_fields[ $prop ] = $value;
+ }
+
+ if ( ! empty( $address_fields['country'] ) && strlen( $address_fields['country'] ) > 2 ) {
+ $value = wc_format_country_state_string( $address_fields['country'] );
+ $address_fields['country'] = $value['country'];
+ $address_fields['state'] = $value['state'];
+ }
+
+ /**
+ * Format/split address 1 into street and house number
+ */
+ if ( ! empty( $address_fields['address_1'] ) ) {
+ $split = wc_gzd_split_shipment_street( $address_fields['address_1'] );
+
+ $address_fields['street'] = $split['street'];
+ $address_fields['street_number'] = $split['number'];
+ } else {
+ $address_fields['street'] = '';
+ $address_fields['street_number'] = '';
+ }
+
+ /**
+ * Attach formatted full name
+ */
+ $address_fields['full_name'] = trim( sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce-germanized' ), $address_fields['first_name'], $address_fields['last_name'] ) );
+
+ return apply_filters( "woocommerce_gzd_shipment_{$address_type}_address_fields", $address_fields, $address_type );
+}
+
+/**
+ * @param Order $shipment_order
+ *
+ * @return array
+ */
+function wc_gzd_get_shipment_return_address( $shipment_order = false ) {
+ return wc_gzd_get_shipment_setting_address_fields( 'return' );
+}
+
+/**
+ * @param WC_Order $order
+ * @return WC_Order_Item_Shipping|false
+ */
+function wc_gzd_get_shipment_order_shipping_method( $order ) {
+ $methods = $order->get_shipping_methods();
+ $method = false;
+
+ if ( ! empty( $methods ) ) {
+ $method_data = array_values( $methods );
+ $method = array_shift( $method_data );
+
+ if ( ! $method ) {
+ $method = false;
+ }
+ }
+
+ /**
+ * Allows adjusting the shipping method for a certain order.
+ *
+ * @param WC_Order_Item_Shipping|false $method The order item.
+ * @param WC_Order $order The order object.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipment_order_shipping_method', $method, $order );
+}
+
+/**
+ * @param WC_Order $order
+ */
+function wc_gzd_get_shipment_order_shipping_method_id( $order ) {
+ $id = '';
+
+ if ( $method = wc_gzd_get_shipment_order_shipping_method( $order ) ) {
+ $id = $method->get_method_id() . ':' . $method->get_instance_id();
+ }
+
+ /**
+ * Allows adjusting the shipping method id for a certain Order.
+ *
+ * @param string $id The shipping method id.
+ * @param WC_Order $order The order object.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipment_order_shipping_method_id', $id, $order );
+}
+
+function wc_gzd_render_shipment_action_buttons( $actions ) {
+ $actions_html = '';
+
+ foreach ( $actions as $action ) {
+ $action = wp_parse_args(
+ $action,
+ array(
+ 'url' => '#',
+ 'group' => '',
+ 'action' => '',
+ 'target' => '_self',
+ 'classes' => '',
+ 'name' => '',
+ 'custom_attributes' => array(),
+ 'title' => '',
+ )
+ );
+
+ if ( ! empty( $action['group'] ) ) {
+ $actions_html .= ' ';
+ } elseif ( isset( $action['action'], $action['url'], $action['name'] ) ) {
+ $classes = 'button wc-gzd-shipment-action-button tip wc-gzd-shipment-action-button-' . $action['action'] . ' ' . $action['action'] . ' ' . $action['classes'];
+
+ if ( empty( $action['title'] ) ) {
+ $action['title'] = $action['name'];
+ }
+
+ $custom_attributes = '';
+
+ foreach ( $action['custom_attributes'] as $attribute => $val ) {
+ $custom_attributes .= ' ' . esc_attr( $attribute ) . '="' . esc_attr( $val ) . '"';
+ }
+
+ $actions_html .= sprintf( '%6$s', esc_attr( $classes ), esc_url( $action['url'] ), esc_attr( $action['title'] ), esc_attr( $action['target'] ), $custom_attributes, esc_html( $action['name'] ) );
+ }
+ }
+
+ return $actions_html;
+}
+
+function wc_gzd_get_shipment_status_name( $status ) {
+ if ( 'gzd-' !== substr( $status, 0, 4 ) ) {
+ $status = 'gzd-' . $status;
+ }
+
+ $status_name = '';
+ $statuses = wc_gzd_get_shipment_statuses();
+
+ if ( array_key_exists( $status, $statuses ) ) {
+ $status_name = $statuses[ $status ];
+ }
+
+ /**
+ * Filter to adjust the shipment status name or title.
+ *
+ * @param string $status_name The status name or title.
+ * @param integer $status The status slug.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipment_status_name', $status_name, $status );
+}
+
+function wc_gzd_get_shipment_sent_statuses() {
+ /**
+ * Filter to adjust which Shipment statuses should be considered as sent.
+ *
+ * @param array $statuses An array of statuses considered as shipped,
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters(
+ 'woocommerce_gzd_shipment_sent_statuses',
+ array(
+ 'shipped',
+ 'delivered',
+ )
+ );
+}
+
+function wc_gzd_get_shipment_counts( $type = '' ) {
+ $counts = array();
+
+ foreach ( array_keys( wc_gzd_get_shipment_statuses() ) as $status ) {
+ $counts[ $status ] = wc_gzd_get_shipment_count( $status, $type );
+ }
+
+ return $counts;
+}
+
+function wc_gzd_get_shipment_count( $status, $type = '' ) {
+ $count = 0;
+ $status = ( substr( $status, 0, 4 ) ) === 'gzd-' ? $status : 'gzd-' . $status;
+ $shipment_statuses = array_keys( wc_gzd_get_shipment_statuses() );
+
+ if ( ! in_array( $status, $shipment_statuses, true ) ) {
+ return 0;
+ }
+
+ $cache_key = WC_Cache_Helper::get_cache_prefix( 'shipments' ) . $status . $type;
+ $cached_count = wp_cache_get( $cache_key, 'counts' );
+
+ if ( false !== $cached_count ) {
+ return $cached_count;
+ }
+
+ $data_store = WC_Data_Store::load( 'shipment' );
+
+ if ( $data_store ) {
+ $count += $data_store->get_shipment_count( $status, $type );
+ }
+
+ wp_cache_set( $cache_key, $count, 'counts' );
+
+ return $count;
+}
+
+/**
+ * See if a string is a shipment status.
+ *
+ * @param string $maybe_status Status, including any gzd- prefix.
+ * @return bool
+ */
+function wc_gzd_is_shipment_status( $maybe_status ) {
+ $shipment_statuses = wc_gzd_get_shipment_statuses();
+
+ return isset( $shipment_statuses[ $maybe_status ] );
+}
+
+/**
+ * Main function for returning shipment items.
+ *
+ * @since 2.2
+ *
+ * @param mixed $the_item Object or shipment item id.
+ * @param string $item_type The shipment item type.
+ *
+ * @return bool|ShipmentItem
+ */
+function wc_gzd_get_shipment_item( $the_item = false, $item_type = 'simple' ) {
+ $item_id = wc_gzd_get_shipment_item_id( $the_item );
+
+ if ( ! $item_id ) {
+ return false;
+ }
+
+ $item_class = 'Vendidero\Germanized\Shipments\ShipmentItem';
+
+ if ( 'return' === $item_type ) {
+ $item_class = 'Vendidero\Germanized\Shipments\ShipmentReturnItem';
+ }
+
+ /**
+ * Filter to adjust the classname used to construct a ShipmentItem.
+ *
+ * @param string $classname The classname to be used.
+ * @param integer $item_id The shipment item id.
+ * @param string $item_type The shipment item type.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ $classname = apply_filters( 'woocommerce_gzd_shipment_item_class', $item_class, $item_id, $item_type );
+
+ if ( ! class_exists( $classname ) ) {
+ return false;
+ }
+
+ try {
+ return new $classname( $item_id );
+ } catch ( Exception $e ) {
+ wc_caught_exception( $e, __FUNCTION__, array( $the_item, $item_type ) );
+ return false;
+ }
+}
+
+/**
+ * Get the shipment item ID depending on what was passed.
+ *
+ * @since 3.0.0
+ * @param mixed $item Item data to convert to an ID.
+ * @return int|bool false on failure
+ */
+function wc_gzd_get_shipment_item_id( $item ) {
+ if ( is_numeric( $item ) ) {
+ return $item;
+ } elseif ( $item instanceof Vendidero\Germanized\Shipments\ShipmentItem ) {
+ return $item->get_id();
+ } elseif ( ! empty( $item->shipment_item_id ) ) {
+ return $item->shipment_item_id;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * Format dimensions for display.
+ *
+ * @since 3.0.0
+ * @param array $dimensions Array of dimensions.
+ * @return string
+ */
+function wc_gzd_format_shipment_dimensions( $dimensions, $unit = '' ) {
+ $dimension_string = implode( ' × ', array_filter( array_map( 'wc_format_localized_decimal', $dimensions ) ) );
+
+ if ( ! empty( $dimension_string ) ) {
+ $unit = empty( $unit ) ? get_option( 'woocommerce_dimension_unit' ) : $unit;
+ $dimension_string .= ' ' . $unit;
+ } else {
+ $dimension_string = _x( 'N/A', 'shipments', 'woocommerce-germanized' );
+ }
+
+ /**
+ * Filter to adjust the format of Shipment dimensions e.g. LxBxH.
+ *
+ * @param string $dimension_string The dimension string.
+ * @param array $dimensions Array containing the dimensions.
+ * @param string $unit The dimension unit.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_format_shipment_dimensions', $dimension_string, $dimensions, $unit );
+}
+
+/**
+ * Format a weight for display.
+ *
+ * @since 3.0.0
+ * @param float $weight Weight.
+ * @return string
+ */
+function wc_gzd_format_shipment_weight( $weight, $unit = '' ) {
+ $weight_string = wc_format_localized_decimal( $weight );
+
+ if ( ! empty( $weight_string ) ) {
+ $unit = empty( $unit ) ? get_option( 'woocommerce_weight_unit' ) : $unit;
+ $weight_string .= ' ' . $unit;
+ } else {
+ $weight_string = _x( 'N/A', 'shipments', 'woocommerce-germanized' );
+ }
+
+ /**
+ * Filter to adjust the format of Shipment weight.
+ *
+ * @param string $weight_string The weight string.
+ * @param string $weight The Shipment weight.
+ * @param string $unit The dimension unit.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_format_shipment_weight', $weight_string, $weight, $unit );
+}
+
+/**
+ * Get My Account > Shipments columns.
+ *
+ * @since 3.0.0
+ * @return array
+ */
+function wc_gzd_get_account_shipments_columns( $type = 'simple' ) {
+ /**
+ * Filter to adjust columns being used to display shipments in a table view on the customer
+ * account page.
+ *
+ * @param string[] $columns The columns in key => value pairs.
+ * @param string $type The shipment type e.g. simple or return.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ $columns = apply_filters(
+ 'woocommerce_gzd_account_shipments_columns',
+ array(
+ 'shipment-number' => _x( 'Shipment', 'shipments', 'woocommerce-germanized' ),
+ 'shipment-date' => _x( 'Date', 'shipments', 'woocommerce-germanized' ),
+ 'shipment-status' => _x( 'Status', 'shipments', 'woocommerce-germanized' ),
+ 'shipment-tracking' => _x( 'Tracking', 'shipments', 'woocommerce-germanized' ),
+ 'shipment-actions' => _x( 'Actions', 'shipments', 'woocommerce-germanized' ),
+ ),
+ $type
+ );
+
+ return $columns;
+}
+
+function wc_gzd_get_order_customer_add_return_url( $order ) {
+
+ if ( ! $shipment_order = wc_gzd_get_shipment_order( $order ) ) {
+ return false;
+ }
+
+ $url = wc_get_endpoint_url( 'add-return-shipment', $shipment_order->get_order()->get_id(), wc_get_page_permalink( 'myaccount' ) );
+
+ if ( $shipment_order->get_order()->get_customer_id() <= 0 ) {
+ $key = $shipment_order->get_order_return_request_key();
+
+ if ( ! empty( $key ) ) {
+ $url = add_query_arg( array( 'key' => $key ), $url );
+ } else {
+ $url = '';
+ }
+ }
+
+ /**
+ * Filter to adjust the URL the customer (or guest) might access to add a return to a certain order.
+ *
+ * @param string $url The URL pointing to the add return page.
+ * @param Order $order The order object.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_shipments_add_return_shipment_url', $url, $shipment_order->get_order() );
+}
+
+/**
+ * @param WC_Order $order
+ *
+ * @return mixed
+ */
+function wc_gzd_order_is_customer_returnable( $order, $check_date = true ) {
+ $is_returnable = false;
+
+ if ( ! $shipment_order = wc_gzd_get_shipment_order( $order ) ) {
+ return false;
+ }
+
+ if ( $provider = wc_gzd_get_order_shipping_provider( $order ) ) {
+ $is_returnable = $provider->supports_customer_returns( $shipment_order->get_order() );
+
+ if ( $shipment_order->get_order()->get_customer_id() <= 0 && ! $provider->supports_guest_returns() ) {
+ $is_returnable = false;
+ }
+ }
+
+ // Shipment is fully returned
+ if ( ! $shipment_order->needs_return() ) {
+ $is_returnable = false;
+ }
+
+ // Check days left for return
+ $maximum_days = Package::get_setting( 'customer_return_open_days' );
+
+ if ( $check_date && ! empty( $maximum_days ) ) {
+ $maximum_days = absint( $maximum_days );
+
+ if ( ! empty( $maximum_days ) ) {
+
+ $completed_date = $shipment_order->get_order()->get_date_created();
+
+ if ( $shipment_order->get_date_shipped() ) {
+ $completed_date = $shipment_order->get_date_shipped();
+ } elseif ( $shipment_order->get_order()->get_date_completed() ) {
+ $completed_date = $shipment_order->get_order()->get_date_completed();
+ }
+
+ /**
+ * Filter to adjust the completed date of an order used to determine whether an order is
+ * still returnable by the customer or not. The date is constructed by checking for existence in the following order:
+ *
+ * 1. The date the order was shipped completely
+ * 2. The date the order was marked as completed
+ * 3. The date the order was created
+ *
+ * @param WC_DateTime $completed_date The order completed date.
+ * @param WC_Order $order The order instance.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ $completed_date = apply_filters( 'woocommerce_gzd_order_return_completed_date', $completed_date, $shipment_order->get_order() );
+
+ if ( $completed_date ) {
+ $today = new WC_DateTime();
+ $diff = $today->diff( $completed_date );
+
+ if ( $diff->days > $maximum_days ) {
+ $is_returnable = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Filter to decide whether a customer might add return request to a certain order.
+ *
+ * @param bool $is_returnable Whether or not shipment supports customer added returns
+ * @param WC_Order $order The order instance for which the return shall be created.
+ * @param bool $check_date Whether to check for a maximum date or not.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_order_is_returnable_by_customer', $is_returnable, $shipment_order->get_order(), $check_date );
+}
+
+/**
+ * @param $order
+ *
+ * @return bool|\Vendidero\Germanized\Shipments\Interfaces\ShippingProvider
+ */
+function wc_gzd_get_order_shipping_provider( $order ) {
+ if ( is_numeric( $order ) ) {
+ $order = wc_get_order( $order );
+ } elseif ( is_a( $order, '\Vendidero\Germanized\Shipments\Order' ) ) {
+ $order = $order->get_order();
+ }
+
+ if ( ! $order ) {
+ return false;
+ }
+
+ $provider = false;
+ $method_id = wc_gzd_get_shipment_order_shipping_method_id( $order );
+
+ if ( $method = wc_gzd_get_shipping_provider_method( $method_id ) ) {
+ $provider = $method->get_shipping_provider_instance();
+ }
+
+ if ( ! $provider ) {
+ foreach ( array_reverse( wc_gzd_get_shipment_order( $order )->get_shipments() ) as $shipment ) {
+ if ( $shipment->get_shipping_provider_instance() ) {
+ $provider = $shipment->get_shipping_provider_instance();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Filters the shipping provider detected for a specific order.
+ *
+ * @param bool|\Vendidero\Germanized\Shipments\Interfaces\ShippingProvider $provider The shipping provider instance.
+ * @param WC_Order $order The order instance.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_get_order_shipping_provider', $provider, $order );
+}
+
+function wc_gzd_get_customer_order_return_request_key() {
+ $key = ( isset( $_REQUEST['key'] ) ? wc_clean( wp_unslash( $_REQUEST['key'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+
+ return $key;
+}
+
+function wc_gzd_customer_can_add_return_shipment( $order_id ) {
+ $can_view_shipments = false;
+
+ if ( isset( $_REQUEST['key'] ) && ! empty( $_REQUEST['key'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $key = wc_gzd_get_customer_order_return_request_key();
+
+ if ( ( $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) && ! empty( $key ) ) {
+
+ if ( hash_equals( $order_shipment->get_order_return_request_key(), $key ) ) {
+ $can_view_shipments = true;
+ }
+ }
+ } elseif ( is_user_logged_in() ) {
+ $can_view_shipments = current_user_can( 'view_order', $order_id );
+ }
+
+ /**
+ * Filters whether a logged in user (or guest) might view shipments belonging to an order or not.
+ *
+ * @param bool $can_view_shipments Whether the user (or guest) might see shipments or not.
+ * @param integer $order_id The order id.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_customer_can_view_shipments', $can_view_shipments, $order_id );
+}
+
+/**
+ * @param WC_Order|integer $order
+ */
+function wc_gzd_customer_return_needs_manual_confirmation( $order ) {
+ if ( is_numeric( $order ) ) {
+ $order = wc_get_order( $order );
+ }
+
+ if ( ! $order ) {
+ return true;
+ }
+
+ $needs_manual_confirmation = true;
+
+ if ( $provider = wc_gzd_get_order_shipping_provider( $order ) ) {
+ $needs_manual_confirmation = $provider->needs_manual_confirmation_for_returns();
+ }
+
+ /**
+ * Filter to decide whether a customer added return of a certain order
+ * needs manual confirmation by the shop manager or not.
+ *
+ * @param bool $needs_manual_confirmation Whether needs manual confirmation or not.
+ * @param WC_Order $order The order instance for which the return shall be created.
+ *
+ * @since 3.1.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_customer_return_needs_manual_confirmation', $needs_manual_confirmation, $order );
+}
+
+/**
+ * Get account shipments actions.
+ *
+ * @since 3.2.0
+ * @param int|Shipment $shipment Shipment instance or ID.
+ * @return array
+ */
+function wc_gzd_get_account_shipments_actions( $shipment ) {
+
+ if ( ! is_object( $shipment ) ) {
+ $shipment_id = absint( $shipment );
+ $shipment = wc_gzd_get_shipment( $shipment_id );
+ }
+
+ if ( ! $shipment ) {
+ return array();
+ }
+
+ $actions = array(
+ 'view' => array(
+ 'url' => $shipment->get_view_shipment_url(),
+ 'name' => _x( 'View', 'shipments', 'woocommerce-germanized' ),
+ ),
+ );
+
+ if ( 'return' === $shipment->get_type() && $shipment->has_label() && ! $shipment->has_status( 'delivered' ) ) {
+ $actions['download-label'] = array(
+ 'url' => $shipment->get_label()->get_download_url(),
+ 'name' => _x( 'Download label', 'shipments', 'woocommerce-germanized' ),
+ );
+ }
+
+ /**
+ * Filter to adjust available actions in the shipments table view on the customer account page
+ * for a specific shipment.
+ *
+ * @param string[] $actions Available actions containing an id as key and a URL and name.
+ * @param Shipment $shipment The shipment instance.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_account_shipments_actions', $actions, $shipment );
+}
+
+function wc_gzd_shipments_get_product( $the_product ) {
+ try {
+ if ( is_a( $the_product, '\Vendidero\Germanized\Shipments\Product' ) ) {
+ return $the_product;
+ }
+
+ return new \Vendidero\Germanized\Shipments\Product( $the_product );
+ } catch ( \Exception $e ) {
+ return false;
+ }
+}
+
+function wc_gzd_get_volume_dimension( $dimension, $to_unit, $from_unit = '' ) {
+ $to_unit = strtolower( $to_unit );
+
+ if ( empty( $from_unit ) ) {
+ $from_unit = strtolower( get_option( 'woocommerce_dimension_unit' ) );
+ }
+
+ // Unify all units to cm first.
+ if ( $from_unit !== $to_unit ) {
+ switch ( $from_unit ) {
+ case 'm':
+ $dimension *= 1000000;
+ break;
+ case 'mm':
+ $dimension *= 0.001;
+ break;
+ }
+
+ // Output desired unit.
+ switch ( $to_unit ) {
+ case 'm':
+ $dimension *= 0.000001;
+ break;
+ case 'mm':
+ $dimension *= 1000;
+ break;
+ }
+ }
+
+ return ( $dimension < 0 ) ? 0 : $dimension;
+}
+
+if ( ! function_exists( 'wc_gzd_wp_theme_get_element_class_name' ) ) {
+ /**
+ * Given an element name, returns a class name.
+ *
+ * If the WP-related function is not defined, return empty string.
+ *
+ * @param string $element The name of the element.
+ *
+ * @return string
+ */
+ function wc_gzd_wp_theme_get_element_class_name( $element ) {
+ if ( function_exists( 'wc_wp_theme_get_element_class_name' ) ) {
+ return wc_wp_theme_get_element_class_name( $element );
+ } elseif ( function_exists( 'wp_theme_get_element_class_name' ) ) {
+ return wp_theme_get_element_class_name( $element );
+ }
+
+ return '';
+ }
+}
+
+function wc_gzd_shipments_allow_deferred_sync( $type = 'shipments' ) {
+ $allow_defer = true;
+
+ if ( 'shipments' === $type || 'label' === $type || 'return_label' === $type ) {
+ if ( is_admin() && current_user_can( 'manage_woocommerce' ) ) {
+ $allow_defer = false;
+ }
+ }
+
+ if ( apply_filters( 'woocommerce_gzd_shipments_disable_deferred_sync', false ) ) {
+ $allow_defer = false;
+ }
+
+ return apply_filters( "woocommerce_gzd_shipments_allow_{$type}_deferred_sync", $allow_defer );
+}
+
+/**
+ * Forces a WP_Error object to be converted to a ShipmentError.
+ *
+ * @param $error
+ *
+ * @return mixed|\Vendidero\Germanized\Shipments\ShipmentError
+ */
+function wc_gzd_get_shipment_error( $error ) {
+ if ( ! is_wp_error( $error ) ) {
+ return $error;
+ } elseif ( is_a( $error, 'Vendidero\Germanized\Shipments\ShipmentError' ) ) {
+ return $error;
+ } else {
+ return \Vendidero\Germanized\Shipments\ShipmentError::from_wp_error( $error );
+ }
+}
diff --git a/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-template-hooks.php b/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-template-hooks.php
new file mode 100644
index 000000000..c982910df
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-template-hooks.php
@@ -0,0 +1,29 @@
+ false,
+ 'show_image' => false,
+ 'image_size' => array( 32, 32 ),
+ 'plain_text' => false,
+ 'sent_to_admin' => false,
+ );
+
+ $args = wp_parse_args( $args, $defaults );
+ $template = $args['plain_text'] ? 'emails/plain/email-shipment-items.php' : 'emails/email-shipment-items.php';
+
+ wc_get_template(
+ $template,
+ /**
+ * Filter to adjust the arguments passed to retrieving ShipmentItems for display in an Email.
+ *
+ * @param array $args Array containing the arguments passed.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ apply_filters(
+ 'woocommerce_gzd_email_shipment_items_args',
+ array(
+ 'shipment' => $shipment,
+ 'items' => $shipment->get_items(),
+ 'show_sku' => $args['show_sku'],
+ 'show_image' => $args['show_image'],
+ 'image_size' => $args['image_size'],
+ 'plain_text' => $args['plain_text'],
+ 'sent_to_admin' => $args['sent_to_admin'],
+ )
+ )
+ );
+
+ /**
+ * Filter that allows adjusting the HTML output of the shipment email item table.
+ *
+ * @param string $html The HTML output.
+ * @param Shipment $shipment The shipment instance.
+ *
+ * @since 3.0.0
+ * @package Vendidero/Germanized/Shipments
+ */
+ return apply_filters( 'woocommerce_gzd_email_shipment_items_table', ob_get_clean(), $shipment );
+ }
+}
+
+if ( ! function_exists( 'woocommerce_gzd_shipments_template_view_shipments' ) ) {
+
+ function woocommerce_gzd_shipments_template_view_shipments( $order_id ) {
+
+ if ( ( ! ( $order = wc_get_order( $order_id ) ) ) || ( ! current_user_can( 'view_order', $order_id ) ) ) {
+ echo '' . sprintf( esc_html_x( 'The following products cannot be returned due to their nature: %1$s', 'shipments', 'woocommerce-germanized' ), esc_html( implode( ', ', $product_names ) ) ) . '
'; + } + } + } + } +} diff --git a/packages/woocommerce-germanized-shipments/license.txt b/packages/woocommerce-germanized-shipments/license.txt new file mode 100644 index 000000000..16aa3cb39 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/license.txt @@ -0,0 +1,699 @@ +WooCommerce Germanized Shipments + +Copyright 2019 by the contributors + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +This program incorporates work covered by the following copyright and +permission notices: + + WooCommerce + +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright © 2007 Free Software Foundation, Inc.+ | + + + | +||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
+ |
+
+
+
+
+
+
+
+
+
|
+ ||||||||||
+ | + + + | +
+ get_product() ) : ?>
+ get_name() ); ?>
+
+ get_name() ); ?>
+
+
+ get_sku() ? ' ' . esc_html_x( 'SKU:', 'shipments', 'woocommerce-germanized' ) . ' ' . esc_html( $item->get_sku() ) . '' : '' ); ?> + + get_hook_prefix()}item_after_name", $item->get_id(), $item, $shipment ); + ?> + |
+ + get_quantity() ); ?>x + | +
+ + +
+ get_status( 'edit' ) ) . '" />'; + + return $result; + } + + private static function get_order_return_status_html( $order_shipment ) { + $status_html = '' . wc_gzd_get_shipment_order_return_status_name( $order_shipment->get_return_status() ) . ''; + + return $status_html; + } + + public static function refresh_shipment_packaging() { + check_ajax_referer( 'refresh-shipment-packaging', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['shipment_id'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $shipment_id = absint( $_POST['shipment_id'] ); + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + $response = array( + 'success' => true, + 'message' => '', + 'shipment_id' => $shipment_id, + 'needs_packaging_refresh' => true, + ); + + $data = array( + 'packaging_id' => isset( $_POST['shipment_packaging_id'][ $shipment_id ] ) ? absint( wc_clean( wp_unslash( $_POST['shipment_packaging_id'][ $shipment_id ] ) ) ) : '', + ); + + $shipment->set_props( $data ); + + $response['fragments'] = array( + '#shipment-' . $shipment->get_id() . ' .shipment-packaging-select' => self::get_packaging_select_html( $shipment ), + ); + + wp_send_json( $response ); + } + + protected static function get_packaging_select_html( $shipment ) { + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-shipment-packaging-select.php'; + $html = ob_get_clean(); + + return $html; + } + + public static function save_shipments() { + check_ajax_referer( 'edit-shipments', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order_id'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + ); + + $order_id = absint( $_POST['order_id'] ); + $active = isset( $_POST['active'] ) ? absint( $_POST['active'] ) : 0; + + if ( ! $order = wc_get_order( $order_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $order ) ) { + wp_send_json( $response_error ); + } + + // Refresh data + self::refresh_shipments( $order_shipment ); + + // Make sure that we are not applying more + $order_shipment->validate_shipment_item_quantities(); + + // Refresh statuses after adjusting quantities + self::refresh_status( $order_shipment ); + + $order_shipment->save(); + + $response['fragments'] = self::get_shipments_html( $order_shipment, $active ); + + self::send_json_success( $response, $order_shipment ); + } + + /** + * @param Order $order_shipment + * @param int $active + * + * @return array + */ + private static function get_shipments_html( $p_order_shipment, $p_active = 0 ) { + $order_shipment = $p_order_shipment; + $active_shipment = $p_active; + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-shipment-list.php'; + $html = ob_get_clean(); + + $order_status_html = self::get_global_order_status_html( $order_shipment->get_order() ); + + $fragments = array( + '#order-shipments-list' => $html, + '.order-shipping-status' => self::get_order_status_html( $order_shipment ), + '.order-return-status' => self::get_order_return_status_html( $order_shipment ), + '.order_data_column p.wc-order-status' => $order_status_html['status'], + 'input[name="post_status"]' => $order_status_html['input'], + ); + + if ( empty( $fragments['.order_data_column p.wc-order-status'] ) ) { + unset( $fragments['.order_data_column p.wc-order-status'] ); + unset( $fragments['input[name="post_status"]'] ); + } + + return $fragments; + } + + public static function add_return_shipment_load() { + check_ajax_referer( 'add-return-shipment-load', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['reference_id'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + 'fragments' => array(), + ); + + $order_id = absint( $_POST['reference_id'] ); + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) { + wp_send_json( $response_error ); + } + + static::refresh_shipments( $order_shipment ); + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-add-return-shipment-items.php'; + $response['fragments']['#wc-gzd-return-shipment-items'] = ob_get_clean(); + + self::send_json_success( $response, $order_shipment ); + } + + public static function add_shipment_item_load() { + check_ajax_referer( 'add-shipment-item-load', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['reference_id'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + 'fragments' => array(), + ); + + $shipment_id = absint( $_POST['reference_id'] ); + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $order = $shipment->get_order() ) { + wp_send_json( $response_error ); + } + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $order ) ) { + wp_send_json( $response_error ); + } + + static::refresh_shipments( $order_shipment ); + + $items = array(); + + if ( 'return' === $shipment->get_type() ) { + $items = $order_shipment->get_available_items_for_return( + array( + 'shipment_id' => $shipment->get_id(), + 'disable_duplicates' => true, + ) + ); + } else { + $items = $order_shipment->get_available_items_for_shipment( + array( + 'shipment_id' => $shipment_id, + 'disable_duplicates' => true, + ) + ); + } + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-add-shipment-item.php'; + $response['fragments']['.wc-gzd-shipment-add-items-table'] = ob_get_clean(); + + self::send_json_success( $response, $order_shipment ); + } + + public static function add_shipment_item_submit() { + check_ajax_referer( 'add-shipment-item-submit', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['reference_id'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + 'new_item' => '', + ); + + $shipment_id = absint( $_POST['reference_id'] ); + $original_item_id = isset( $_POST['item_id'] ) ? absint( $_POST['item_id'] ) : 0; + $item_quantity = isset( $_POST['item_qty'] ) ? absint( $_POST['item_qty'] ) : false; + + if ( false !== $item_quantity && 0 === $item_quantity ) { + $item_quantity = 1; + } + + if ( empty( $original_item_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $order = $shipment->get_order() ) { + wp_send_json( $response_error ); + } + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $order ) ) { + wp_send_json( $response_error ); + } + + static::refresh_shipments( $order_shipment ); + + // Make sure we are working with the shipment from the order + $shipment = $order_shipment->get_shipment( $shipment_id ); + + if ( 'return' === $shipment->get_type() ) { + $item = self::add_shipment_return_item( $order_shipment, $shipment, $original_item_id, $item_quantity ); + } else { + $item = self::add_shipment_order_item( $order_shipment, $shipment, $original_item_id, $item_quantity ); + } + + if ( ! $item ) { + wp_send_json( $response_error ); + } + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-shipment-item.php'; + $response['new_item'] = ob_get_clean(); + + $response['fragments'] = array( + '#shipment-' . $shipment->get_id() . ' .item-count:first' => self::get_item_count_html( $shipment, $order_shipment ), + ); + + self::send_json_success( $response, $order_shipment, $shipment ); + } + + /** + * @param Order $order_shipment + * @param ReturnShipment $shipment + * @param integer $parent_item_id + * @param integer $quantity + */ + private static function add_shipment_return_item( $order_shipment, $shipment, $order_item_id, $quantity ) { + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + if ( ! $shipment_item = $order_shipment->get_simple_shipment_item( $order_item_id ) ) { + wp_send_json( $response_error ); + } + + // No duplicates allowed + if ( $shipment->get_item_by_order_item_id( $order_item_id ) ) { + wp_send_json( $response_error ); + } + + // Check max quantity + $quantity_left = $order_shipment->get_item_quantity_left_for_returning( $shipment_item->get_order_item_id() ); + + if ( $quantity ) { + if ( $quantity > $quantity_left ) { + $quantity = $quantity_left; + } + } else { + $quantity = $quantity_left; + } + + if ( $item = wc_gzd_create_return_shipment_item( $shipment, $shipment_item, array( 'quantity' => $quantity ) ) ) { + $shipment->add_item( $item ); + $shipment->save(); + } + + return $item; + } + + /** + * @param Order $order_shipment + * @param SimpleShipment $shipment + * @param integer $order_item_id + * @param integer $quantity + */ + private static function add_shipment_order_item( $order_shipment, $shipment, $order_item_id, $quantity ) { + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $order = $order_shipment->get_order(); + + if ( ! $order_item = $order->get_item( $order_item_id ) ) { + wp_send_json( $response_error ); + } + + // No duplicates allowed + if ( $shipment->get_item_by_order_item_id( $order_item_id ) ) { + wp_send_json( $response_error ); + } + + // Check max quantity + $quantity_left = $order_shipment->get_item_quantity_left_for_shipping( $order_item ); + + if ( $quantity ) { + if ( $quantity > $quantity_left ) { + $quantity = $quantity_left; + } + } else { + $quantity = $quantity_left; + } + + if ( $item = wc_gzd_create_shipment_item( $shipment, $order_item, array( 'quantity' => $quantity ) ) ) { + $shipment->add_item( $item ); + $shipment->save(); + } + + return $item; + } + + private static function get_item_count_html( $p_shipment, $p_order_shipment ) { + $shipment = $p_shipment; + + // Refresh the instance to make sure we are working with the same object + $shipment->set_order_shipment( $p_order_shipment ); + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-shipment-item-count.php'; + $html = ob_get_clean(); + + return $html; + } + + public static function remove_shipment_item() { + check_ajax_referer( 'edit-shipments', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['shipment_id'] ) || ! isset( $_POST['item_id'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + 'item_id' => '', + ); + + $shipment_id = absint( $_POST['shipment_id'] ); + $item_id = absint( $_POST['item_id'] ); + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $item = $shipment->get_item( $item_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $shipment->get_order_id() ) ) { + wp_send_json( $response_error ); + } + + $shipment->remove_item( $item_id ); + $shipment->save(); + + $response['item_id'] = $item_id; + $response['fragments'] = array( + '#shipment-' . $shipment->get_id() . ' .item-count:first' => self::get_item_count_html( $shipment, $order_shipment ), + ); + + self::send_json_success( $response, $order_shipment, $shipment ); + } + + public static function limit_shipment_item_quantity() { + check_ajax_referer( 'edit-shipments', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['shipment_id'] ) || ! isset( $_POST['item_id'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error processing the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + 'max_quantity' => '', + 'item_id' => '', + ); + + $shipment_id = absint( $_POST['shipment_id'] ); + $item_id = absint( $_POST['item_id'] ); + $quantity = isset( $_POST['quantity'] ) ? (int) $_POST['quantity'] : 1; + $quantity = $quantity <= 0 ? 1 : $quantity; + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $order = $shipment->get_order() ) { + wp_send_json( $response_error ); + } + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $order ) ) { + wp_send_json( $response_error ); + } + + // Make sure the shipment order gets notified about changes + if ( ! $shipment = $order_shipment->get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $item = $shipment->get_item( $item_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $order_item = $order->get_item( $item->get_order_item_id() ) ) { + wp_send_json( $response_error ); + } + + static::refresh_shipments( $order_shipment ); + + $quantity_max = 0; + + if ( 'return' === $shipment->get_type() ) { + $quantity_max = $order_shipment->get_item_quantity_left_for_returning( + $item->get_order_item_id(), + array( + 'exclude_current_shipment' => true, + 'shipment_id' => $shipment->get_id(), + ) + ); + } else { + $quantity_max = $order_shipment->get_item_quantity_left_for_shipping( + $order_item, + array( + 'exclude_current_shipment' => true, + 'shipment_id' => $shipment->get_id(), + ) + ); + } + + $response['item_id'] = $item_id; + $response['max_quantity'] = $quantity_max; + + if ( $quantity > $quantity_max ) { + $quantity = $quantity_max; + } + + $shipment->update_item_quantity( $item_id, $quantity ); + + $response['fragments'] = array( + '#shipment-' . $shipment->get_id() . ' .item-count:first' => self::get_item_count_html( $shipment, $order_shipment ), + ); + + self::send_json_success( $response, $order_shipment, $shipment ); + } + + /** + * @param $response + * @param Order $order_shipment + * @param Shipment|bool $shipment + */ + private static function send_json_success( $response, $order_shipment, $current_shipment = false ) { + $available_items = $order_shipment->get_available_items_for_shipment(); + $response['shipments'] = array(); + + foreach ( $order_shipment->get_shipments() as $shipment ) { + $shipment->set_order_shipment( $order_shipment ); + + $response['shipments'][ $shipment->get_id() ] = array( + 'is_editable' => $shipment->is_editable(), + 'needs_items' => $shipment->needs_items( array_keys( $available_items ) ), + 'weight' => wc_format_localized_decimal( $shipment->get_content_weight() ), + 'length' => wc_format_localized_decimal( $shipment->get_content_length() ), + 'width' => wc_format_localized_decimal( $shipment->get_content_width() ), + 'height' => wc_format_localized_decimal( $shipment->get_content_height() ), + 'total_weight' => wc_format_localized_decimal( $shipment->get_total_weight() ), + ); + } + + $response['order_needs_new_shipments'] = $order_shipment->needs_shipping(); + $response['order_needs_new_returns'] = $order_shipment->needs_return(); + + if ( $current_shipment ) { + if ( ! isset( $response['fragments'] ) ) { + $response['fragments'] = array(); + } + + $response['needs_packaging_refresh'] = true; + $response['shipment_id'] = $current_shipment->get_id(); + $response['fragments'][ '#shipment-' . $current_shipment->get_id() . ' .shipment-packaging-select' ] = self::get_packaging_select_html( $current_shipment ); + } + + wp_send_json( $response ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Api.php b/packages/woocommerce-germanized-shipments/src/Api.php new file mode 100644 index 000000000..258ccefe9 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Api.php @@ -0,0 +1,233 @@ +set_data( array_merge( $response->data, self::get_product_data( $product, $context ) ) ); + } + + return $response; + } + + private static function get_product_data( $product, $context = 'view' ) { + $data = array(); + + if ( $shipments_product = wc_gzd_shipments_get_product( $product ) ) { + $data['hs_code'] = $shipments_product->get_hs_code( $context ); + $data['customs_description'] = $shipments_product->get_customs_description( $context ); + $data['manufacture_country'] = $shipments_product->get_manufacture_country( $context ); + } + + return $data; + } + + /** + * @param \WC_Product $product + * @param $request + * + * @return \WC_Product $product + */ + public static function update_product( $product, $request ) { + if ( $shipments_product = wc_gzd_shipments_get_product( $product ) ) { + if ( isset( $request['customs_description'] ) ) { + $shipments_product->set_customs_description( wc_clean( wp_unslash( $request['customs_description'] ) ) ); + } + + if ( isset( $request['hs_code'] ) ) { + $shipments_product->set_hs_code( wc_clean( wp_unslash( $request['hs_code'] ) ) ); + } + + if ( isset( $request['manufacture_country'] ) ) { + $shipments_product->set_manufacture_country( wc_clean( wp_unslash( $request['manufacture_country'] ) ) ); + } + } + + return $product; + } + + public static function remove_status_prefix( $status ) { + if ( 'gzd-' === substr( $status, 0, 4 ) ) { + $status = substr( $status, 4 ); + } + + return $status; + } + + /** + * Extend schema. + * + * @since 1.0.0 + * + * @param array $schema_properties Data used to create the order. + * + * @return array + */ + public static function order_schema( $schema_properties ) { + $statuses = array_map( array( __CLASS__, 'remove_status_prefix' ), array_keys( wc_gzd_get_shipment_order_shipping_statuses() ) ); + + $schema_properties['shipping_status'] = array( + 'description' => _x( 'Shipping status', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'enum' => $statuses, + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ); + + return $schema_properties; + } + + /** + * Extend product variation schema. + * + * @since 1.0.0 + * + * @param array $schema_properties Data used to create the product. + * + * @return array + */ + public static function product_variation_schema( $schema_properties ) { + $schema_properties['customs_description'] = array( + 'description' => _x( 'Customs description', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ); + + $schema_properties['hs_code'] = array( + 'description' => _x( 'HS-Code', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ); + + $schema_properties['manufacture_country'] = array( + 'description' => _x( 'Country of manufacture', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ); + + return $schema_properties; + } + + /** + * Extend product schema. + * + * @since 1.0.0 + * + * @param array $schema_properties Data used to create the product. + * + * @return array + */ + public static function product_schema( $schema_properties ) { + $schema_properties['customs_description'] = array( + 'description' => _x( 'Customs description', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ); + + $schema_properties['hs_code'] = array( + 'description' => _x( 'HS-Code (Customs)', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ); + + $schema_properties['manufacture_country'] = array( + 'description' => _x( 'Country of manufacture (Customs)', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ); + + return $schema_properties; + } + + /** + * @param WP_REST_Response $response + * @param $post + * @param WP_REST_Request $request + * + * @return mixed + */ + public static function prepare_order_shipments( $response, $post, $request ) { + $order = wc_get_order( $post ); + $response_order_data = $response->get_data(); + $response_order_data['shipments'] = array(); + $response_order_data['shipping_status'] = 'no-shipping-needed'; + + if ( $order ) { + $order_shipment = wc_gzd_get_shipment_order( $order ); + $shipments = $order_shipment->get_shipments(); + + if ( ! empty( $shipments ) ) { + foreach ( $shipments as $shipment ) { + $response_order_data['shipments'][] = ShipmentsController::prepare_shipment( $shipment, 'view', $request['dp'] ); + } + } + + $response_order_data['shipping_status'] = $order_shipment->get_shipping_status(); + } + + $response->set_data( $response_order_data ); + + return $response; + } + + public static function order_shipments_schema( $schema ) { + $schema['shipments'] = array( + 'description' => _x( 'List of shipments.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => ShipmentsController::get_single_item_schema(), + ); + + return $schema; + } + + protected static function get_shipment_statuses() { + $statuses = array(); + + foreach ( array_keys( wc_gzd_get_shipment_statuses() ) as $status ) { + $statuses[] = str_replace( 'gzd-', '', $status ); + } + + return $statuses; + } + + public static function register_controllers( $controller ) { + $controller['wc/v3']['shipments'] = ShipmentsController::class; + + return $controller; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Automation.php b/packages/woocommerce-germanized-shipments/src/Automation.php new file mode 100644 index 000000000..8303367d3 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Automation.php @@ -0,0 +1,336 @@ +queue(); + + /** + * Cancel outstanding events and queue new. + */ + $queue->cancel_all( 'woocommerce_gzd_shipments_order_auto_sync_callback', $args, 'woocommerce-gzd-shipments-order-sync' ); + } + + /** + * Make sure that his callback is only executed once for new order requests. + * + * @param WC_Order $order + * + * @return void + */ + public static function after_new_order( $order ) { + if ( self::$current_new_order_id === $order->get_id() ) { + self::maybe_create_shipments( $order ); + + remove_action( 'woocommerce_after_order_object_save', array( __CLASS__, 'after_new_order' ), 150 ); + self::$current_new_order_id = null; + } + } + + /** + * @param $order_id + * @param $old_status + * @param $new_status + * @param WC_Order $order + */ + public static function maybe_mark_shipments_shipped( $order_id, $old_status, $new_status, $order ) { + /** + * Filter to decide which order status is used to determine if a order + * is completed or not to update contained shipment statuses to shipped. + * Does only take effect if the automation option has been set within the shipment settings. + * + * @param string $status The current order status. + * @param integer $order_id The order id. + * + * @since 3.0.5 + * @package Vendidero/Germanized/Shipments + */ + if ( apply_filters( 'woocommerce_gzd_shipments_order_completed_status', 'completed', $order_id ) === $new_status ) { + // Make sure that MetaBox is saved before we process automation + if ( self::is_admin_edit_order_request() ) { + add_action( 'woocommerce_process_shop_order_meta', array( __CLASS__, 'mark_shipments_shipped' ), 80 ); + } else { + self::mark_shipments_shipped( $order_id ); + } + } + } + + public static function is_admin_edit_order_request() { + $is_admin_edit_order_request = ( isset( $_POST['action'] ) && ( ( 'editpost' === $_POST['action'] && isset( $_POST['post_type'] ) && 'shop_order' === $_POST['post_type'] ) || 'edit_order' === $_POST['action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + + /** + * Check whether the hook has already been firing. + */ + if ( did_action( 'woocommerce_process_shop_order_meta' ) && ! doing_action( 'woocommerce_process_shop_order_meta' ) ) { + $is_admin_edit_order_request = false; + } + + return $is_admin_edit_order_request; + } + + public static function mark_shipments_shipped( $order_id ) { + if ( $order = wc_get_order( $order_id ) ) { + if ( $shipment_order = wc_gzd_get_shipment_order( $order ) ) { + foreach ( $shipment_order->get_simple_shipments() as $shipment ) { + if ( ! $shipment->is_shipped() ) { + $shipment->update_status( 'shipped' ); + } + } + } + } + } + + /** + * Mark the order as completed if the order is fully shipped. + * + * @param $order_id + */ + public static function mark_order_completed( $order_id ) { + if ( $order = wc_get_order( $order_id ) ) { + + /** + * By default do not mark orders (via invoice) as completed after shipped as + * the order will be shipped before the invoice was paid. + */ + $mark_as_completed = ! in_array( $order->get_payment_method(), array( 'invoice' ), true ) ? true : false; + + /** + * Filter that allows to conditionally disable automatic + * order completion after the shipments are marked as shipped. + * + * @param boolean $mark_as_completed Whether to mark the order as completed or not. + * @param integer $order_id The order id. + * + * @since 3.2.3 + * @package Vendidero/Germanized/Shipments + */ + if ( ! apply_filters( 'woocommerce_gzd_shipment_order_mark_as_completed', $mark_as_completed, $order_id ) ) { + return; + } + + /** + * Filter to adjust the new status of an order after all it's required + * shipments have been marked as shipped. Does only take effect if the automation option has been set + * within the shipment settings. + * + * @param string $status The order status to be used. + * @param integer $order_id The order id. + * + * @since 3.0.5 + * @package Vendidero/Germanized/Shipments + */ + $order->update_status( apply_filters( 'woocommerce_gzd_shipment_order_completed_status', 'completed', $order_id ), _x( 'Order is fully shipped.', 'shipments', 'woocommerce-germanized' ) ); + } + } + + /** + * @param $order + * + * @return Shipment[]|\WP_Error + */ + public static function create_shipments( $order ) { + $result = array(); + + if ( is_numeric( $order ) ) { + $order = wc_get_order( $order ); + } + + if ( ! $order ) { + return $result; + } + + $shipment_status = Package::get_setting( 'auto_default_status' ); + + if ( empty( $shipment_status ) ) { + $shipment_status = 'processing'; + } + + /** + * In case the order is already completed, e.g. while asynchronously creating shipments, make sure to update the shipment status. + */ + if ( 'yes' === Package::get_setting( 'auto_order_completed_shipped_enable' ) && apply_filters( 'woocommerce_gzd_shipments_order_completed_status', 'completed', $order->get_id() ) === $order->get_status() ) { + $shipment_status = 'shipped'; + } + + /** + * Filter to disable automatically creating shipments for a specific order. + * + * @param string $enable Whether to create or not create shipments. + * @param integer $order_id The order id. + * @param WC_Order $order The order instance. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + if ( ! apply_filters( 'woocommerce_gzd_auto_create_shipments_for_order', true, $order->get_id(), $order ) ) { + return $result; + } + + if ( $order_shipment = wc_gzd_get_shipment_order( $order ) ) { + /** + * Sync existing shipments and items before creating new shipments + */ + foreach ( $order_shipment->get_simple_shipments() as $shipment ) { + if ( $shipment->is_editable() ) { + $shipment->sync(); + $shipment->sync_items(); + $shipment->save(); + } + } + + do_action( 'woocommerce_gzd_before_auto_create_shipments_for_order', $order->get_id(), $shipment_status, $order ); + + $result = $order_shipment->create_shipments( $shipment_status ); + + do_action( 'woocommerce_gzd_after_auto_create_shipments_for_order', $order->get_id(), $shipment_status, $order, $result ); + } + + if ( is_wp_error( $result ) ) { + return $result; + } + + return $result; + } + + protected static function get_auto_statuses() { + $statuses = (array) Package::get_setting( 'auto_statuses' ); + $clean_statuses = array(); + + if ( ! empty( $statuses ) ) { + foreach ( $statuses as $status ) { + $status = trim( str_replace( 'wc-', '', $status ) ); + + if ( ! in_array( $status, $clean_statuses, true ) ) { + $clean_statuses[] = $status; + } + } + } + + return $clean_statuses; + } + + public static function maybe_create_shipments( $order_id, $args = array() ) { + $args = wp_parse_args( + (array) $args, + array( + 'allow_deferred_sync' => wc_gzd_shipments_allow_deferred_sync( 'shipments' ), + ) + ); + + $statuses = self::get_auto_statuses(); + $has_status = empty( $statuses ) ? true : false; + + if ( ! $has_status ) { + if ( $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) { + $has_status = $order_shipment->get_order()->has_status( $statuses ); + } + } + + if ( $has_status ) { + // Make sure that MetaBox is saved before we process automation + if ( self::is_admin_edit_order_request() ) { + add_action( + 'woocommerce_process_shop_order_meta', + function( $order_id ) { + self::create_shipments( $order_id ); + }, + 70 + ); + } else { + if ( $args['allow_deferred_sync'] ) { + Package::log( 'Deferring order #' . $order_id . ' shipments sync' ); + + $queue = WC()->queue(); + $defer_args = array( + 'order_id' => $order_id, + ); + + /** + * Cancel outstanding events and queue new. + */ + self::cancel_deferred_sync( $defer_args ); + + $queue->schedule_single( + time() + 50, + 'woocommerce_gzd_shipments_order_auto_sync_callback', + $defer_args, + 'woocommerce-gzd-shipments-order-sync' + ); + } else { + self::create_shipments( $order_id ); + } + } + } + } + + public static function auto_sync_callback( $order_id ) { + /** + * Maybe cancel duplicate deferred syncs. + */ + self::cancel_deferred_sync( array( 'order_id' => $order_id ) ); + + Package::log( 'Starting order #' . $order_id . ' shipments sync (deferred)' ); + + self::create_shipments( $order_id ); + + return true; + } + + public static function maybe_create_subscription_shipments( $renewal_order ) { + self::maybe_create_shipments( $renewal_order->get_id() ); + + return $renewal_order; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/CacheHelper.php b/packages/woocommerce-germanized-shipments/src/CacheHelper.php new file mode 100644 index 000000000..7be2abdfe --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/CacheHelper.php @@ -0,0 +1,75 @@ +set_date_created( time() ); + + $data = array( + 'label_number' => $label->get_number(), + 'label_shipment_id' => $label->get_shipment_id(), + 'label_path' => $label->get_path(), + 'label_product_id' => $label->get_product_id(), + 'label_type' => $label->get_type(), + 'label_shipping_provider' => $label->get_shipping_provider(), + 'label_parent_id' => $label->get_parent_id(), + 'label_date_created' => gmdate( 'Y-m-d H:i:s', $label->get_date_created( 'edit' )->getOffsetTimestamp() ), + 'label_date_created_gmt' => gmdate( 'Y-m-d H:i:s', $label->get_date_created( 'edit' )->getTimestamp() ), + ); + + $wpdb->insert( + $wpdb->gzd_shipment_labels, + $data + ); + + $label_id = $wpdb->insert_id; + + if ( $label_id ) { + $label->set_id( $label_id ); + + $this->save_label_data( $label ); + + $label->save_meta_data(); + $label->apply_changes(); + + $this->clear_caches( $label ); + + $hook_postfix = $this->get_hook_postfix( $label ); + + /** + * Action fires when a new DHL label has been created. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * label type e.g. return in case it is not a simple label. + * + * @param integer $label_id The label id. + * @param Label $label The label instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + do_action( "woocommerce_gzd_shipment_{$hook_postfix}label_created", $label_id, $label ); + } + } + + /** + * @param \Vendidero\Germanized\Shipments\Labels\Label $label + * + * @return string + */ + protected function get_hook_postfix( $label ) { + $prefix = $label->get_shipping_provider() . '_'; + + if ( 'simple' !== $label->get_type() ) { + $prefix = $prefix . $label->get_type() . '_'; + } + + return $prefix; + } + + /** + * Method to update a label in the database. + * + * @param \Vendidero\Germanized\Shipments\Labels\Label $label Label object. + */ + public function update( &$label ) { + global $wpdb; + + $updated_props = array(); + $core_props = $this->core_props; + $changed_props = array_keys( $label->get_changes() ); + $label_data = array(); + + foreach ( $changed_props as $prop ) { + + if ( ! in_array( $prop, $core_props, true ) ) { + continue; + } + + switch ( $prop ) { + case 'date_created': + $label_data[ 'label' . $prop ] = $label->{'get_' . $prop}( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $label->{'get_' . $prop}( 'edit' )->getOffsetTimestamp() ) : null; + $label_data[ 'label_' . $prop . '_gmt' ] = $label->{'get_' . $prop}( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $label->{'get_' . $prop}( 'edit' )->getTimestamp() ) : null; + break; + default: + if ( is_callable( array( $label, 'get_' . $prop ) ) ) { + $label_data[ 'label_' . $prop ] = $label->{'get_' . $prop}( 'edit' ); + } + break; + } + } + + if ( ! empty( $label_data ) ) { + $wpdb->update( + $wpdb->gzd_shipment_labels, + $label_data, + array( 'label_id' => $label->get_id() ) + ); + } + + $this->save_label_data( $label ); + $label->save_meta_data(); + + $label->apply_changes(); + $this->clear_caches( $label ); + + $hook_postfix = $this->get_hook_postfix( $label ); + + /** + * Action fires after a DHL label has been updated in the DB. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * label type e.g. return in case it is not a simple label. + * + * @param integer $label_id The label id. + * @param Label $label The label instance. + * @param array $changed_props Properties that have been changed. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + do_action( "woocommerce_gzd_shipment_{$hook_postfix}label_updated", $label->get_id(), $label, $changed_props ); + } + + /** + * Remove a shipment from the database. + * + * @since 3.0.0 + * @param \Vendidero\Germanized\Shipments\Labels\Label $label Label object. + * @param bool $force_delete Unused param. + */ + public function delete( &$label, $force_delete = false ) { + global $wpdb; + + if ( $file = $label->get_file() ) { + wp_delete_file( $file ); + } + + /* + * Delete additional files e.g. export documents + */ + foreach ( $label->get_additional_file_types() as $file_type ) { + if ( $file = $label->get_file( $file_type ) ) { + wp_delete_file( $file ); + } + } + + $wpdb->delete( $wpdb->gzd_shipment_labels, array( 'label_id' => $label->get_id() ), array( '%d' ) ); + $wpdb->delete( $wpdb->gzd_shipment_labelmeta, array( 'gzd_shipment_label_id' => $label->get_id() ), array( '%d' ) ); + + $this->clear_caches( $label ); + + $children = $label->get_children(); + + foreach ( $children as $child ) { + $child->delete( $force_delete ); + } + + $hook_postfix = $this->get_hook_postfix( $label ); + + /** + * Action fires after a DHL label has been deleted from DB. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * label type e.g. return in case it is not a simple label. + * + * @param integer $label_id The label id. + * @param \Vendidero\Germanized\DHL\Label $label The label object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + do_action( "woocommerce_gzd_shipment_{$hook_postfix}label_deleted", $label->get_id(), $label ); + } + + /** + * Read a shipment from the database. + * + * @since 3.0.0 + * + * @param \Vendidero\Germanized\Shipments\Labels\Label $label Label object. + * + * @throws Exception Throw exception if invalid shipment. + */ + public function read( &$label ) { + global $wpdb; + + $data = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$wpdb->gzd_shipment_labels} WHERE label_id = %d LIMIT 1", + $label->get_id() + ) + ); + + if ( $data ) { + $label->set_props( + array( + 'shipment_id' => $data->label_shipment_id, + 'number' => $data->label_number, + 'path' => $data->label_path, + 'parent_id' => $data->label_parent_id, + 'shipping_provider' => $data->label_shipping_provider, + 'product_id' => $data->label_product_id, + 'date_created' => Package::is_valid_mysql_date( $data->label_date_created_gmt ) ? wc_string_to_timestamp( $data->label_date_created_gmt ) : null, + ) + ); + + $this->read_label_data( $label ); + $label->read_meta_data(); + $label->set_object_read( true ); + + $hook_postfix = $this->get_hook_postfix( $label ); + + /** + * Action fires after reading a DHL label from DB. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * label type e.g. return in case it is not a simple label. + * + * @param \Vendidero\Germanized\Shipments\Labels\Label $label The label object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + do_action( "woocommerce_gzd_shipment_{$hook_postfix}label_loaded", $label ); + } else { + throw new Exception( _x( 'Invalid label.', 'shipments', 'woocommerce-germanized' ) ); + } + } + + /** + * Clear any caches. + * + * @param \Vendidero\Germanized\Shipments\Labels\Label $label Label object. + * @since 3.0.0 + */ + protected function clear_caches( &$label ) { + wp_cache_delete( $label->get_id(), $this->meta_type . '_meta' ); + } + + /* + |-------------------------------------------------------------------------- + | Additional Methods + |-------------------------------------------------------------------------- + */ + + /** + * Get the label type based on label ID. + * + * @param int $label_id Label id. + * @return bool|mixed + */ + public function get_label_data( $label_id ) { + global $wpdb; + + $data = $wpdb->get_row( + $wpdb->prepare( + "SELECT label_type, label_shipping_provider FROM {$wpdb->gzd_shipment_labels} WHERE label_id = %d LIMIT 1", + $label_id + ) + ); + + if ( ! empty( $data ) ) { + return (object) array( + 'shipping_provider' => $data->label_shipping_provider, + 'type' => $data->label_type, + ); + } + + return false; + } + + /** + * Read extra data associated with the shipment. + * + * @param \Vendidero\Germanized\Shipments\Labels\Label $label Label object. + * @since 3.0.0 + */ + protected function read_label_data( &$label ) { + $props = array(); + $meta_keys = $this->internal_meta_keys; + + foreach ( $label->get_extra_data_keys() as $key ) { + $meta_keys[] = '_' . $key; + } + + foreach ( $meta_keys as $meta_key ) { + $props[ substr( $meta_key, 1 ) ] = get_metadata( $this->meta_type, $label->get_id(), $meta_key, true ); + } + + $label->set_props( $props ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Labels\Label $label + */ + protected function save_label_data( &$label ) { + $updated_props = array(); + $meta_key_to_props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + $prop_name = substr( $meta_key, 1 ); + + if ( in_array( $prop_name, $this->core_props, true ) ) { + continue; + } + + $meta_key_to_props[ $meta_key ] = $prop_name; + } + + // Make sure to take extra data (like product url or text for external products) into account. + $extra_data_keys = $label->get_extra_data_keys(); + + foreach ( $extra_data_keys as $key ) { + $meta_key_to_props[ '_' . $key ] = $key; + } + + $props_to_update = $this->get_props_to_update( $label, $meta_key_to_props, $this->meta_type ); + + foreach ( $props_to_update as $meta_key => $prop ) { + + if ( ! is_callable( array( $label, "get_$prop" ) ) ) { + continue; + } + + $value = $label->{"get_$prop"}( 'edit' ); + $value = is_string( $value ) ? wp_slash( $value ) : $value; + + if ( is_bool( $value ) ) { + $value = wc_bool_to_string( $value ); + } + + $updated = $this->update_or_delete_meta( $label, $meta_key, $value ); + + if ( $updated ) { + $updated_props[] = $prop; + } + } + + /** + * Action fires after DHL label meta properties have been updated. + * + * @param \Vendidero\Germanized\Shipments\Labels\Label $label The label object. + * @param array $updated_props The updated properties. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + do_action( 'woocommerce_gzd_shipment_label_object_updated_props', $label, $updated_props ); + } + + /** + * Update meta data in, or delete it from, the database. + * + * Avoids storing meta when it's either an empty string or empty array. + * Other empty values such as numeric 0 and null should still be stored. + * Data-stores can force meta to exist using `must_exist_meta_keys`. + * + * Note: WordPress `get_metadata` function returns an empty string when meta data does not exist. + * + * @param WC_Data $object The WP_Data object (WC_Coupon for coupons, etc). + * @param string $meta_key Meta key to update. + * @param mixed $meta_value Value to save. + * + * @since 3.6.0 Added to prevent empty meta being stored unless required. + * + * @return bool True if updated/deleted. + */ + protected function update_or_delete_meta( $object, $meta_key, $meta_value ) { + if ( in_array( $meta_value, array( array(), '' ), true ) && ! in_array( $meta_key, $this->must_exist_meta_keys, true ) ) { + $updated = delete_metadata( $this->meta_type, $object->get_id(), $meta_key ); + } else { + $updated = update_metadata( $this->meta_type, $object->get_id(), $meta_key, $meta_value ); + } + + return (bool) $updated; + } + + /** + * Get valid WP_Query args from a WC_Order_Query's query variables. + * + * @since 3.0.6 + * @param array $query_vars query vars from a WC_Order_Query. + * @return array + */ + protected function get_wp_query_args( $query_vars ) { + global $wpdb; + + $wp_query_args = parent::get_wp_query_args( $query_vars ); + + // Force type to be existent + if ( isset( $query_vars['type'] ) ) { + $wp_query_args['type'] = $query_vars['type']; + } + + 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(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + } + + // Allow Woo to treat these props as date query compatible + $date_queries = array( + 'date_created' => 'post_date', + ); + + 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 ); + $meta_query_index = array_search( $db_key, $existing_queries, true ); + + if ( false !== $meta_query_index ) { + unset( $wp_query_args['meta_query'][ $meta_query_index ] ); + } + + $wp_query_args = $this->parse_date_for_wp_query( $query_vars[ $query_var_key ], $db_key, $wp_query_args ); + } + } + + /** + * Replace date query columns after Woo parsed dates. + * Include table name because otherwise WP_Date_Query won't accept our custom column. + */ + if ( isset( $wp_query_args['date_query'] ) ) { + foreach ( $wp_query_args['date_query'] as $key => $date_query ) { + if ( isset( $date_query['column'] ) && in_array( $date_query['column'], $date_queries, true ) ) { + $wp_query_args['date_query'][ $key ]['column'] = $wpdb->gzd_shipment_labels . '.label_' . array_search( $date_query['column'], $date_queries, true ); + } + } + } + + if ( ! isset( $query_vars['paginate'] ) || ! $query_vars['paginate'] ) { + $wp_query_args['no_found_rows'] = true; + } + + /** + * Filter to adjust the DHL label query args after parsing them. + * + * @param array $wp_query_args Parsed query arguments. + * @param array $query_vars Original query arguments. + * @param Label $data_store The label data store. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + return apply_filters( 'woocommerce_gzd_shipment_label_data_store_get_labels_query', $wp_query_args, $query_vars, $this ); + } + + public function get_query_args( $query_vars ) { + return $this->get_wp_query_args( $query_vars ); + } + + /** + * Table structure is slightly different between meta types, this function will return what we need to know. + * + * @since 3.0.0 + * @return array Array elements: table, object_id_field, meta_id_field + */ + protected function get_db_info() { + global $wpdb; + + $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. + $table = $wpdb->gzd_shipment_labelmeta; + $object_id_field = $this->meta_type . '_id'; + + if ( ! empty( $this->object_id_field_for_meta ) ) { + $object_id_field = $this->object_id_field_for_meta; + } + + return array( + 'table' => $table, + 'object_id_field' => $object_id_field, + 'meta_id_field' => $meta_id_field, + ); + } + + public function get_label_count() { + global $wpdb; + + return absint( $wpdb->get_var( "SELECT COUNT( * ) FROM {$wpdb->gzd_shipment_labels}" ) ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/DataStores/Packaging.php b/packages/woocommerce-germanized-shipments/src/DataStores/Packaging.php new file mode 100644 index 000000000..742a70d40 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/DataStores/Packaging.php @@ -0,0 +1,783 @@ +set_date_created( time() ); + + $data = array( + 'packaging_type' => $packaging->get_type(), + 'packaging_description' => $packaging->get_description(), + 'packaging_weight' => $packaging->get_weight(), + 'packaging_max_content_weight' => $packaging->get_max_content_weight(), + 'packaging_length' => $packaging->get_length(), + 'packaging_width' => $packaging->get_width(), + 'packaging_height' => $packaging->get_height(), + 'packaging_inner_length' => $packaging->get_inner_length(), + 'packaging_inner_width' => $packaging->get_inner_width(), + 'packaging_inner_height' => $packaging->get_inner_height(), + 'packaging_order' => $packaging->get_order(), + 'packaging_date_created' => $packaging->get_date_created( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $packaging->get_date_created( 'edit' )->getOffsetTimestamp() ) : null, + 'packaging_date_created_gmt' => $packaging->get_date_created( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $packaging->get_date_created( 'edit' )->getTimestamp() ) : null, + ); + + $wpdb->insert( + $wpdb->gzd_packaging, + $data + ); + + $packaging_id = $wpdb->insert_id; + + if ( $packaging_id ) { + $packaging->set_id( $packaging_id ); + + $this->save_packaging_data( $packaging ); + + $packaging->save_meta_data(); + $packaging->apply_changes(); + + $this->clear_caches( $packaging ); + + /** + * Action that indicates that a new Packaging has been created in the DB. + * + * @param integer $packaging_id The packaging id. + * @param \Vendidero\Germanized\Shipments\Packaging $packaging The packaging instance. + * + * @since 3.3.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_new_packaging', $packaging_id, $packaging ); + } + } + + /** + * Method to update a packaging in the database. + * + * @param \Vendidero\Germanized\Shipments\Packaging $packaging Packaging object. + */ + public function update( &$packaging ) { + global $wpdb; + + $updated_props = array(); + $core_props = $this->core_props; + $changed_props = array_keys( $packaging->get_changes() ); + $packaging_data = array(); + + foreach ( $changed_props as $prop ) { + if ( ! in_array( $prop, $core_props, true ) ) { + continue; + } + + switch ( $prop ) { + case 'date_created': + if ( is_callable( array( $packaging, 'get_' . $prop ) ) ) { + $packaging_data[ 'packaging_' . $prop ] = $packaging->{'get_' . $prop}( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $packaging->{'get_' . $prop}( 'edit' )->getOffsetTimestamp() ) : null; + $packaging_data[ 'packaging_' . $prop . '_gmt' ] = $packaging->{'get_' . $prop}( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $packaging->{'get_' . $prop}( 'edit' )->getTimestamp() ) : null; + } + break; + default: + if ( is_callable( array( $packaging, 'get_' . $prop ) ) ) { + $packaging_data[ 'packaging_' . $prop ] = $packaging->{'get_' . $prop}( 'edit' ); + } + break; + } + } + + if ( ! empty( $packaging_data ) ) { + $wpdb->update( + $wpdb->gzd_packaging, + $packaging_data, + array( 'packaging_id' => $packaging->get_id() ) + ); + } + + $this->save_packaging_data( $packaging ); + + $packaging->save_meta_data(); + $packaging->apply_changes(); + + $this->clear_caches( $packaging ); + + /** + * Action that indicates that a Packaging has been updated in the DB. + * + * @param integer $packaging_id The packaging id. + * @param \Vendidero\Germanized\Shipments\Packaging $packaging The packaging instance. + * + * @since 3.3.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_packaging_updated', $packaging->get_id(), $packaging ); + } + + /** + * Remove a Packaging from the database. + * + * @since 3.0.0 + * @param \Vendidero\Germanized\Shipments\Packaging $packaging Packaging object. + * @param bool $force_delete Unused param. + */ + public function delete( &$packaging, $force_delete = false ) { + global $wpdb; + + $wpdb->delete( $wpdb->gzd_packaging, array( 'packaging_id' => $packaging->get_id() ), array( '%d' ) ); + $wpdb->delete( $wpdb->gzd_packagingmeta, array( 'gzd_packaging_id' => $packaging->get_id() ), array( '%d' ) ); + + $this->clear_caches( $packaging ); + + /** + * Action that indicates that a Packaging has been deleted from the DB. + * + * @param integer $packaging_id The packaging id. + * @param \Vendidero\Germanized\Shipments\Packaging $packaging The packaging instance. + * + * @since 3.3.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_packaging_deleted', $packaging->get_id(), $packaging ); + } + + /** + * Read a Packaging from the database. + * + * @since 3.3.0 + * + * @param \Vendidero\Germanized\Shipments\Packaging $packaging Packaging object. + * + * @throws Exception Throw exception if invalid packaging. + */ + public function read( &$packaging ) { + global $wpdb; + + $data = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$wpdb->gzd_packaging} WHERE packaging_id = %d LIMIT 1", + $packaging->get_id() + ) + ); + + if ( $data ) { + $packaging->set_props( + array( + 'type' => $data->packaging_type, + 'description' => $data->packaging_description, + 'weight' => $data->packaging_weight, + 'max_content_weight' => $data->packaging_max_content_weight, + 'length' => $data->packaging_length, + 'width' => $data->packaging_width, + 'height' => $data->packaging_height, + 'inner_length' => $data->packaging_inner_length, + 'inner_width' => $data->packaging_inner_width, + 'inner_height' => $data->packaging_inner_height, + 'order' => $data->packaging_order, + 'date_created' => Package::is_valid_mysql_date( $data->packaging_date_created_gmt ) ? wc_string_to_timestamp( $data->packaging_date_created_gmt ) : null, + ) + ); + + $this->read_packaging_data( $packaging ); + + $packaging->read_meta_data(); + $packaging->set_object_read( true ); + + /** + * Action that indicates that a Packaging has been loaded from DB. + * + * @param \Vendidero\Germanized\Shipments\Packaging $packaging The Packaging object. + * + * @since 3.3.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_packaging_loaded', $packaging ); + } else { + throw new Exception( _x( 'Invalid packaging.', 'shipments', 'woocommerce-germanized' ) ); + } + } + + /** + * Clear any caches. + * + * @param \Vendidero\Germanized\Shipments\Packaging $packaging Packaging object. + * @since 3.0.0 + */ + protected function clear_caches( &$packaging ) { + wp_cache_delete( $packaging->get_id(), $this->meta_type . '_meta' ); + wp_cache_delete( 'packaging-list', 'packaging' ); + + $this->all_packaging = null; + $this->packaging_lookup = null; + } + + /* + |-------------------------------------------------------------------------- + | Additional Methods + |-------------------------------------------------------------------------- + */ + + /** + * Get the packaging type based on ID. + * + * @param int $packaging_id Packaging id. + * @return string + */ + public function get_packaging_type( $packing_id ) { + global $wpdb; + + $type = $wpdb->get_col( + $wpdb->prepare( + "SELECT packaging_type FROM {$wpdb->gzd_packaging} WHERE packaging_id = %d LIMIT 1", + $packing_id + ) + ); + + return ! empty( $type ) ? $type[0] : false; + } + + /** + * Read extra data associated with the Packaging. + * + * @param \Vendidero\Germanized\Shipments\Packaging $packaging Packaging object. + * @since 3.0.0 + */ + protected function read_packaging_data( &$packaging ) { + $props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + $props[ substr( $meta_key, 1 ) ] = get_metadata( 'gzd_packaging', $packaging->get_id(), $meta_key, true ); + } + + $packaging->set_props( $props ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Packaging $packaging + */ + protected function save_packaging_data( &$packaging ) { + $updated_props = array(); + $meta_key_to_props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + $prop_name = substr( $meta_key, 1 ); + + if ( in_array( $prop_name, $this->core_props, true ) ) { + continue; + } + + $meta_key_to_props[ $meta_key ] = $prop_name; + } + + $props_to_update = $this->get_props_to_update( $packaging, $meta_key_to_props, $this->meta_type ); + + foreach ( $props_to_update as $meta_key => $prop ) { + if ( ! is_callable( array( $packaging, "get_$prop" ) ) ) { + continue; + } + + $value = $packaging->{"get_$prop"}( 'edit' ); + $value = is_string( $value ) ? wp_slash( $value ) : $value; + + $updated = $this->update_or_delete_meta( $packaging, $meta_key, $value ); + + if ( $updated ) { + $updated_props[] = $prop; + } + } + + /** + * Action that fires after updating a Packaging's properties. + * + * @param \Vendidero\Germanized\Shipments\Packaging $packaging The Packaging object. + * @param array $changed_props The updated properties. + * + * @since 3.3.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_packaging_object_updated_props', $packaging, $updated_props ); + } + + /** + * Update meta data in, or delete it from, the database. + * + * Avoids storing meta when it's either an empty string or empty array. + * Other empty values such as numeric 0 and null should still be stored. + * Data-stores can force meta to exist using `must_exist_meta_keys`. + * + * Note: WordPress `get_metadata` function returns an empty string when meta data does not exist. + * + * @param WC_Data $object The WP_Data object (WC_Coupon for coupons, etc). + * @param string $meta_key Meta key to update. + * @param mixed $meta_value Value to save. + * + * @since 3.6.0 Added to prevent empty meta being stored unless required. + * + * @return bool True if updated/deleted. + */ + protected function update_or_delete_meta( $object, $meta_key, $meta_value ) { + if ( in_array( $meta_value, array( array(), '' ), true ) && ! in_array( $meta_key, $this->must_exist_meta_keys, true ) ) { + $updated = delete_metadata( $this->meta_type, $object->get_id(), $meta_key ); + } else { + $updated = update_metadata( $this->meta_type, $object->get_id(), $meta_key, $meta_value ); + } + + return (bool) $updated; + } + + /** + * Get valid WP_Query args from a WC_Order_Query's query variables. + * + * @since 3.0.6 + * @param array $query_vars query vars from a WC_Order_Query. + * @return array + */ + protected function get_wp_query_args( $query_vars ) { + global $wpdb; + + $wp_query_args = parent::get_wp_query_args( $query_vars ); + + // Force type to be existent + if ( isset( $query_vars['type'] ) ) { + $wp_query_args['type'] = $query_vars['type']; + } + + 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(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + } + + // Allow Woo to treat these props as date query compatible + $date_queries = array( + 'date_created', + ); + + foreach ( $date_queries as $db_key ) { + if ( isset( $query_vars[ $db_key ] ) && '' !== $query_vars[ $db_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 ); + $meta_query_index = array_search( $db_key, $existing_queries, true ); + + if ( false !== $meta_query_index ) { + unset( $wp_query_args['meta_query'][ $meta_query_index ] ); + } + + $date_query_args = $this->parse_date_for_wp_query( $query_vars[ $db_key ], 'post_date', array() ); + + /** + * Replace date query columns after Woo parsed dates. + * Include table name because otherwise WP_Date_Query won't accept our custom column. + */ + if ( isset( $date_query_args['date_query'] ) && ! empty( $date_query_args['date_query'] ) ) { + $date_query = $date_query_args['date_query'][0]; + + if ( 'post_date' === $date_query['column'] ) { + $date_query['column'] = $wpdb->gzd_shipments . '.shipment_' . $db_key; + } + + $wp_query_args['date_query'][] = $date_query; + } + } + } + + if ( ! isset( $query_vars['paginate'] ) || ! $query_vars['paginate'] ) { + $wp_query_args['no_found_rows'] = true; + } + + /** + * Filter to adjust Packaging query arguments after parsing. + * + * @param array $wp_query_args Array containing parsed query arguments. + * @param array $query_vars The original query arguments. + * @param Packaging $data_store The packaging data store object. + * + * @since 3.3.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_packaging_data_store_get_shipments_query', $wp_query_args, $query_vars, $this ); + } + + /** + * @return \Vendidero\Germanized\Shipments\Packaging[] + */ + protected function get_all_packaging() { + global $wpdb; + + if ( is_null( $this->all_packaging ) ) { + $query = " + SELECT packaging_id FROM {$wpdb->gzd_packaging} + ORDER BY packaging_order ASC + "; + + // Get from cache if available. + $results = wp_cache_get( 'packaging-list', 'packaging' ); + + if ( false === $results ) { + $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + wp_cache_set( 'packaging-list', $results, 'packaging' ); + } + + $all_packaging = array(); + $packaging_lookup = array(); + + foreach ( $results as $key => $packaging ) { + if ( $the_packaging = wc_gzd_get_packaging( $packaging ) ) { + $all_packaging[ $key ] = $the_packaging; + $packaging_lookup[ $the_packaging->get_id() ] = $key; + } + } + + $this->all_packaging = $all_packaging; + $this->packaging_lookup = $packaging_lookup; + } + + return $this->all_packaging; + } + + /** + * @param $args + * + * @return \Vendidero\Germanized\Shipments\Packaging[] + */ + public function get_packaging_list( $args = array() ) { + $the_list = $this->get_all_packaging(); + $all_types = array_keys( wc_gzd_get_packaging_types() ); + $args = wp_parse_args( + $args, + array( + 'type' => $all_types, + 'shipping_provider' => '', + ) + ); + + if ( ! is_array( $args['type'] ) ) { + $args['type'] = array_filter( array( $args['type'] ) ); + } + + $types = array_filter( wc_clean( $args['type'] ) ); + $types = empty( $types ) ? $all_types : $types; + + foreach ( $the_list as $key => $packaging ) { + if ( ! in_array( $packaging->get_type(), $types, true ) ) { + unset( $the_list[ $key ] ); + continue; + } + + if ( ! empty( $args['shipping_provider'] ) && ! $packaging->supports_shipping_provider( $args['shipping_provider'] ) ) { + unset( $the_list[ $key ] ); + } + } + + return array_values( $the_list ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + * @param \Vendidero\Germanized\Shipments\Packaging|PackagingBox $packaging + * + * @return bool + */ + public function shipment_fits_into_packaging_naive( $shipment, $packaging ) { + if ( is_a( $packaging, '\Vendidero\Germanized\Shipments\Packing\PackagingBox' ) ) { + $packaging = $packaging->getReference(); + } + + if ( ! $packaging ) { + return false; + } + + if ( ! $packaging->supports_shipping_provider( $shipment->get_shipping_provider() ) ) { + return false; + } + + $weight = (float) wc_format_decimal( empty( $shipment->get_content_weight() ) ? 0 : wc_get_weight( $shipment->get_content_weight(), wc_gzd_get_packaging_weight_unit(), $shipment->get_weight_unit() ), 3 ); + $volume = (float) wc_format_decimal( empty( $shipment->get_content_volume() ) ? 0 : wc_gzd_get_volume_dimension( $shipment->get_content_volume(), wc_gzd_get_packaging_dimension_unit(), $shipment->get_dimension_unit() ), 1 ); + $fits = true; + + if ( $packaging->has_inner_dimensions() ) { + $packaging_volume = (float) $packaging->get_inner_length() * (float) $packaging->get_inner_width() * (float) $packaging->get_inner_height(); + } else { + $packaging_volume = (float) $packaging->get_length() * (float) $packaging->get_width() * (float) $packaging->get_height(); + } + + /** + * The packaging does not fit in case: + * - total weight is greater than it's maximum capability + * - the total volume is greater than the packaging volume + */ + if ( ! empty( $packaging->get_max_content_weight() ) && $weight > $packaging->get_max_content_weight() ) { + $fits = false; + } elseif ( $volume > $packaging_volume ) { + $fits = false; + } + + return $fits; + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + * + * @return \Vendidero\Germanized\Shipments\Packaging[] + */ + public function find_available_packaging_for_shipment( $shipment ) { + $packaging_available = array(); + $items = $shipment->get_items_to_pack(); + $results = false; + + // Get from cache if available. + if ( $shipment->get_id() > 0 ) { + $results = wp_cache_get( 'available-packaging-' . $shipment->get_id(), 'shipments' ); + } + + if ( false === $results && count( $items ) > 0 ) { + $available_packaging_ids = array(); + + if ( Package::is_packing_supported() ) { + $packaging_list = $this->get_packaging_list( array( 'shipping_provider' => $shipment->get_shipping_provider() ) ); + + foreach ( $packaging_list as $packaging ) { + /** + * Make sure to only check naively fitting packaging to improve performance + */ + if ( ! $this->shipment_fits_into_packaging_naive( $shipment, $packaging ) ) { + continue; + } + + $box = new PackagingBox( $packaging ); + $item_count = count( $items ); + + $packer = new VolumePacker( $box, $items ); + $packed = $packer->pack(); + + if ( count( $packed->getItems() ) === $item_count ) { + $packaging_available[] = $packaging; + $available_packaging_ids[] = $packaging->get_id(); + } + } + } else { + global $wpdb; + + $weight = wc_format_decimal( empty( $shipment->get_weight() ) ? 0 : wc_get_weight( $shipment->get_weight(), wc_gzd_get_packaging_weight_unit(), $shipment->get_weight_unit() ), 3 ); + $length = wc_format_decimal( empty( $shipment->get_length() ) ? 0 : wc_get_dimension( $shipment->get_length(), wc_gzd_get_packaging_dimension_unit(), $shipment->get_dimension_unit() ), 1 ); + $width = wc_format_decimal( empty( $shipment->get_width() ) ? 0 : wc_get_dimension( $shipment->get_width(), wc_gzd_get_packaging_dimension_unit(), $shipment->get_dimension_unit() ), 1 ); + $height = wc_format_decimal( empty( $shipment->get_height() ) ? 0 : wc_get_dimension( $shipment->get_height(), wc_gzd_get_packaging_dimension_unit(), $shipment->get_dimension_unit() ), 1 ); + + $types = array_keys( wc_gzd_get_packaging_types() ); + $threshold = apply_filters( 'woocommerce_gzd_shipment_packaging_match_threshold', 0, $shipment ); + + $query_sql = "SELECT + packaging_id, + CASE + WHEN packaging_inner_length > 0 + THEN (packaging_inner_length - %f) + ELSE (packaging_length - %f) + END as length_diff, + CASE + WHEN packaging_inner_width > 0 + THEN (packaging_inner_width - %f) + ELSE (packaging_width - %f) + END as width_diff, + CASE + WHEN packaging_inner_height > 0 + THEN (packaging_inner_height - %f) + ELSE (packaging_height - %f) + END as height_diff + FROM {$wpdb->gzd_packaging} + WHERE ( packaging_max_content_weight = 0 OR packaging_max_content_weight >= %f ) AND packaging_type IN ( '" . implode( "','", $types ) . "' ) + HAVING length_diff >= %f AND width_diff >= %f AND height_diff >= %f + ORDER BY (length_diff+width_diff+height_diff) ASC, packaging_weight ASC, packaging_order ASC + "; + + $query = $wpdb->prepare( $query_sql, $length, $length, $width, $width, $height, $height, $weight, $threshold, $threshold, $threshold ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + if ( $results ) { + foreach ( $results as $result ) { + $packaging = wc_gzd_get_packaging( $result->packaging_id ); + + if ( ! $packaging->supports_shipping_provider( $shipment->get_shipping_provider() ) ) { + continue; + } + + $available_packaging_ids[] = $packaging->get_id(); + $packaging_available[] = $packaging; + } + } + } + + wp_cache_set( 'available-packaging-' . $shipment->get_id(), $available_packaging_ids, 'shipments' ); + } elseif ( count( $items ) <= 0 ) { + $packaging_available = $this->get_packaging_list(); + } else { + foreach ( (array) $results as $packaging_id ) { + $packaging = wc_gzd_get_packaging( $packaging_id ); + + if ( ! $packaging->supports_shipping_provider( $shipment->get_shipping_provider() ) ) { + continue; + } + + $packaging_available[] = $packaging; + } + } + + $packaging_list = apply_filters( 'woocommerce_gzd_find_available_packaging_for_shipment', $packaging_available, $shipment ); + + return $this->sort_packaging_list( $packaging_list ); + } + + protected function sort_packaging_list( $packaging ) { + usort( $packaging, array( $this, 'sort_packaging_list_callback' ) ); + + return $packaging; + } + + /** + * @param \Vendidero\Germanized\Shipments\Packaging $packaging_a + * @param \Vendidero\Germanized\Shipments\Packaging $packaging_b + * + * @return int + */ + protected function sort_packaging_list_callback( $packaging_a, $packaging_b ) { + if ( $packaging_a->get_volume() === $packaging_b->get_volume() ) { + return 0; + } + + return ( $packaging_a->get_volume() > $packaging_b->get_volume() ) ? 1 : -1; + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + * @param string|array $types + * + * @return \Vendidero\Germanized\Shipments\Packaging|false + */ + public function find_best_match_for_shipment( $shipment ) { + $results = $this->find_available_packaging_for_shipment( $shipment ); + $packaging = false; + + if ( ! empty( $results ) ) { + $packaging = $results[0]; + + /** + * In case more than one packaging is available - choose the default packaging in case it is available. + */ + if ( count( $results ) > 1 ) { + $default_packaging_id = Package::get_setting( 'default_packaging' ); + + if ( ! empty( $default_packaging_id ) ) { + $default_packaging_id = absint( $default_packaging_id ); + + foreach ( $results as $result ) { + if ( $result->get_id() === $default_packaging_id ) { + $packaging = $result; + break; + } + } + } + } + } + + return apply_filters( 'woocommerce_gzd_find_best_matching_packaging_for_shipment', $packaging, $shipment ); + } + + /** + * Table structure is slightly different between meta types, this function will return what we need to know. + * + * @since 3.0.0 + * @return array Array elements: table, object_id_field, meta_id_field + */ + protected function get_db_info() { + global $wpdb; + + $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. + $table = $wpdb->gzd_packagingmeta; + $object_id_field = $this->meta_type . '_id'; + + if ( ! empty( $this->object_id_field_for_meta ) ) { + $object_id_field = $this->object_id_field_for_meta; + } + + return array( + 'table' => $table, + 'object_id_field' => $object_id_field, + 'meta_id_field' => $meta_id_field, + ); + } + + public function get_query_args( $query_vars ) { + return $this->get_wp_query_args( $query_vars ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/DataStores/Shipment.php b/packages/woocommerce-germanized-shipments/src/DataStores/Shipment.php new file mode 100644 index 000000000..cf3cfa38c --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/DataStores/Shipment.php @@ -0,0 +1,712 @@ +set_date_created( time() ); + $shipment->set_weight_unit( get_option( 'woocommerce_weight_unit', 'kg' ) ); + $shipment->set_dimension_unit( get_option( 'woocommerce_dimension_unit', 'cm' ) ); + + $data = array( + 'shipment_country' => $shipment->get_country(), + 'shipment_order_id' => is_callable( array( $shipment, 'get_order_id' ) ) ? $shipment->get_order_id() : 0, + 'shipment_parent_id' => is_callable( array( $shipment, 'get_parent_id' ) ) ? $shipment->get_parent_id() : 0, + 'shipment_tracking_id' => $shipment->get_tracking_id(), + 'shipment_status' => $this->get_status( $shipment ), + 'shipment_search_index' => $this->get_search_index( $shipment ), + 'shipment_packaging_id' => $shipment->get_packaging_id(), + 'shipment_type' => $shipment->get_type(), + 'shipment_shipping_provider' => $shipment->get_shipping_provider(), + 'shipment_shipping_method' => $shipment->get_shipping_method(), + 'shipment_date_created' => gmdate( 'Y-m-d H:i:s', $shipment->get_date_created( 'edit' )->getOffsetTimestamp() ), + 'shipment_date_created_gmt' => gmdate( 'Y-m-d H:i:s', $shipment->get_date_created( 'edit' )->getTimestamp() ), + 'shipment_version' => Package::get_version(), + ); + + if ( $shipment->get_date_sent() ) { + $data['shipment_date_sent'] = gmdate( 'Y-m-d H:i:s', $shipment->get_date_sent( 'edit' )->getOffsetTimestamp() ); + $data['shipment_date_sent_gmt'] = gmdate( 'Y-m-d H:i:s', $shipment->get_date_sent( 'edit' )->getTimestamp() ); + } + + if ( is_callable( array( $shipment, 'get_est_delivery_date' ) ) && $shipment->get_est_delivery_date() ) { + $data['shipment_est_delivery_date'] = gmdate( 'Y-m-d H:i:s', $shipment->get_est_delivery_date( 'edit' )->getOffsetTimestamp() ); + $data['shipment_est_delivery_date_gmt'] = gmdate( 'Y-m-d H:i:s', $shipment->get_est_delivery_date( 'edit' )->getTimestamp() ); + } + + $wpdb->insert( + $wpdb->gzd_shipments, + $data + ); + + $shipment_id = $wpdb->insert_id; + + if ( $shipment_id ) { + $shipment->set_id( $shipment_id ); + + $this->save_shipment_data( $shipment ); + + $shipment->save_meta_data(); + $shipment->apply_changes(); + + $this->clear_caches( $shipment ); + + $hook_postfix = $this->get_hook_postfix( $shipment ); + + /** + * Action that indicates that a new Shipment has been created in the DB. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * shipment type in case it is not a simple shipment. + * + * @param integer $shipment_id The shipment id. + * @param Shipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "woocommerce_gzd_new_{$hook_postfix}shipment", $shipment_id, $shipment ); + } + } + + /** + * Get the status to save to the object. + * + * @since 3.6.0 + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + * @return string + */ + protected function get_status( $shipment ) { + $shipment_status = $shipment->get_status( 'edit' ); + + if ( ! $shipment_status ) { + /** This filter is documented in src/Shipment.php */ + $shipment_status = apply_filters( 'woocommerce_gzd_get_shipment_default_status', 'gzd-draft' ); + } + + $valid_statuses = array_keys( wc_gzd_get_shipment_statuses() ); + + // Add a gzd- prefix to the status. + if ( in_array( 'gzd-' . $shipment_status, $valid_statuses, true ) ) { + $shipment_status = 'gzd-' . $shipment_status; + } + + return $shipment_status; + } + + /** + * Method to update a shipment in the database. + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + */ + public function update( &$shipment ) { + global $wpdb; + + $updated_props = array(); + $core_props = $this->core_props; + $changed_props = array_keys( $shipment->get_changes() ); + $shipment_data = array(); + + if ( '' === $shipment->get_weight_unit( 'edit' ) ) { + $shipment->set_weight_unit( get_option( 'woocommerce_weight_unit', 'kg' ) ); + } + + if ( '' === $shipment->get_dimension_unit( 'edit' ) ) { + $shipment->set_dimension_unit( get_option( 'woocommerce_dimension_unit', 'cm' ) ); + } + + // Make sure country in core props is updated as soon as the address changes + if ( in_array( 'address', $changed_props, true ) ) { + $changed_props[] = 'country'; + + // Update search index + $shipment_data['shipment_search_index'] = $this->get_search_index( $shipment ); + } + + // Shipping provider has changed - lets remove existing label + if ( in_array( 'shipping_provider', $changed_props, true ) ) { + + if ( $shipment->supports_label() && $shipment->has_label() ) { + $shipment->get_label()->delete(); + } + } + + foreach ( $changed_props as $prop ) { + if ( ! in_array( $prop, $core_props, true ) ) { + continue; + } + + switch ( $prop ) { + case 'status': + $shipment_data[ 'shipment_' . $prop ] = $this->get_status( $shipment ); + break; + case 'date_created': + case 'date_sent': + case 'est_delivery_date': + if ( is_callable( array( $shipment, 'get_' . $prop ) ) ) { + $shipment_data[ 'shipment_' . $prop ] = $shipment->{'get_' . $prop}( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $shipment->{'get_' . $prop}( 'edit' )->getOffsetTimestamp() ) : null; + $shipment_data[ 'shipment_' . $prop . '_gmt' ] = $shipment->{'get_' . $prop}( 'edit' ) ? gmdate( 'Y-m-d H:i:s', $shipment->{'get_' . $prop}( 'edit' )->getTimestamp() ) : null; + } + break; + default: + if ( is_callable( array( $shipment, 'get_' . $prop ) ) ) { + $shipment_data[ 'shipment_' . $prop ] = $shipment->{'get_' . $prop}( 'edit' ); + } + break; + } + } + + if ( ! empty( $shipment_data ) ) { + $shipment_data['shipment_search_index'] = $this->get_search_index( $shipment ); + + $wpdb->update( + $wpdb->gzd_shipments, + $shipment_data, + array( 'shipment_id' => $shipment->get_id() ) + ); + } + + $this->save_shipment_data( $shipment ); + + $shipment->save_meta_data(); + $shipment->apply_changes(); + + $this->clear_caches( $shipment ); + + $hook_postfix = $this->get_hook_postfix( $shipment ); + + /** + * Action that indicates that a Shipment has been updated in the DB. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * shipment type in case it is not a simple shipment. + * + * @param integer $shipment_id The shipment id. + * @param Shipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "woocommerce_gzd_{$hook_postfix}shipment_updated", $shipment->get_id(), $shipment ); + } + + /** + * Remove a shipment from the database. + * + * @since 3.0.0 + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + * @param bool $force_delete Unused param. + */ + public function delete( &$shipment, $force_delete = false ) { + global $wpdb; + + $wpdb->delete( $wpdb->gzd_shipments, array( 'shipment_id' => $shipment->get_id() ), array( '%d' ) ); + $wpdb->delete( $wpdb->gzd_shipmentmeta, array( 'gzd_shipment_id' => $shipment->get_id() ), array( '%d' ) ); + + $this->delete_items( $shipment ); + $this->clear_caches( $shipment ); + + $hook_postfix = $this->get_hook_postfix( $shipment ); + + /** + * Action that indicates that a Shipment has been deleted from the DB. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * shipment type in case it is not a simple shipment. + * + * @param integer $shipment_id The shipment id. + * @param \Vendidero\Germanized\Shipments\Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "woocommerce_gzd_{$hook_postfix}shipment_deleted", $shipment->get_id(), $shipment ); + } + + /** + * Read a shipment from the database. + * + * @since 3.0.0 + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + * + * @throws Exception Throw exception if invalid shipment. + */ + public function read( &$shipment ) { + global $wpdb; + + $data = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$wpdb->gzd_shipments} WHERE shipment_id = %d LIMIT 1", + $shipment->get_id() + ) + ); + + if ( $data ) { + $shipment->set_props( + array( + 'order_id' => $data->shipment_order_id, + 'parent_id' => $data->shipment_parent_id, + 'country' => $data->shipment_country, + 'tracking_id' => $data->shipment_tracking_id, + 'shipping_provider' => $data->shipment_shipping_provider, + 'shipping_method' => $data->shipment_shipping_method, + 'packaging_id' => $data->shipment_packaging_id, + 'date_created' => Package::is_valid_mysql_date( $data->shipment_date_created_gmt ) ? wc_string_to_timestamp( $data->shipment_date_created_gmt ) : null, + 'date_sent' => Package::is_valid_mysql_date( $data->shipment_date_sent_gmt ) ? wc_string_to_timestamp( $data->shipment_date_sent_gmt ) : null, + 'est_delivery_date' => Package::is_valid_mysql_date( $data->shipment_est_delivery_date_gmt ) ? wc_string_to_timestamp( $data->shipment_est_delivery_date_gmt ) : null, + 'status' => $data->shipment_status, + 'version' => $data->shipment_version, + ) + ); + + $this->read_shipment_data( $shipment ); + + $shipment->read_meta_data(); + $shipment->set_object_read( true ); + + $hook_postfix = $this->get_hook_postfix( $shipment ); + + /** + * Action that indicates that a Shipment has been loaded from DB. + * + * The dynamic portion of this hook, `$hook_postfix` refers to the + * shipment type in case it is not a simple shipment. + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "woocommerce_gzd_{$hook_postfix}shipment_loaded", $shipment ); + } else { + throw new Exception( _x( 'Invalid shipment.', 'shipments', 'woocommerce-germanized' ) ); + } + } + + /** + * Clear any caches. + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + * @since 3.0.0 + */ + protected function clear_caches( &$shipment ) { + wp_cache_delete( 'shipment-items-' . $shipment->get_id(), 'shipments' ); + wp_cache_delete( $shipment->get_id(), $this->meta_type . '_meta' ); + wp_cache_delete( 'available-packaging-' . $shipment->get_id(), 'shipments' ); + } + + /* + |-------------------------------------------------------------------------- + | Additional Methods + |-------------------------------------------------------------------------- + */ + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + protected function get_search_index( $shipment ) { + $index = array(); + + if ( is_a( $shipment, '\Vendidero\Germanized\Shipments\ReturnShipment' ) ) { + $index = array_merge( $index, $shipment->get_sender_address() ); + } else { + $index = array_merge( $index, $shipment->get_address() ); + } + + return implode( ' ', $index ); + } + + protected function get_hook_postfix( $shipment ) { + if ( 'simple' !== $shipment->get_type() ) { + return $shipment->get_type() . '_'; + } + + return ''; + } + + /** + * Get the label type based on label ID. + * + * @param int $shipment_id Shipment id. + * @return string + */ + public function get_shipment_type( $shipment_id ) { + global $wpdb; + + $type = $wpdb->get_col( + $wpdb->prepare( + "SELECT shipment_type FROM {$wpdb->gzd_shipments} WHERE shipment_id = %d LIMIT 1", + $shipment_id + ) + ); + + return ! empty( $type ) ? $type[0] : false; + } + + /** + * Read extra data associated with the shipment. + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + * @since 3.0.0 + */ + protected function read_shipment_data( &$shipment ) { + $props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + $props[ substr( $meta_key, 1 ) ] = get_metadata( 'gzd_shipment', $shipment->get_id(), $meta_key, true ); + } + + $shipment->set_props( $props ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + protected function save_shipment_data( &$shipment ) { + $updated_props = array(); + $meta_key_to_props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + $prop_name = substr( $meta_key, 1 ); + + if ( in_array( $prop_name, $this->core_props, true ) ) { + continue; + } + + $meta_key_to_props[ $meta_key ] = $prop_name; + } + + $props_to_update = $this->get_props_to_update( $shipment, $meta_key_to_props, 'gzd_shipment' ); + + foreach ( $props_to_update as $meta_key => $prop ) { + if ( ! is_callable( array( $shipment, "get_$prop" ) ) ) { + continue; + } + + $value = $shipment->{"get_$prop"}( 'edit' ); + $value = is_string( $value ) ? wp_slash( $value ) : $value; + + switch ( $prop ) { + case 'is_customer_requested': + $value = wc_bool_to_string( $value ); + break; + } + + // Force updating props that are dependent on inner content data (weight, dimensions) + if ( in_array( $prop, array( 'weight', 'width', 'length', 'height' ), true ) && ! $shipment->is_editable() ) { + // Get weight in view context to maybe allow calculating inner content props. + $value = $shipment->{"get_$prop"}( 'view' ); + $updated = update_metadata( 'gzd_shipment', $shipment->get_id(), $meta_key, $value ); + } else { + $updated = $this->update_or_delete_meta( $shipment, $meta_key, $value ); + } + + if ( $updated ) { + $updated_props[] = $prop; + } + } + + /** + * Action that fires after updating a Shipment's properties. + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment The shipment object. + * @param array $changed_props The updated properties. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_object_updated_props', $shipment, $updated_props ); + } + + /** + * Update meta data in, or delete it from, the database. + * + * Avoids storing meta when it's either an empty string or empty array. + * Other empty values such as numeric 0 and null should still be stored. + * Data-stores can force meta to exist using `must_exist_meta_keys`. + * + * Note: WordPress `get_metadata` function returns an empty string when meta data does not exist. + * + * @param WC_Data $object The WP_Data object (WC_Coupon for coupons, etc). + * @param string $meta_key Meta key to update. + * @param mixed $meta_value Value to save. + * + * @since 3.6.0 Added to prevent empty meta being stored unless required. + * + * @return bool True if updated/deleted. + */ + protected function update_or_delete_meta( $object, $meta_key, $meta_value ) { + if ( in_array( $meta_value, array( array(), '' ), true ) && ! in_array( $meta_key, $this->must_exist_meta_keys, true ) ) { + $updated = delete_metadata( 'gzd_shipment', $object->get_id(), $meta_key ); + } else { + $updated = update_metadata( 'gzd_shipment', $object->get_id(), $meta_key, $meta_value ); + } + + return (bool) $updated; + } + + /** + * Read items from the database for this shipment. + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + * + * @return array + */ + public function read_items( $shipment ) { + global $wpdb; + + // Get from cache if available. + $items = 0 < $shipment->get_id() ? wp_cache_get( 'shipment-items-' . $shipment->get_id(), 'shipments' ) : false; + + if ( false === $items ) { + + $items = $wpdb->get_results( + $wpdb->prepare( "SELECT * FROM {$wpdb->gzd_shipment_items} WHERE shipment_id = %d ORDER BY shipment_item_id;", $shipment->get_id() ) + ); + + foreach ( $items as $item ) { + wp_cache_set( 'item-' . $item->shipment_item_id, $item, 'shipment-items' ); + } + + if ( 0 < $shipment->get_id() ) { + wp_cache_set( 'shipment-items-' . $shipment->get_id(), $items, 'shipments' ); + } + } + + if ( ! empty( $items ) ) { + + $shipment_type = $shipment->get_type(); + + $items = array_map( + function( $item_id ) use ( $shipment_type ) { + return wc_gzd_get_shipment_item( $item_id, $shipment_type ); + }, + array_combine( wp_list_pluck( $items, 'shipment_item_id' ), $items ) + ); + } else { + $items = array(); + } + + return $items; + } + + /** + * Remove all items from the shipment. + * + * @param \Vendidero\Germanized\Shipments\Shipment $shipment Shipment object. + */ + public function delete_items( $shipment ) { + global $wpdb; + + $wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->gzd_shipment_itemmeta} itemmeta INNER JOIN {$wpdb->gzd_shipment_items} items WHERE itemmeta.gzd_shipment_item_id = items.shipment_item_id and items.shipment_id = %d", $shipment->get_id() ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->gzd_shipment_items} WHERE shipment_id = %d", $shipment->get_id() ) ); + + $this->clear_caches( $shipment ); + } + + /** + * Get valid WP_Query args from a WC_Order_Query's query variables. + * + * @since 3.0.6 + * @param array $query_vars query vars from a WC_Order_Query. + * @return array + */ + protected function get_wp_query_args( $query_vars ) { + global $wpdb; + + // Add the 'wc-' prefix to status if needed. + if ( ! empty( $query_vars['status'] ) ) { + if ( is_array( $query_vars['status'] ) ) { + foreach ( $query_vars['status'] as &$status ) { + $status = wc_gzd_is_shipment_status( 'gzd-' . $status ) ? 'gzd-' . $status : $status; + } + } else { + $query_vars['status'] = wc_gzd_is_shipment_status( 'gzd-' . $query_vars['status'] ) ? 'gzd-' . $query_vars['status'] : $query_vars['status']; + } + } + + $wp_query_args = parent::get_wp_query_args( $query_vars ); + + // Force type to be existent + if ( isset( $query_vars['type'] ) ) { + $wp_query_args['type'] = $query_vars['type']; + } + + 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(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query + } + + // Allow Woo to treat these props as date query compatible + $date_queries = array( + 'date_created', + 'date_sent', + 'est_delivery_date', + ); + + foreach ( $date_queries as $db_key ) { + if ( isset( $query_vars[ $db_key ] ) && '' !== $query_vars[ $db_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 ); + $meta_query_index = array_search( $db_key, $existing_queries, true ); + + if ( false !== $meta_query_index ) { + unset( $wp_query_args['meta_query'][ $meta_query_index ] ); + } + + $date_query_args = $this->parse_date_for_wp_query( $query_vars[ $db_key ], 'post_date', array() ); + + /** + * Replace date query columns after Woo parsed dates. + * Include table name because otherwise WP_Date_Query won't accept our custom column. + */ + if ( isset( $date_query_args['date_query'] ) && ! empty( $date_query_args['date_query'] ) ) { + $date_query = $date_query_args['date_query'][0]; + + if ( 'post_date' === $date_query['column'] ) { + $date_query['column'] = $wpdb->gzd_shipments . '.shipment_' . $db_key; + } + + $wp_query_args['date_query'][] = $date_query; + } + } + } + + if ( ! isset( $query_vars['paginate'] ) || ! $query_vars['paginate'] ) { + $wp_query_args['no_found_rows'] = true; + } + + /** + * Filter to adjust Shipments query arguments after parsing. + * + * @param array $wp_query_args Array containing parsed query arguments. + * @param array $query_vars The original query arguments. + * @param Shipment $data_store The shipment data store object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipping_data_store_get_shipments_query', $wp_query_args, $query_vars, $this ); + } + + /** + * Table structure is slightly different between meta types, this function will return what we need to know. + * + * @since 3.0.0 + * @return array Array elements: table, object_id_field, meta_id_field + */ + protected function get_db_info() { + global $wpdb; + + $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. + $table = $wpdb->gzd_shipmentmeta; + $object_id_field = $this->meta_type . '_id'; + + if ( ! empty( $this->object_id_field_for_meta ) ) { + $object_id_field = $this->object_id_field_for_meta; + } + + return array( + 'table' => $table, + 'object_id_field' => $object_id_field, + 'meta_id_field' => $meta_id_field, + ); + } + + public function get_query_args( $query_vars ) { + return $this->get_wp_query_args( $query_vars ); + } + + public function get_shipment_count( $status, $type = '' ) { + global $wpdb; + + if ( empty( $type ) ) { + $query = $wpdb->prepare( "SELECT COUNT( * ) FROM {$wpdb->gzd_shipments} WHERE shipment_status = %s", $status ); + } else { + $query = $wpdb->prepare( "SELECT COUNT( * ) FROM {$wpdb->gzd_shipments} WHERE shipment_status = %s and shipment_type = %s", $status, $type ); + } + + return absint( $wpdb->get_var( $query ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } +} diff --git a/packages/woocommerce-germanized-shipments/src/DataStores/ShipmentItem.php b/packages/woocommerce-germanized-shipments/src/DataStores/ShipmentItem.php new file mode 100644 index 000000000..84dd9af1a --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/DataStores/ShipmentItem.php @@ -0,0 +1,355 @@ +insert( + $wpdb->gzd_shipment_items, + array( + 'shipment_id' => $item->get_shipment_id(), + 'shipment_item_quantity' => $item->get_quantity(), + 'shipment_item_order_item_id' => $item->get_order_item_id(), + 'shipment_item_product_id' => $item->get_product_id(), + 'shipment_item_parent_id' => $item->get_parent_id(), + 'shipment_item_name' => $item->get_name(), + ) + ); + + $item->set_id( $wpdb->insert_id ); + $this->save_item_data( $item ); + $item->save_meta_data(); + $item->apply_changes(); + $this->clear_cache( $item ); + + /** + * Action that indicates that a new ShipmentItem has been created in the DB. + * + * @param integer $shipment_item_id The shipment item id. + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item The shipment item object. + * @param integer $shipment_id The shipment id. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_new_shipment_item', $item->get_id(), $item, $item->get_shipment_id() ); + } + + /** + * Update a shipment item in the database. + * + * @since 3.0.0 + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item Shipment item object. + */ + public function update( &$item ) { + global $wpdb; + + $changes = $item->get_changes(); + + if ( array_intersect( $this->core_props, array_keys( $changes ) ) ) { + $wpdb->update( + $wpdb->gzd_shipment_items, + array( + 'shipment_id' => $item->get_shipment_id(), + 'shipment_item_order_item_id' => $item->get_order_item_id(), + 'shipment_item_quantity' => $item->get_quantity(), + 'shipment_item_product_id' => $item->get_product_id(), + 'shipment_item_parent_id' => $item->get_parent_id(), + 'shipment_item_name' => $item->get_name(), + ), + array( 'shipment_item_id' => $item->get_id() ) + ); + } + + $this->save_item_data( $item ); + $item->save_meta_data(); + $item->apply_changes(); + $this->clear_cache( $item ); + + /** + * Action that indicates that a ShipmentItem has been updated in the DB. + * + * @param integer $shipment_item_id The shipment item id. + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item The shipment item object. + * @param integer $shipment_id The shipment id. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_item_updated', $item->get_id(), $item, $item->get_shipment_id() ); + } + + /** + * Remove a shipment item from the database. + * + * @since 3.0.0 + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item Shipment item object. + * @param array $args Array of args to pass to the delete method. + */ + public function delete( &$item, $args = array() ) { + if ( $item->get_id() ) { + global $wpdb; + + /** + * Action that fires before deleting a ShipmentItem from the DB. + * + * @param integer $shipment_item_id The shipment item id. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_before_delete_shipment_item', $item->get_id() ); + + $wpdb->delete( $wpdb->gzd_shipment_items, array( 'shipment_item_id' => $item->get_id() ) ); + $wpdb->delete( $wpdb->gzd_shipment_itemmeta, array( 'gzd_shipment_item_id' => $item->get_id() ) ); + + /** + * Action that indicates that a ShipmentItem has been deleted from the DB. + * + * @param integer $shipment_item_id The shipment item id. + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item The shipment item object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_delete_shipment_item', $item->get_id(), $item ); + $this->clear_cache( $item ); + } + } + + /** + * Read a shipment item from the database. + * + * @since 3.0.0 + * + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item Shipment item object. + * + * @throws Exception If invalid shipment item. + */ + public function read( &$item ) { + global $wpdb; + + $item->set_defaults(); + + // Get from cache if available. + $data = wp_cache_get( 'item-' . $item->get_id(), 'shipment-items' ); + + if ( false === $data ) { + $data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->gzd_shipment_items} WHERE shipment_item_id = %d LIMIT 1;", $item->get_id() ) ); + wp_cache_set( 'item-' . $item->get_id(), $data, 'shipment-items' ); + } + + if ( ! $data ) { + throw new Exception( _x( 'Invalid shipment item.', 'shipments', 'woocommerce-germanized' ) ); + } + + $item->set_props( + array( + 'shipment_id' => $data->shipment_id, + 'order_item_id' => $data->shipment_item_order_item_id, + 'quantity' => $data->shipment_item_quantity, + 'product_id' => $data->shipment_item_product_id, + 'parent_id' => $data->shipment_item_parent_id, + 'name' => $data->shipment_item_name, + ) + ); + + $this->read_item_data( $item ); + $item->read_meta_data(); + $item->set_object_read( true ); + } + + /** + * Read extra data associated with the shipment item. + * + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item Shipment item object. + * @since 3.0.0 + */ + protected function read_item_data( &$item ) { + $props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + $props[ substr( $meta_key, 1 ) ] = get_metadata( $this->meta_type, $item->get_id(), $meta_key, true ); + } + + $item->set_props( $props ); + } + + /** + * Update meta data in, or delete it from, the database. + * + * Avoids storing meta when it's either an empty string or empty array. + * Other empty values such as numeric 0 and null should still be stored. + * Data-stores can force meta to exist using `must_exist_meta_keys`. + * + * Note: WordPress `get_metadata` function returns an empty string when meta data does not exist. + * + * @param WC_Data $object The WP_Data object (WC_Coupon for coupons, etc). + * @param string $meta_key Meta key to update. + * @param mixed $meta_value Value to save. + * + * @since 3.6.0 Added to prevent empty meta being stored unless required. + * + * @return bool True if updated/deleted. + */ + protected function update_or_delete_meta( $object, $meta_key, $meta_value ) { + if ( in_array( $meta_value, array( array(), '' ), true ) && ! in_array( $meta_key, $this->must_exist_meta_keys, true ) ) { + $updated = delete_metadata( $this->meta_type, $object->get_id(), $meta_key ); + } else { + $updated = update_metadata( $this->meta_type, $object->get_id(), $meta_key, $meta_value ); + } + + return (bool) $updated; + } + + /** + * Saves an item's data to the database / item meta. + * Ran after both create and update, so $item->get_id() will be set. + * + * @since 3.0.0 + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item Shipment item object. + */ + public function save_item_data( &$item ) { + $updated_props = array(); + $meta_key_to_props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + + $prop_name = substr( $meta_key, 1 ); + + if ( in_array( $prop_name, $this->core_props, true ) ) { + continue; + } + + $meta_key_to_props[ $meta_key ] = $prop_name; + } + + $props_to_update = $this->get_props_to_update( $item, $meta_key_to_props, $this->meta_type ); + + foreach ( $props_to_update as $meta_key => $prop ) { + + $getter = "get_$prop"; + + if ( ! is_callable( array( $item, $getter ) ) ) { + continue; + } + + $value = $item->{"get_$prop"}( 'edit' ); + $value = is_string( $value ) ? wp_slash( $value ) : $value; + + $updated = $this->update_or_delete_meta( $item, $meta_key, $value ); + + if ( $updated ) { + $updated_props[] = $prop; + } + } + + /** + * Action that fires after updating a ShipmentItem's properties. + * + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item The shipment item object. + * @param array $changed_props The updated properties. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_item_object_updated_props', $item, $updated_props ); + } + + /** + * Clear meta cache. + * + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item Shipment item object. + */ + public function clear_cache( &$item ) { + wp_cache_delete( 'item-' . $item->get_id(), 'shipment-items' ); + wp_cache_delete( 'shipment-items-' . $item->get_shipment_id(), 'shipments' ); + wp_cache_delete( $item->get_id(), $this->meta_type . '_meta' ); + } + + /** + * Table structure is slightly different between meta types, this function will return what we need to know. + * + * @since 3.0.0 + * @return array Array elements: table, object_id_field, meta_id_field + */ + protected function get_db_info() { + global $wpdb; + + $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. + $table = $wpdb->gzd_shipment_itemmeta; + $object_id_field = $this->meta_type . '_id'; + + if ( ! empty( $this->object_id_field_for_meta ) ) { + $object_id_field = $this->object_id_field_for_meta; + } + + return array( + 'table' => $table, + 'object_id_field' => $object_id_field, + 'meta_id_field' => $meta_id_field, + ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/DataStores/ShippingProvider.php b/packages/woocommerce-germanized-shipments/src/DataStores/ShippingProvider.php new file mode 100644 index 000000000..73dc56928 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/DataStores/ShippingProvider.php @@ -0,0 +1,492 @@ +set_name( $this->get_unqiue_name( $provider ) ); + + if ( 0 === $provider->get_order( 'edit' ) ) { + $max_order = 1; + $max_order_col = $wpdb->get_col( "SELECT MAX(shipping_provider_order) FROM {$wpdb->gzd_shipping_provider}" ); + + if ( ! empty( $max_order_col ) ) { + $max_order = absint( $max_order_col[0] ) + 1; + } + + $provider->set_order( $max_order ); + } + + $data = array( + 'shipping_provider_activated' => $provider->is_activated() ? 1 : 0, + 'shipping_provider_name' => $provider->get_name( 'edit' ), + 'shipping_provider_title' => $provider->get_title( 'edit' ), + 'shipping_provider_order' => $provider->get_order( 'edit' ), + ); + + $wpdb->insert( + $wpdb->gzd_shipping_provider, + $data + ); + + $provider_id = $wpdb->insert_id; + + if ( $provider_id ) { + $provider->set_id( $provider_id ); + + $this->save_provider_data( $provider ); + + $provider->update_settings_with_defaults(); + $provider->save_meta_data(); + $provider->apply_changes(); + + $this->clear_caches( $provider ); + + /** + * Action that indicates that a new Shipping Provider has been created in the DB. + * + * @param integer $provider_id The provider id. + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $shipping_provider The shipping provider instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_new_shipping_provider', $provider_id, $provider ); + } + } + + /** + * Generate a unique name to save to the object. + * + * @since 3.6.0 + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider Shipping provider object. + * @return string + */ + protected function get_unqiue_name( $provider ) { + global $wpdb; + + $slug = sanitize_key( $provider->get_title() ); + + // Post slugs must be unique across all posts. + $check_sql = "SELECT shipping_provider_name FROM $wpdb->gzd_shipping_provider WHERE shipping_provider_name = %s AND shipping_provider_id != %d LIMIT 1"; + $provider_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $provider->get_id() ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + + if ( $provider_name_check || ( $this->is_manual_creation_request() && $this->is_reserved_name( $slug ) ) ) { + $suffix = 2; + do { + $alt_provider_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix"; + $provider_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_provider_name, $provider->get_id() ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $suffix++; + } while ( $provider_name_check || ( $this->is_manual_creation_request() && $this->is_reserved_name( $alt_provider_name ) ) ); + $slug = $alt_provider_name; + } + + return $slug; + } + + protected function is_manual_creation_request() { + return apply_filters( 'woocommerce_gzd_shipments_shipping_provider_is_manual_creation_request', false ); + } + + protected function is_reserved_name( $name ) { + $reserved_names = array( + 'dhl', + 'deutsche_post', + 'dpd', + 'gls', + 'ups', + 'hermes', + ); + + return apply_filters( 'woocommerce_gzd_shipments_shipping_provider_is_reserved_name', in_array( $name, $reserved_names, true ) ); + } + + /** + * Method to update a shipping provider in the database. + * + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider Shipping provider object. + */ + public function update( &$provider ) { + global $wpdb; + + $updated_props = array(); + $core_props = $this->core_props; + $changed_props = array_keys( $provider->get_changes() ); + $provider_data = array(); + + foreach ( $changed_props as $prop ) { + + if ( ! in_array( $prop, $core_props, true ) ) { + continue; + } + + switch ( $prop ) { + case 'activated': + $provider_data[ 'shipping_provider_' . $prop ] = $provider->is_activated() ? 1 : 0; + break; + default: + if ( is_callable( array( $provider, 'get_' . $prop ) ) ) { + $provider_data[ 'shipping_provider_' . $prop ] = $provider->{'get_' . $prop}( 'edit' ); + } + break; + } + } + + if ( ! empty( $provider_data ) ) { + $wpdb->update( + $wpdb->gzd_shipping_provider, + $provider_data, + array( 'shipping_provider_id' => $provider->get_id() ) + ); + } + + $this->save_provider_data( $provider ); + + $provider->save_meta_data(); + $provider->apply_changes(); + + $this->clear_caches( $provider ); + + /** + * Action that indicates that a shipping provider has been updated in the DB. + * + * @param integer $shipping_provider_id The shipping provider id. + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $shipping_provider The shipping provider instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipping_provider_updated', $provider->get_id(), $provider ); + } + + /** + * Remove a shipping provider from the database. + * + * @since 3.0.0 + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider Shipping provider object. + * @param bool $force_delete Unused param. + */ + public function delete( &$provider, $force_delete = false ) { + global $wpdb; + + $wpdb->delete( $wpdb->gzd_shipping_provider, array( 'shipping_provider_id' => $provider->get_id() ), array( '%d' ) ); + $wpdb->delete( $wpdb->gzd_shipping_providermeta, array( 'gzd_shipping_provider_id' => $provider->get_id() ), array( '%d' ) ); + + $this->clear_caches( $provider ); + + /** + * Action that indicates that a shipping provider has been deleted from the DB. + * + * @param integer $shipping_provider_id The shipping provider id. + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider The shipping provider object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipping_provider_deleted', $provider->get_id(), $provider ); + } + + /** + * Read a shipping provider from the database. + * + * @since 3.0.0 + * + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider Shipping provider object. + * + * @throws Exception Throw exception if invalid shipping provider. + */ + public function read( &$provider ) { + global $wpdb; + + $data = $wpdb->get_row( + $wpdb->prepare( + "SELECT * FROM {$wpdb->gzd_shipping_provider} WHERE shipping_provider_id = %d LIMIT 1", + $provider->get_id() + ) + ); + + if ( $data ) { + $provider->set_props( + array( + 'name' => $data->shipping_provider_name, + 'title' => $data->shipping_provider_title, + 'activated' => $data->shipping_provider_activated, + 'order' => $data->shipping_provider_order, + ) + ); + + $this->read_provider_data( $provider ); + + $provider->read_meta_data(); + $provider->set_object_read( true ); + + /** + * Action that indicates that a shipping provider has been loaded from DB. + * + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider The shipping provider object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipping_provider_loaded', $provider ); + } else { + throw new Exception( _x( 'Invalid shipping provider.', 'shipments', 'woocommerce-germanized' ) ); + } + } + + public function is_activated( $name ) { + global $wpdb; + + $data = $wpdb->get_row( + $wpdb->prepare( + "SELECT shipping_provider_activated FROM {$wpdb->gzd_shipping_provider} WHERE shipping_provider_name = %s LIMIT 1", + $name + ) + ); + + if ( $data ) { + return wc_string_to_bool( $data->shipping_provider_activated ); + } + + return false; + } + + /** + * Clear any caches. + * + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider Shipping provider object. + * @since 3.0.0 + */ + protected function clear_caches( &$provider ) { + wp_cache_delete( $provider->get_id(), $this->meta_type . '_meta' ); + } + + /* + |-------------------------------------------------------------------------- + | Additional Methods + |-------------------------------------------------------------------------- + */ + + /** + * Read extra data associated with the shipping provider. + * + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider Shipping provider object. + * @since 3.0.0 + */ + protected function read_provider_data( &$provider ) { + $props = array(); + $meta_keys = $this->internal_meta_keys; + + foreach ( $provider->get_extra_data_keys() as $key ) { + $meta_keys[] = '_' . $key; + } + + foreach ( $meta_keys as $meta_key ) { + $props[ substr( $meta_key, 1 ) ] = get_metadata( 'gzd_shipping_provider', $provider->get_id(), $meta_key, true ); + } + + $provider->set_props( $props ); + } + + /** + * Save shipping provider data. + * + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider Shipping provider object. + */ + protected function save_provider_data( &$provider ) { + $updated_props = array(); + $meta_key_to_props = array(); + + foreach ( $this->internal_meta_keys as $meta_key ) { + $prop_name = substr( $meta_key, 1 ); + + if ( in_array( $prop_name, $this->core_props, true ) ) { + continue; + } + + $meta_key_to_props[ $meta_key ] = $prop_name; + } + + // Make sure to take extra data (like product url or text for external products) into account. + $extra_data_keys = $provider->get_extra_data_keys(); + + foreach ( $extra_data_keys as $key ) { + $meta_key_to_props[ '_' . $key ] = $key; + } + + $props_to_update = $this->get_props_to_update( $provider, $meta_key_to_props, 'gzd_shipping_provider' ); + + foreach ( $props_to_update as $meta_key => $prop ) { + + if ( ! is_callable( array( $provider, "get_$prop" ) ) ) { + continue; + } + + $value = $provider->{"get_$prop"}( 'edit' ); + $value = is_string( $value ) ? wp_slash( $value ) : $value; + + if ( is_bool( $value ) ) { + $value = wc_bool_to_string( $value ); + } + + $updated = $this->update_or_delete_meta( $provider, $meta_key, $value ); + + if ( $updated ) { + $updated_props[] = $prop; + } + } + + /** + * Action that fires after updating a shipping providers' properties. + * + * @param \Vendidero\Germanized\Shipments\ShippingProvider\Simple $provider The shipping provider object. + * @param array $changed_props The updated properties. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipping_provider_object_updated_props', $provider, $updated_props ); + } + + /** + * Update meta data in, or delete it from, the database. + * + * Avoids storing meta when it's either an empty string or empty array. + * Other empty values such as numeric 0 and null should still be stored. + * Data-stores can force meta to exist using `must_exist_meta_keys`. + * + * Note: WordPress `get_metadata` function returns an empty string when meta data does not exist. + * + * @param WC_Data $object The WP_Data object (WC_Coupon for coupons, etc). + * @param string $meta_key Meta key to update. + * @param mixed $meta_value Value to save. + * + * @since 3.6.0 Added to prevent empty meta being stored unless required. + * + * @return bool True if updated/deleted. + */ + protected function update_or_delete_meta( $object, $meta_key, $meta_value ) { + if ( in_array( $meta_value, array( array(), '' ), true ) && ! in_array( $meta_key, $this->must_exist_meta_keys, true ) ) { + $updated = delete_metadata( 'gzd_shipping_provider', $object->get_id(), $meta_key ); + } else { + $updated = update_metadata( 'gzd_shipping_provider', $object->get_id(), $meta_key, $meta_value ); + } + + return (bool) $updated; + } + + /** + * Table structure is slightly different between meta types, this function will return what we need to know. + * + * @since 3.0.0 + * @return array Array elements: table, object_id_field, meta_id_field + */ + protected function get_db_info() { + global $wpdb; + + $meta_id_field = 'meta_id'; // for some reason users calls this umeta_id so we need to track this as well. + $table = $wpdb->gzd_shipping_providermeta; + $object_id_field = $this->meta_type . '_id'; + + if ( ! empty( $this->object_id_field_for_meta ) ) { + $object_id_field = $this->object_id_field_for_meta; + } + + return array( + 'table' => $table, + 'object_id_field' => $object_id_field, + 'meta_id_field' => $meta_id_field, + ); + } + + public function get_shipping_provider_count() { + global $wpdb; + + return absint( $wpdb->get_var( "SELECT COUNT( * ) FROM {$wpdb->gzd_shipping_provider}" ) ); + } + + public function get_shipping_provider_name( $provider_id ) { + global $wpdb; + + $provider_name_check = $wpdb->get_row( $wpdb->prepare( "SELECT shipping_provider_name FROM $wpdb->gzd_shipping_provider WHERE shipping_provider_id = %d LIMIT 1", $provider_id ) ); + + if ( ! empty( $provider_name_check ) ) { + return $provider_name_check->shipping_provider_name; + } + + return false; + } + + public function get_shipping_providers() { + global $wpdb; + + $providers = $wpdb->get_results( "SELECT * FROM $wpdb->gzd_shipping_provider ORDER BY shipping_provider_order ASC" ); + $shipping_providers = array(); + + foreach ( $providers as $provider ) { + try { + $shipping_providers[ $provider->shipping_provider_name ] = $provider; + } catch ( Exception $e ) { + continue; + } + } + + return $shipping_providers; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Emails.php b/packages/woocommerce-germanized-shipments/src/Emails.php new file mode 100644 index 000000000..8523de1e0 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Emails.php @@ -0,0 +1,229 @@ +id ), $email ) ) ) { + if ( $shipments_order = wc_gzd_get_shipment_order( $order ) ) { + $template = $plain_text ? 'plain/email-order-shipments.php' : 'email-order-shipments.php'; + + wc_get_template( + 'emails/' . $template, + array( + 'shipments' => $shipments_order->get_simple_shipments( true ), + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } + } + } + + public static function set_woocommerce_template_dir( $dir, $template ) { + if ( file_exists( Package::get_path() . '/templates/' . $template ) ) { + return 'woocommerce-germanized'; + } + + return $dir; + } + + public static function register_emails( $emails ) { + $emails['WC_GZD_Email_Customer_Shipment'] = include Package::get_path() . '/includes/emails/class-wc-gzd-email-customer-shipment.php'; + $emails['WC_GZD_Email_Customer_Return_Shipment'] = include Package::get_path() . '/includes/emails/class-wc-gzd-email-customer-return-shipment.php'; + $emails['WC_GZD_Email_Customer_Return_Shipment_Delivered'] = include Package::get_path() . '/includes/emails/class-wc-gzd-email-customer-return-shipment-delivered.php'; + $emails['WC_GZD_Email_Customer_Guest_Return_Shipment_Request'] = include Package::get_path() . '/includes/emails/class-wc-gzd-email-customer-guest-return-shipment-request.php'; + $emails['WC_GZD_Email_New_Return_Shipment_Request'] = include Package::get_path() . '/includes/emails/class-wc-gzd-email-new-return-shipment-request.php'; + + return $emails; + } + + public static function email_hooks() { + add_action( 'woocommerce_gzd_email_shipment_details', array( __CLASS__, 'email_return_instructions' ), 5, 4 ); + add_action( 'woocommerce_gzd_email_shipment_details', array( __CLASS__, 'email_tracking' ), 10, 4 ); + add_action( 'woocommerce_gzd_email_shipment_details', array( __CLASS__, 'email_address' ), 20, 4 ); + add_action( 'woocommerce_gzd_email_shipment_details', array( __CLASS__, 'email_details' ), 30, 4 ); + } + + public static function register_email_notifications( $actions ) { + $actions = array_merge( + $actions, + array( + 'woocommerce_gzd_shipment_status_draft_to_processing', + 'woocommerce_gzd_shipment_status_draft_to_shipped', + 'woocommerce_gzd_shipment_status_draft_to_delivered', + 'woocommerce_gzd_shipment_status_processing_to_shipped', + 'woocommerce_gzd_shipment_status_processing_to_delivered', + 'woocommerce_gzd_shipment_status_shipped_to_delivered', + 'woocommerce_gzd_return_shipment_status_draft_to_processing', + 'woocommerce_gzd_return_shipment_status_draft_to_shipped', + 'woocommerce_gzd_return_shipment_status_draft_to_delivered', + 'woocommerce_gzd_return_shipment_status_draft_to_requested', + 'woocommerce_gzd_return_shipment_status_processing_to_shipped', + 'woocommerce_gzd_return_shipment_status_processing_to_delivered', + 'woocommerce_gzd_return_shipment_status_shipped_to_delivered', + 'woocommerce_gzd_return_shipment_status_requested_to_processing', + 'woocommerce_gzd_return_shipment_status_requested_to_shipped', + ) + ); + + return $actions; + } + + /** + * @param Shipment $shipment + * @param bool $sent_to_admin + * @param bool $plain_text + * @param string $email + */ + public static function email_return_instructions( $shipment, $sent_to_admin = false, $plain_text = false, $email = '' ) { + + if ( 'return' !== $shipment->get_type() || $shipment->has_status( 'delivered' ) ) { + return; + } + + if ( $plain_text ) { + wc_get_template( + 'emails/plain/email-return-shipment-instructions.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } else { + wc_get_template( + 'emails/email-return-shipment-instructions.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } + } + + /** + * @param Shipment $shipment + * @param bool $sent_to_admin + * @param bool $plain_text + * @param string $email + */ + public static function email_tracking( $shipment, $sent_to_admin = false, $plain_text = false, $email = '' ) { + + // Do only include shipment tracking if estimated delivery date or tracking instruction or tracking url exists + // Do not show tracking for returns + if ( ! $shipment->has_tracking() || $shipment->has_status( 'delivered' ) || 'return' === $shipment->get_type() ) { + return; + } + + if ( $plain_text ) { + wc_get_template( + 'emails/plain/email-shipment-tracking.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } else { + wc_get_template( + 'emails/email-shipment-tracking.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } + } + + /** + * @param Shipment $shipment + * @param bool $sent_to_admin + * @param bool $plain_text + * @param string $email + */ + public static function email_address( $shipment, $sent_to_admin = false, $plain_text = false, $email = '' ) { + if ( is_a( $shipment, 'Vendidero\Germanized\Shipments\ReturnShipment' ) ) { + if ( $shipment->hide_return_address() ) { + return; + } + } + + if ( $plain_text ) { + wc_get_template( + 'emails/plain/email-shipment-address.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } else { + wc_get_template( + 'emails/email-shipment-address.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } + } + + /** + * Show the order details table + * + * @param \WC_Order $order Order instance. + * @param bool $sent_to_admin If should sent to admin. + * @param bool $plain_text If is plain text email. + * @param string $email Email address. + */ + public static function email_details( $shipment, $sent_to_admin = false, $plain_text = false, $email = '' ) { + if ( $plain_text ) { + wc_get_template( + 'emails/plain/email-shipment-details.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } else { + wc_get_template( + 'emails/email-shipment-details.php', + array( + 'shipment' => $shipment, + 'sent_to_admin' => $sent_to_admin, + 'plain_text' => $plain_text, + 'email' => $email, + ) + ); + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/FormHandler.php b/packages/woocommerce-germanized-shipments/src/FormHandler.php new file mode 100644 index 000000000..4881a7460 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/FormHandler.php @@ -0,0 +1,337 @@ +' . _x( 'Error:', 'shipments', 'woocommerce-germanized' ) . ' ' . _x( 'We were not able to find a matching order.', 'shipments', 'woocommerce-germanized' ) ); + } + + if ( ! wc_gzd_order_is_customer_returnable( $order ) ) { + throw new Exception( '' . _x( 'Error:', 'shipments', 'woocommerce-germanized' ) . ' ' . _x( 'This order is currently not eligible for returns. Please contact us for further details.', 'shipments', 'woocommerce-germanized' ) ); + } + + $key = 'wc_gzd_order_return_request_' . wp_generate_password( 13, false ); + + $order->update_meta_data( '_return_request_key', $key ); + $order->save(); + + // Send email to customer + wc_add_notice( _x( 'Thank you. You\'ll receive an email containing a link to create a new return to your order.', 'shipments', 'woocommerce-germanized' ), 'success' ); + + WC()->mailer()->emails['WC_GZD_Email_Customer_Guest_Return_Shipment_Request']->trigger( $order ); + + do_action( 'woocommerce_gzd_return_request_successful', $order ); + + } catch ( Exception $e ) { + wc_add_notice( $e->getMessage(), 'error' ); + do_action( 'woocommerce_gzd_return_request_failed' ); + } + } + } + + /** + * @param $order_id + * @param $email + * + * @return false|integer + */ + public static function find_order( $order_id, $email ) { + $order_id_parsed = self::get_order_id_from_string( $order_id ); + $db_order_id = false; + $orders = wc_get_orders( + apply_filters( + 'woocommerce_gzd_return_request_order_query_args', + array( + 'billing_email' => $email, + 'post__in' => array( $order_id_parsed ), + 'limit' => 1, + 'return' => 'ids', + ) + ) + ); + + // Now lets try to find the order by a custom order number field + if ( empty( $orders ) ) { + add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', array( __CLASS__, 'filter_query_by_order_number' ), 10, 2 ); + + $orders = wc_get_orders( + apply_filters( + 'woocommerce_gzd_return_request_alternate_order_query_args', + array( + 'billing_email' => $email, + 'order_number' => $order_id, + 'limit' => 1, + 'return' => 'ids', + ) + ) + ); + + remove_filter( 'woocommerce_order_data_store_cpt_get_orders_query', array( __CLASS__, 'filter_query_by_order_number' ), 10 ); + } + + if ( ! empty( $orders ) ) { + $db_order_id = $orders[0]; + } + + return apply_filters( 'woocommerce_gzd_shipments_valid_order_for_return_request', $db_order_id, $order_id, $email ); + } + + public static function filter_query_by_order_number( $query, $query_vars ) { + $meta_field_name = apply_filters( 'woocommerce_gzd_return_request_customer_order_number_meta_key', '_order_number' ); + + if ( ! empty( $query_vars['order_number'] ) ) { + $query['meta_query'][] = array( + 'key' => $meta_field_name, + 'value' => esc_attr( wc_clean( $query_vars['order_number'] ) ), + 'compare' => '=', + ); + } + + return $query; + } + + /** + * Save the password/account details and redirect back to the my account page. + */ + public static function add_return_shipment() { + $nonce_value = isset( $_REQUEST['add-return-shipment-nonce'] ) ? wp_unslash( $_REQUEST['add-return-shipment-nonce'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + + if ( ! wp_verify_nonce( $nonce_value, 'add_return_shipment' ) ) { + return; + } + + if ( empty( $_POST['action'] ) || 'gzd_add_return_shipment' !== $_POST['action'] ) { + return; + } + + wc_nocache_headers(); + + $order_id = ! empty( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : false; + $items = ! empty( $_POST['items'] ) ? wc_clean( wp_unslash( $_POST['items'] ) ) : array(); + $item_data = ! empty( $_POST['item'] ) ? wc_clean( wp_unslash( $_POST['item'] ) ) : array(); + + if ( ! ( $order = wc_get_order( $order_id ) ) || ( ! wc_gzd_customer_can_add_return_shipment( $order_id ) ) ) { + wc_add_notice( _x( 'You are not allowed to add returns to that order.', 'shipments', 'woocommerce-germanized' ), 'error' ); + return; + } + + if ( ! wc_gzd_order_is_customer_returnable( $order ) ) { + wc_add_notice( _x( 'Sorry, but this order does not support returns any longer.', 'shipments', 'woocommerce-germanized' ), 'error' ); + return; + } + + if ( empty( $items ) ) { + wc_add_notice( _x( 'Please choose one or more items from the list.', 'shipments', 'woocommerce-germanized' ), 'error' ); + return; + } + + $return_items = array(); + $shipment_order = wc_gzd_get_shipment_order( $order ); + + foreach ( $items as $order_item_id ) { + if ( $item = $shipment_order->get_simple_shipment_item( $order_item_id ) ) { + $quantity = isset( $item_data[ $order_item_id ]['quantity'] ) ? absint( $item_data[ $order_item_id ]['quantity'] ) : 0; + $quantity_returnable = $shipment_order->get_item_quantity_left_for_returning( $order_item_id ); + $reason = isset( $item_data[ $order_item_id ]['reason'] ) ? wc_clean( $item_data[ $order_item_id ]['reason'] ) : ''; + + if ( ! empty( $reason ) && ! wc_gzd_return_shipment_reason_exists( $reason ) ) { + wc_add_notice( _x( 'The return reason you have chosen does not exist.', 'shipments', 'woocommerce-germanized' ), 'error' ); + return; + } elseif ( empty( $reason ) && ! wc_gzd_allow_customer_return_empty_return_reason( $order ) ) { + wc_add_notice( _x( 'Please choose a return reason from the list.', 'shipments', 'woocommerce-germanized' ), 'error' ); + return; + } + + if ( $quantity > $quantity_returnable ) { + wc_add_notice( _x( 'Please check your item quantities. Quantities must not exceed maximum quantities.', 'shipments', 'woocommerce-germanized' ), 'error' ); + return; + } else { + $return_items[ $order_item_id ] = array( + 'quantity' => $quantity, + 'return_reason_code' => $reason, + ); + } + } + } + + if ( empty( $return_items ) ) { + wc_add_notice( _x( 'Please choose one or more items from the list.', 'shipments', 'woocommerce-germanized' ), 'error' ); + } + + if ( wc_notice_count( 'error' ) > 0 ) { + return; + } + + $needs_manual_confirmation = wc_gzd_customer_return_needs_manual_confirmation( $order ); + + if ( $needs_manual_confirmation ) { + $default_status = 'requested'; + } else { + $default_status = 'processing'; + } + + // Add return shipment + $return_shipment = wc_gzd_create_return_shipment( + $shipment_order, + array( + 'items' => $return_items, + 'props' => array( + /** + * This filter may be used to adjust the default status of a return shipment + * added by a customer. + * + * @param string $status The default status. + * @param WC_Order $order The order object. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + 'status' => apply_filters( 'woocommerce_gzd_customer_new_return_shipment_request_status', $default_status, $order ), + 'is_customer_requested' => true, + ), + ) + ); + + if ( is_wp_error( $return_shipment ) ) { + wc_add_notice( _x( 'There was an error while creating the return. Please contact us for further information.', 'shipments', 'woocommerce-germanized' ), 'error' ); + return; + } else { + // Delete return request key if available + $shipment_order->delete_order_return_request_key(); + + $success_message = self::get_return_request_success_message( $needs_manual_confirmation ); + + // Do not add success message for guest returns + if ( $order->get_customer_id() > 0 ) { + wc_add_notice( $success_message ); + } + + /** + * This hook is fired after a customer has added a new return request + * for a specific shipment. The return shipment object has been added successfully. + * + * @param ReturnShipment $shipment The return shipment object. + * @param WC_Order $order The order object. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_new_customer_return_shipment_request', $return_shipment, $order ); + + if ( $needs_manual_confirmation ) { + $return_url = $order->get_view_order_url(); + } else { + $return_url = $return_shipment->get_view_shipment_url(); + } + + if ( $order->get_customer_id() <= 0 ) { + $return_url = add_query_arg( + array( + 'return-request-success' => 'yes', + 'needs-confirmation' => wc_bool_to_string( $needs_manual_confirmation ), + ), + wc_get_page_permalink( 'myaccount' ) + ); + } + + /** + * This filter may be used to adjust the redirect of a customer + * after adding a new return shipment. In case the return request needs manual confirmation + * the customer will be redirected to the parent shipment. + * + * @param string $url The redirect URL. + * @param ReturnShipment $shipment The return shipment object. + * @param boolean $needs_manual_confirmation Whether the request needs manual confirmation or not. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + $redirect = apply_filters( 'woocommerce_gzd_customer_new_return_shipment_request_redirect', $return_url, $return_shipment, $needs_manual_confirmation ); + + wp_safe_redirect( esc_url_raw( $redirect ) ); + exit; + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Install.php b/packages/woocommerce-germanized-shipments/src/Install.php new file mode 100644 index 000000000..802c9b383 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Install.php @@ -0,0 +1,561 @@ + $value, + ); + } elseif ( 'label_ident_min_age' === $setting_name ) { + $service_name = 'IdentCheck'; + $services[ $service_name ] = 'yes'; + + $service_meta[ $service_name ] = array( + 'min_age' => $value, + ); + } elseif ( 'label_auto_inlay_return_label' === $setting_name ) { + if ( 'no' !== $value ) { + $services['dhlRetoure'] = 'yes'; + } + } + } + + return array( + 'products' => $products, + 'services' => $services, + 'service_meta' => $service_meta, + ); + } + + /** + * @param array $config_set_data + * @param ConfigurationSetTrait $handler + * @param ShippingProvider $provider + * @param string $shipment_type + * + * @return boolean + * @throws \Exception + */ + protected static function create_configuration_set_from_migration_data( $config_set_data, $handler, $provider, $shipment_type = 'simple' ) { + foreach ( $config_set_data['products'] as $zone => $product ) { + $config_set = $handler->get_or_create_configuration_set( + array( + 'shipping_provider_name' => $provider->get_name(), + 'shipment_type' => $shipment_type, + 'zone' => $zone, + ) + ); + + $config_set->update_product( $product ); + + foreach ( $config_set_data['services'] as $service_name => $value ) { + if ( $p_service = $provider->get_service( $service_name ) ) { + if ( $p_service->supports( + array( + 'product_id' => $product, + 'zone' => $zone, + 'shipment_type' => 'simple', + ) + ) ) { + $config_set->update_service( $service_name, $value ); + + if ( array_key_exists( $service_name, $config_set_data['service_meta'] ) ) { + foreach ( $config_set_data['service_meta'][ $service_name ] as $k => $v ) { + $config_set->update_service_meta( $service_name, $k, $v ); + } + } + } + } + } + } + + return true; + } + + public static function migrate_to_configuration_sets( $providers_to_migrate = array() ) { + $providers = empty( $providers_to_migrate ) ? Helper::instance()->get_shipping_providers() : (array) $providers_to_migrate; + $provider_ids = array(); + + foreach ( $providers as $provider ) { + if ( ! is_a( $provider, '\Vendidero\Germanized\Shipments\Interfaces\ShippingProvider' ) ) { + $provider = wc_gzd_get_shipping_provider( $provider ); + } + + if ( is_a( $provider, '\Vendidero\Germanized\Shipments\ShippingProvider\Auto' ) && $provider->is_activated() ) { + $provider_ids[] = $provider->get_id(); + + if ( is_callable( array( $provider, 'get_configuration_sets' ) ) ) { + $config_data = array(); + + foreach ( $provider->get_meta_data() as $meta ) { + $setting_name = $meta->key; + $value = $meta->value; + + if ( 'label_default_page_format' === $setting_name ) { + $provider->set_label_print_format( $value ); + } else { + $config_data = array_replace_recursive( $config_data, self::get_configuration_set_data( $setting_name, $value ) ); + } + } + + if ( isset( $config_data['products'] ) ) { + self::create_configuration_set_from_migration_data( $config_data, $provider, $provider ); + } + + $provider->save(); + } + } + } + + // Make sure shipping zones are loaded + include_once WC_ABSPATH . 'includes/class-wc-shipping-zones.php'; + + foreach ( \WC_Shipping_Zones::get_zones() as $zone_data ) { + if ( $zone = \WC_Shipping_Zones::get_zone( $zone_data['id'] ) ) { + foreach ( $zone->get_shipping_methods() as $method ) { + if ( $shipment_method = MethodHelper::get_provider_method( $method ) ) { + if ( $shipment_method->is_placeholder() || ! $shipment_method->get_method()->supports( 'instance-settings' ) ) { + continue; + } + + $shipment_method->get_method()->init_instance_settings(); + + $settings = $shipment_method->get_method()->instance_settings; + + if ( isset( $settings['configuration_sets'] ) && ! empty( $settings['configuration_sets'] ) ) { + continue; + } + + if ( isset( $settings['shipping_provider'] ) && ! empty( $settings['shipping_provider'] ) ) { + $provider_name = $settings['shipping_provider']; + + if ( $provider = wc_gzd_get_shipping_provider( $provider_name ) ) { + if ( ! in_array( $provider->get_id(), $provider_ids, true ) ) { + continue; + } + + $settings_prefix = "{$provider_name}_"; + $config_data = array(); + + foreach ( $settings as $setting_name => $value ) { + if ( substr( $setting_name, 0, strlen( $settings_prefix ) ) === $settings_prefix ) { + $setting_name = substr( $setting_name, strlen( $settings_prefix ) ); + $config_data = array_replace_recursive( $config_data, self::get_configuration_set_data( $setting_name, $value ) ); + } + } + + if ( isset( $config_data['products'] ) ) { + self::create_configuration_set_from_migration_data( $config_data, $shipment_method, $provider ); + + $configuration_sets = $shipment_method->get_configuration_sets(); + + if ( ! empty( $configuration_sets ) ) { + $current_settings = $shipment_method->get_method()->instance_settings; + + if ( ! empty( $current_settings ) ) { + update_option( $shipment_method->get_method()->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $shipment_method->get_method()->id . '_instance_settings_values', $current_settings, $shipment_method->get_method() ), 'yes' ); + } + } + } + } + } + } + } + } + } + } + + private static function update_providers() { + $providers = Helper::instance()->get_shipping_providers(); + + foreach ( $providers as $provider ) { + if ( ! $provider->is_activated() ) { + continue; + } + + $provider->update_settings_with_defaults(); + $provider->save(); + } + } + + private static function maybe_create_return_reasons() { + $reasons = get_option( 'woocommerce_gzd_shipments_return_reasons', null ); + + if ( is_null( $reasons ) ) { + $default_reasons = array( + array( + 'order' => 1, + 'code' => 'wrong-product', + 'reason' => _x( 'Wrong product or size ordered', 'shipments', 'woocommerce-germanized' ), + ), + array( + 'order' => 2, + 'code' => 'not-needed', + 'reason' => _x( 'Product no longer needed', 'shipments', 'woocommerce-germanized' ), + ), + array( + 'order' => 3, + 'code' => 'look', + 'reason' => _x( 'Don\'t like the look', 'shipments', 'woocommerce-germanized' ), + ), + ); + + update_option( 'woocommerce_gzd_shipments_return_reasons', $default_reasons ); + } + } + + private static function get_db_version() { + return get_option( 'woocommerce_gzd_shipments_db_version', null ); + } + + private static function maybe_create_packaging() { + $packaging = wc_gzd_get_packaging_list(); + $db_version = self::get_db_version(); + + if ( empty( $packaging ) && is_null( $db_version ) ) { + $defaults = array( + array( + 'description' => _x( 'Cardboard S', 'shipments', 'woocommerce-germanized' ), + 'length' => 25, + 'width' => 17.5, + 'height' => 10, + 'weight' => 0.14, + 'max_content_weight' => 30, + 'type' => 'cardboard', + ), + array( + 'description' => _x( 'Cardboard M', 'shipments', 'woocommerce-germanized' ), + 'length' => 37.5, + 'width' => 30, + 'height' => 13.5, + 'weight' => 0.23, + 'max_content_weight' => 30, + 'type' => 'cardboard', + ), + array( + 'description' => _x( 'Cardboard L', 'shipments', 'woocommerce-germanized' ), + 'length' => 45, + 'width' => 35, + 'height' => 20, + 'weight' => 0.3, + 'max_content_weight' => 30, + 'type' => 'cardboard', + ), + array( + 'description' => _x( 'Letter C5/6', 'shipments', 'woocommerce-germanized' ), + 'length' => 22, + 'width' => 11, + 'height' => 1, + 'weight' => 0, + 'max_content_weight' => 0.05, + 'type' => 'letter', + ), + array( + 'description' => _x( 'Letter C4', 'shipments', 'woocommerce-germanized' ), + 'length' => 32.4, + 'width' => 22.9, + 'height' => 2, + 'weight' => 0.01, + 'max_content_weight' => 1, + 'type' => 'letter', + ), + ); + + foreach ( $defaults as $default ) { + $packaging = new Packaging(); + $packaging->set_props( $default ); + $packaging->save(); + } + } + } + + private static function create_tables() { + global $wpdb; + + $current_version = get_option( 'woocommerce_gzd_shipments_version', null ); + $current_db_version = self::get_db_version(); + + /** + * Make possible duplicate names unique. + */ + if ( null !== $current_version && isset( $wpdb->gzd_shipping_provider ) ) { + $providers = $wpdb->get_results( "SELECT * FROM $wpdb->gzd_shipping_provider" ); + $shipping_providers = array(); + + foreach ( $providers as $provider ) { + if ( in_array( $provider->shipping_provider_name, $shipping_providers, true ) ) { + $unique_provider_name = sanitize_title( $provider->shipping_provider_name . '_' . wp_generate_password( 4, false, false ) ); + + $wpdb->update( + $wpdb->gzd_shipping_provider, + array( + 'shipping_provider_name' => $unique_provider_name, + ), + array( 'shipping_provider_id' => $provider->shipping_provider_id ) + ); + } else { + $shipping_providers[] = $provider->shipping_provider_name; + } + } + } + + $wpdb->hide_errors(); + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + $db_delta_result = dbDelta( self::get_schema() ); + + /** + * Update MySQL datetime default to NULL for MySQL 8 compatibility. + */ + if ( ! empty( $current_db_version ) && version_compare( $current_db_version, '2.3.0', '<' ) ) { + $date_fields = array( + "{$wpdb->prefix}woocommerce_gzd_shipments" => array( + 'shipment_date_created', + 'shipment_date_created_gmt', + 'shipment_date_sent', + 'shipment_date_sent_gmt', + 'shipment_est_delivery_date', + 'shipment_est_delivery_date_gmt', + ), + "{$wpdb->prefix}woocommerce_gzd_shipment_labels" => array( + 'label_date_created', + 'label_date_created_gmt', + ), + "{$wpdb->prefix}woocommerce_gzd_packaging" => array( + 'packaging_date_created', + 'packaging_date_created_gmt', + ), + ); + + foreach ( $date_fields as $table => $columns ) { + foreach ( $columns as $column ) { + $result = $wpdb->query( "ALTER TABLE `$table` CHANGE $column $column datetime DEFAULT NULL;" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + + if ( true !== $result ) { + $db_delta_result = false; + Package::log( sprintf( 'Error while updating datetime field in %s for column %s', $table, $column ), 'error' ); + } + } + } + } + + return $db_delta_result; + } + + private static function create_upload_dir() { + Package::maybe_set_upload_dir(); + + $dir = Package::get_upload_dir(); + + if ( ! @is_dir( $dir['basedir'] ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + @mkdir( $dir['basedir'] ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + } + + if ( ! file_exists( trailingslashit( $dir['basedir'] ) . '.htaccess' ) ) { + @file_put_contents( trailingslashit( $dir['basedir'] ) . '.htaccess', 'deny from all' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents + } + + if ( ! file_exists( trailingslashit( $dir['basedir'] ) . 'index.php' ) ) { + @touch( trailingslashit( $dir['basedir'] ) . 'index.php' ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + } + } + + private static function get_schema() { + global $wpdb; + + $collate = ''; + + if ( $wpdb->has_cap( 'collation' ) ) { + $collate = $wpdb->get_charset_collate(); + } + + /** + * Use a varchar(191) for shipping_provider_name as the key length might overflow max key length for older MySQL (< 5.7). + * @see https://stackoverflow.com/a/31474509 + */ + $tables = " +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipment_items ( + shipment_item_id bigint(20) unsigned NOT NULL auto_increment, + shipment_id bigint(20) unsigned NOT NULL, + shipment_item_name text NOT NULL, + shipment_item_order_item_id bigint(20) unsigned NOT NULL, + shipment_item_product_id bigint(20) unsigned NOT NULL, + shipment_item_parent_id bigint(20) unsigned NOT NULL, + shipment_item_quantity smallint(4) unsigned NOT NULL DEFAULT '1', + PRIMARY KEY (shipment_item_id), + KEY shipment_id (shipment_id), + KEY shipment_item_order_item_id (shipment_item_order_item_id), + KEY shipment_item_product_id (shipment_item_product_id), + KEY shipment_item_parent_id (shipment_item_parent_id) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipment_itemmeta ( + meta_id bigint(20) unsigned NOT NULL auto_increment, + gzd_shipment_item_id bigint(20) unsigned NOT NULL, + meta_key varchar(255) default NULL, + meta_value longtext NULL, + PRIMARY KEY (meta_id), + KEY gzd_shipment_item_id (gzd_shipment_item_id), + KEY meta_key (meta_key(32)) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipments ( + shipment_id bigint(20) unsigned NOT NULL auto_increment, + shipment_date_created datetime default NULL, + shipment_date_created_gmt datetime default NULL, + shipment_date_sent datetime default NULL, + shipment_date_sent_gmt datetime default NULL, + shipment_est_delivery_date datetime default NULL, + shipment_est_delivery_date_gmt datetime default NULL, + shipment_status varchar(20) NOT NULL default 'gzd-draft', + shipment_order_id bigint(20) unsigned NOT NULL DEFAULT 0, + shipment_packaging_id bigint(20) unsigned NOT NULL DEFAULT 0, + shipment_parent_id bigint(20) unsigned NOT NULL DEFAULT 0, + shipment_country varchar(2) NOT NULL DEFAULT '', + shipment_tracking_id varchar(200) NOT NULL DEFAULT '', + shipment_type varchar(200) NOT NULL DEFAULT '', + shipment_version varchar(200) NOT NULL DEFAULT '', + shipment_search_index longtext NOT NULL DEFAULT '', + shipment_shipping_provider varchar(200) NOT NULL DEFAULT '', + shipment_shipping_method varchar(200) NOT NULL DEFAULT '', + PRIMARY KEY (shipment_id), + KEY shipment_order_id (shipment_order_id), + KEY shipment_packaging_id (shipment_packaging_id), + KEY shipment_parent_id (shipment_parent_id) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipment_labels ( + label_id bigint(20) unsigned NOT NULL auto_increment, + label_date_created datetime default NULL, + label_date_created_gmt datetime default NULL, + label_shipment_id bigint(20) unsigned NOT NULL, + label_parent_id bigint(20) unsigned NOT NULL DEFAULT 0, + label_number varchar(200) NOT NULL DEFAULT '', + label_product_id varchar(200) NOT NULL DEFAULT '', + label_shipping_provider varchar(200) NOT NULL DEFAULT '', + label_path varchar(200) NOT NULL DEFAULT '', + label_type varchar(200) NOT NULL DEFAULT '', + PRIMARY KEY (label_id), + KEY label_shipment_id (label_shipment_id), + KEY label_parent_id (label_parent_id) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipment_labelmeta ( + meta_id bigint(20) unsigned NOT NULL auto_increment, + gzd_shipment_label_id bigint(20) unsigned NOT NULL, + meta_key varchar(255) default NULL, + meta_value longtext NULL, + PRIMARY KEY (meta_id), + KEY gzd_shipment_label_id (gzd_shipment_label_id), + KEY meta_key (meta_key(32)) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipmentmeta ( + meta_id bigint(20) unsigned NOT NULL auto_increment, + gzd_shipment_id bigint(20) unsigned NOT NULL, + meta_key varchar(255) default NULL, + meta_value longtext NULL, + PRIMARY KEY (meta_id), + KEY gzd_shipment_id (gzd_shipment_id), + KEY meta_key (meta_key(32)) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_packaging ( + packaging_id bigint(20) unsigned NOT NULL auto_increment, + packaging_date_created datetime default NULL, + packaging_date_created_gmt datetime default NULL, + packaging_type varchar(200) NOT NULL DEFAULT '', + packaging_description tinytext NOT NULL DEFAULT '', + packaging_weight decimal(6,2) unsigned NOT NULL DEFAULT 0, + packaging_order bigint(20) unsigned NOT NULL DEFAULT 0, + packaging_max_content_weight decimal(6,2) unsigned NOT NULL DEFAULT 0, + packaging_length decimal(6,2) unsigned NOT NULL DEFAULT 0, + packaging_width decimal(6,2) unsigned NOT NULL DEFAULT 0, + packaging_height decimal(6,2) unsigned NOT NULL DEFAULT 0, + packaging_inner_length decimal(6,2) unsigned NOT NULL DEFAULT 0, + packaging_inner_width decimal(6,2) unsigned NOT NULL DEFAULT 0, + packaging_inner_height decimal(6,2) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY (packaging_id) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_packagingmeta ( + meta_id bigint(20) unsigned NOT NULL auto_increment, + gzd_packaging_id bigint(20) unsigned NOT NULL, + meta_key varchar(255) default NULL, + meta_value longtext NULL, + PRIMARY KEY (meta_id), + KEY gzd_packaging_id (gzd_packaging_id), + KEY meta_key (meta_key(32)) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipping_provider ( + shipping_provider_id bigint(20) unsigned NOT NULL auto_increment, + shipping_provider_activated tinyint(1) NOT NULL default 1, + shipping_provider_order smallint(10) NOT NULL DEFAULT 0, + shipping_provider_title varchar(200) NOT NULL DEFAULT '', + shipping_provider_name varchar(191) NOT NULL DEFAULT '', + PRIMARY KEY (shipping_provider_id), + UNIQUE KEY shipping_provider_name (shipping_provider_name) +) $collate; +CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipping_providermeta ( + meta_id bigint(20) unsigned NOT NULL auto_increment, + gzd_shipping_provider_id bigint(20) unsigned NOT NULL, + meta_key varchar(255) default NULL, + meta_value longtext NULL, + PRIMARY KEY (meta_id), + KEY gzd_shipping_provider_id (gzd_shipping_provider_id), + KEY meta_key (meta_key(32)) +) $collate;"; + + return $tables; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Interfaces/LabelConfigurationSet.php b/packages/woocommerce-germanized-shipments/src/Interfaces/LabelConfigurationSet.php new file mode 100644 index 000000000..d0897a9e6 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Interfaces/LabelConfigurationSet.php @@ -0,0 +1,39 @@ +shipment; + + if ( ! $shipment->has_label() && $shipment->needs_label( false ) ) { + if ( $provider = $shipment->get_shipping_provider_instance() ) { + $auto_status = str_replace( 'gzd-', '', $provider->get_label_automation_shipment_status( $shipment ) ); + + if ( $provider->automatically_generate_label( $shipment ) && $auto_status === $shipment->get_status() ) { + self::cancel_deferred_sync( array( 'shipment_id' => $shipment->get_id() ) ); + self::create_label( $shipment->get_id(), $shipment ); + + if ( $shipment->has_label() ) { + $label = $shipment->get_label(); + + if ( $file = $label->get_file() ) { + $attachments[] = $file; + } + } + } + } + } + } + + return $attachments; + } + + public static function auto_sync_callback( $shipment_id ) { + /** + * Maybe cancel duplicate deferred syncs. + */ + self::cancel_deferred_sync( array( 'shipment_id' => $shipment_id ) ); + + Package::log( 'Starting shipment #' . $shipment_id . ' label sync (deferred)' ); + + self::create_label( $shipment_id ); + + return true; + } + + public static function cancel_deferred_sync( $args ) { + $queue = WC()->queue(); + + /** + * Cancel outstanding events and queue new. + */ + $queue->cancel_all( 'woocommerce_gzd_shipments_label_auto_sync_callback', $args, 'woocommerce-gzd-shipments-label-sync' ); + } + + /** + * @param Shipment $shipment + */ + public static function maybe_adjust_shipment_status( $shipment ) { + if ( $provider = $shipment->get_shipping_provider_instance() ) { + if ( $provider->automatically_set_shipment_status_shipped( $shipment ) ) { + $shipment->set_status( 'shipped' ); + } + } + } + + public static function set_after_create_automation( $shipment_id, $shipment ) { + self::init_automation( $shipment, array( 'is_hook' => false ) ); + } + + /** + * @param Shipment $shipment + * @param boolean $is_hook + */ + protected static function init_automation( $shipment, $args = array() ) { + $label_type = ( 'return' === $shipment->get_type() ? 'return_label' : 'label' ); + + $args = wp_parse_args( + $args, + array( + 'is_hook' => true, + 'allow_deferred_sync' => wc_gzd_shipments_allow_deferred_sync( $label_type ), + ) + ); + + $disable = false; + + if ( ! $shipment->needs_label( false ) ) { + $disable = true; + } + + /** + * Filter that allows to disable automatically creating DHL labels for a certain shipment. + * + * @param boolean $disable True if you want to disable automation. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $disable = apply_filters( 'woocommerce_gzd_shipments_disable_label_auto_generate', $disable, $shipment ); + + if ( $disable ) { + return; + } + + if ( $provider = $shipment->get_shipping_provider_instance() ) { + $hook_prefix = 'woocommerce_gzd_' . ( 'return' === $shipment->get_type() ? 'return_' : '' ) . 'shipment_status_'; + $auto_status = $provider->get_label_automation_shipment_status( $shipment ); + + if ( $provider->automatically_generate_label( $shipment ) && ! empty( $auto_status ) ) { + $status = str_replace( 'gzd-', '', $auto_status ); + + if ( $args['is_hook'] ) { + add_action( + $hook_prefix . $status, + function( $shipment_id, $shipment ) use ( $args ) { + self::maybe_create_label( $shipment_id, $shipment, $args ); + }, + 5, + 2 + ); + } elseif ( $shipment->has_status( $status ) ) { + self::maybe_create_label( $shipment->get_id(), $shipment, $args ); + } + } + } + } + + /** + * @param $shipment_id + * @param Shipment $shipment + */ + public static function set_automation( $shipment_id, $shipment ) { + self::init_automation( $shipment ); + } + + public static function create_label( $shipment_id, $shipment = false ) { + if ( ! $shipment ) { + $shipment = wc_gzd_get_shipment( $shipment_id ); + + if ( ! $shipment ) { + return; + } + } + + if ( ! $shipment->has_label() ) { + $result = $shipment->create_label(); + + if ( is_wp_error( $result ) ) { + $result = wc_gzd_get_shipment_error( $result ); + } + + if ( is_wp_error( $result ) ) { + if ( $result->is_soft_error() ) { + Package::log( sprintf( 'Info while automatically creating label for %1$s: %2$s', $shipment->get_shipment_number(), wc_print_r( $result->get_error_messages(), true ) ) ); + } else { + Package::log( sprintf( 'Error while automatically creating label for %1$s: %2$s', $shipment->get_shipment_number(), wc_print_r( $result->get_error_messages(), true ) ) ); + } + } + } + } + + protected static function maybe_create_label( $shipment_id, $shipment = false, $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'allow_deferred_sync' => wc_gzd_shipments_allow_deferred_sync( 'label' ), + ) + ); + + $defer_args = array( + 'shipment_id' => $shipment_id, + ); + + /** + * Cancel outstanding events and queue new. + */ + self::cancel_deferred_sync( $defer_args ); + + /** + * In case the shipment has automatically been created within the same request + * and is already marked as shipped (e.g. due to default status set to shipped) + * make sure to prevent a deferred sync as the label would not get attached to the email. + */ + if ( $the_shipment = wc_gzd_get_shipment( $shipment_id ) ) { + if ( $the_shipment->has_status( 'shipped' ) ) { + $args['allow_deferred_sync'] = false; + } + } + + if ( $args['allow_deferred_sync'] ) { + Package::log( 'Deferring shipment #' . $shipment_id . ' label sync' ); + $queue = WC()->queue(); + + $queue->schedule_single( + time() + 50, + 'woocommerce_gzd_shipments_label_auto_sync_callback', + $defer_args, + 'woocommerce-gzd-shipments-label-sync' + ); + } else { + self::create_label( $shipment_id, $shipment ); + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Labels/ConfigurationSet.php b/packages/woocommerce-germanized-shipments/src/Labels/ConfigurationSet.php new file mode 100644 index 000000000..299ab0d10 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/ConfigurationSet.php @@ -0,0 +1,390 @@ + '', + 'shipment_type' => 'simple', + 'setting_type' => 'shipping_provider', + 'zone' => 'dom', + 'product' => '', + 'services' => array(), + 'additional' => array(), + ) + ); + + $this->handler = is_a( $handler, '\Vendidero\Germanized\Shipments\Interfaces\LabelConfigurationSet' ) ? $handler : null; + $this->shipment_type = $args['shipment_type']; + $this->setting_type = $args['setting_type']; + $this->zone = $args['zone']; + + $this->product = $args['product']; + $this->services = $args['services']; + $this->additional = $args['additional']; + + if ( is_a( $args['shipping_provider_name'], '\Vendidero\Germanized\Shipments\Interfaces\ShippingProvider' ) ) { + $this->shipping_provider = $args['shipping_provider_name']; + $this->shipping_provider_name = $this->shipping_provider->get_name(); + } else { + $this->shipping_provider_name = $args['shipping_provider_name']; + } + } + + public function get_id() { + return "-p-{$this->get_shipping_provider_name()}-s-{$this->get_shipment_type()}-z-{$this->get_zone()}"; + } + + /** + * @return null|LabelConfigurationSet + */ + public function get_handler() { + return $this->handler; + } + + /** + * @param LabelConfigurationSet|null $handler + */ + public function set_handler( $handler ) { + $this->handler = $handler; + } + + public function get_shipping_provider_name() { + return $this->shipping_provider_name; + } + + public function get_setting_type() { + return $this->setting_type; + } + + public function set_shipping_provider_name( $provider_name ) { + $this->shipping_provider_name = $provider_name; + $this->shipping_provider = null; + } + + public function get_shipping_provider() { + if ( is_null( $this->shipping_provider ) ) { + $this->shipping_provider = wc_gzd_get_shipping_provider( $this->get_shipping_provider_name() ); + } + + return $this->shipping_provider; + } + + public function get_product() { + return $this->product; + } + + public function get_zone() { + return $this->zone; + } + + public function set_zone( $zone ) { + $this->zone = $zone; + } + + public function set_setting_type( $type ) { + $this->setting_type = $type; + } + + public function get_shipment_type() { + return $this->shipment_type; + } + + public function set_shipment_type( $type ) { + $this->shipment_type = $type; + } + + protected function get_all_services() { + if ( is_null( $this->all_services ) ) { + $this->all_services = wp_list_pluck( wp_list_filter( wp_list_filter( $this->services, array( 'value' => 'no' ), 'NOT' ), array( 'value' => '' ), 'NOT' ), 'name' ); + } + + return $this->all_services; + } + + public function get_services() { + return array_values( $this->get_all_services() ); + } + + public function get_service_id( $name ) { + if ( array_key_exists( $name, $this->services ) ) { + return $name; + } else { + if ( $key = array_search( $name, $this->get_all_services(), true ) ) { + return $key; + } + } + + return $name; + } + + public function update_product( $value ) { + $this->product = $value; + $this->update(); + } + + protected function current_product_supports_service( $id ) { + $supports_service = true; + + if ( $shipping_provider = $this->get_shipping_provider() ) { + if ( $service = $shipping_provider->get_service( $id ) ) { + if ( ! $service->supports_product( $this->get_product() ) ) { + $supports_service = false; + } + } + } + + return $supports_service; + } + + public function update_service( $id, $value, $service_name = '' ) { + if ( empty( $service_name ) ) { + $service_name = array_key_exists( $id, $this->services ) ? $this->services[ $id ]['name'] : $id; + } + + if ( ! $this->current_product_supports_service( $id ) ) { + return; + } + + if ( in_array( $value, array( true, false, 'true', 'false', 'yes', 'no' ), true ) ) { + $value = wc_bool_to_string( $value ); + } + + $this->services[ $id ] = array( + 'id' => $id, + 'name' => $service_name, + 'value' => $value, + ); + + $this->all_services = null; + + $this->update(); + } + + public function update_service_meta( $service_id, $meta_key, $value ) { + if ( $this->has_service( $service_id ) ) { + $this->update_setting( $service_id . '_' . $meta_key, $value, 'additional' ); + } + } + + public function has_service( $service ) { + if ( $service_id = $this->get_service_id( $service ) ) { + $service = $service_id; + } + + return in_array( $service, $this->get_all_services(), true ) ? true : false; + } + + public function get_service( $service ) { + if ( $service_id = $this->get_service_id( $service ) ) { + $service = $service_id; + } + + if ( array_key_exists( $service, $this->services ) ) { + return wp_parse_args( + $this->services[ $service ], + array( + 'id' => '', + 'name' => '', + 'value' => null, + ) + ); + } + + return false; + } + + public function get_service_meta( $service_id, $meta_key, $default_value = null ) { + $additional_id = $service_id . '_' . $meta_key; + + return array_key_exists( $additional_id, $this->additional ) ? $this->additional[ $additional_id ] : $default_value; + } + + public function get_service_value( $service, $default = null ) { + if ( $service_id = $this->get_service_id( $service ) ) { + $service = $service_id; + } + + return array_key_exists( $service, $this->services ) ? $this->services[ $service ]['value'] : $default; + } + + public function get_settings() { + if ( is_null( $this->settings ) ) { + $this->settings = array_merge( array( 'product' => $this->product ), wp_list_pluck( $this->services, 'value' ), $this->additional ); + } + + return $this->settings; + } + + public function get_setting_id( $setting_name, $group = '' ) { + if ( 'label_config_set_' === substr( $setting_name, 0, 17 ) ) { + $setting_name = self::get_clean_setting_id( $setting_name ); + } + + if ( 'product' === $setting_name ) { + $group = 'product'; + } elseif ( empty( $group ) ) { + $group = 'additional'; + } + + $setting_id = "label_config_set_{$this->get_id()}-g-{$group}-n-{$setting_name}"; + + return $setting_id; + } + + protected function get_clean_setting_id( $id ) { + $id = strrpos( $id, '-' ) !== false ? substr( $id, strrpos( $id, '-n-' ) + 3 ) : $id; + + return $id; + } + + protected function get_setting_details( $id ) { + $default_args = array( + 'suffix' => $id, + 'group' => '', + 'service_meta' => '', + ); + + $args = Package::extract_args_from_id( $id ); + + if ( ! empty( $args['setting_name'] ) ) { + $default_args['suffix'] = $args['setting_name']; + $default_args['group'] = $args['setting_group']; + $default_args['service_meta'] = $args['meta']; + } + + return $default_args; + } + + public function has_setting( $id ) { + $details = $this->get_setting_details( $id ); + $id = $this->get_clean_setting_id( $id ); + + if ( 'service_meta' === $details['group'] && ! empty( $details['service_meta'] ) ) { + $id = $details['suffix'] . '_' . $details['service_meta']; + } + + $all_settings = $this->get_settings(); + $the_setting = array_key_exists( $id, $all_settings ) ? $all_settings[ $id ] : null; + + if ( 'product' === $id && '' === $the_setting ) { + return false; + } elseif ( ! is_null( $the_setting ) ) { + return true; + } + + return false; + } + + public function get_setting( $id, $default = null, $group = '' ) { + $details = $this->get_setting_details( $id ); + $settings = $this->get_settings(); + + if ( '' === $group && ! empty( $details['group'] ) ) { + $group = $details['group']; + $id = $details['suffix']; + } + + if ( 'service_meta' === $group && ! empty( $details['service_meta'] ) ) { + return $this->get_service_meta( $id, $details['service_meta'], $default ); + } else { + $setting_id = $this->get_clean_setting_id( $id ); + + if ( $this->has_setting( $setting_id ) ) { + return $settings[ $setting_id ]; + } + } + + return $default; + } + + public function update_setting( $id, $value, $group = '' ) { + $details = $this->get_setting_details( $id ); + + if ( '' === $group && ! empty( $details['group'] ) ) { + $group = $details['group']; + $id = $details['suffix']; + } + + if ( ! empty( $group ) ) { + if ( 'product' === $group ) { + $this->update_product( $value ); + } elseif ( 'service' === $group ) { + $this->update_service( $id, $value ); + } elseif ( 'service_meta' === $group && ! empty( $details['service_meta'] ) ) { + $this->update_service_meta( $id, $details['service_meta'], $value ); + } elseif ( 'additional' === $group ) { + $this->update_additional( $id, $value ); + } + } elseif ( 'product' === $id ) { + $this->update_product( $value ); + } elseif ( array_key_exists( $id, $this->services ) ) { + $this->update_service( $id, $value ); + } elseif ( array_key_exists( $id, $this->additional ) ) { + $this->update_additional( $id, $value ); + } + } + + public function update_additional( $key, $value ) { + $this->additional[ $key ] = $value; + $this->update(); + } + + protected function update() { + if ( $handler = $this->get_handler() ) { + $handler->update_configuration_set( $this ); + } + + $this->settings = null; + } + + public function get_data() { + return array( + 'product' => $this->product, + 'services' => $this->services, + 'additional' => $this->additional, + 'shipment_type' => $this->get_shipment_type(), + 'shipping_provider_name' => $this->get_shipping_provider_name(), + 'zone' => $this->get_zone(), + 'setting_type' => $this->get_setting_type(), + ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Labels/ConfigurationSetTrait.php b/packages/woocommerce-germanized-shipments/src/Labels/ConfigurationSetTrait.php new file mode 100644 index 000000000..82940e0bb --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/ConfigurationSetTrait.php @@ -0,0 +1,246 @@ +get_prop( 'configuration_sets', $context ); + } + + protected function get_configuration_set_default_args( $args ) { + if ( is_array( $args ) ) { + $args = wp_parse_args( + $args, + array( + 'shipping_provider_name' => is_a( $this, '\Vendidero\Germanized\Shipments\Interfaces\ShippingProvider' ) ? $this->get_name() : '', + 'shipment_type' => 'simple', + 'zone' => 'dom', + 'setting_type' => $this->get_configuration_set_setting_type(), + ) + ); + } elseif ( is_a( $args, 'Vendidero\Germanized\Shipments\Shipment' ) ) { + $args = wp_parse_args( + $args, + array( + 'shipping_provider_name' => $args->get_shipping_provider(), + 'shipment_type' => $args->get_type(), + 'zone' => $args->get_shipping_zone(), + 'setting_type' => $this->get_configuration_set_setting_type(), + ) + ); + } else { + $args = $this->get_configuration_set_args_by_id( $args ); + } + + return $args; + } + + protected function get_configuration_set_id( $args ) { + $args = $this->get_configuration_set_default_args( $args ); + $set_id = ''; + + if ( ! empty( $args['shipping_provider_name'] ) ) { + $set_id = '-p-' . $args['shipping_provider_name']; + } + + $set_id = "{$set_id}-s-{$args['shipment_type']}-z-{$args['zone']}"; + + return $set_id; + } + + public function get_configuration_set_args_by_id( $id ) { + return Package::extract_args_from_id( $id ); + } + + /** + * @param $args + * @param $context + * + * @return false|ConfigurationSet + */ + protected function get_configuration_set_data( $args, $context = 'view' ) { + if ( is_string( $args ) ) { + $id = $args; + } else { + $id = $this->get_configuration_set_id( $args ); + } + + $configuration_sets = $this->get_configuration_sets( $context ); + + if ( array_key_exists( $id, $configuration_sets ) ) { + return $configuration_sets[ $id ]; + } + + return false; + } + + public function has_configuration_set( $args, $context = 'view' ) { + return $this->get_configuration_set( $args, $context ) ? true : false; + } + + /** + * @param $args + * @param $context + * + * @return false|ConfigurationSet + */ + public function get_configuration_set( $args, $context = 'view' ) { + if ( is_string( $args ) ) { + $configuration_set_id = $this->get_configuration_set_id( $args ); + } else { + $args = $this->get_configuration_set_default_args( $args ); + $configuration_set_id = $this->get_configuration_set_id( $args ); + } + + $configuration_set = false; + + if ( ! is_null( $this->configuration_sets ) && array_key_exists( $configuration_set_id, $this->configuration_sets ) ) { + return $this->configuration_sets[ $configuration_set_id ]; + } elseif ( $configuration_set_data = $this->get_configuration_set_data( $configuration_set_id, $context ) ) { + $configuration_set = new ConfigurationSet( $configuration_set_data, $this ); + + if ( is_null( $this->configuration_sets ) ) { + $this->configuration_sets = array(); + } + + $this->configuration_sets[ $configuration_set_id ] = $configuration_set; + + return $this->configuration_sets[ $configuration_set_id ]; + } + + return $configuration_set; + } + + public function set_configuration_sets( $sets ) { + $this->set_prop( 'configuration_sets', array_filter( (array) $sets ) ); + $this->configuration_sets = null; + } + + /** + * @param ConfigurationSet $set + * + * @return void + */ + public function update_configuration_set( $set ) { + $configuration_sets = $this->get_configuration_sets( 'edit' ); + $set_id = $this->get_configuration_set_id( + array( + 'shipping_provider_name' => $set->get_shipping_provider_name(), + 'shipment_type' => $set->get_shipment_type(), + 'zone' => $set->get_zone(), + ) + ); + + $configuration_sets[ $set_id ] = $set->get_data(); + + $this->set_configuration_sets( $configuration_sets ); + } + + /** + * @param $args + * + * @return ConfigurationSet + */ + public function get_or_create_configuration_set( $args = array(), $context = 'view' ) { + if ( $configuration_set = $this->get_configuration_set( $args, $context ) ) { + return $configuration_set; + } else { + $args = $this->get_configuration_set_default_args( $args ); + $configuration_set = new ConfigurationSet( $args, $this ); + + $this->update_configuration_set( $configuration_set ); + + return $configuration_set; + } + } + + private function get_configuration_set_setting_parts( $setting_name ) { + if ( 'label_config_set_' === substr( $setting_name, 0, 17 ) ) { + return $this->get_configuration_set_args_by_id( substr( $setting_name, 17 ) ); + } + + return false; + } + + public function get_configuration_set_id_by_setting_name( $setting_name ) { + if ( $this->is_configuration_set_setting( $setting_name ) ) { + if ( $parts = $this->get_configuration_set_setting_parts( $setting_name ) ) { + return $this->get_configuration_set_id( $parts ); + } + } + + return false; + } + + public function is_configuration_set_setting( $setting_name ) { + if ( 'label_config_set_' === substr( $setting_name, 0, 17 ) ) { + return true; + } + + return false; + } + + public function get_configuration_setting_suffix( $setting_name ) { + if ( $this->is_configuration_set_setting( $setting_name ) ) { + if ( $parts = $this->get_configuration_set_setting_parts( $setting_name ) ) { + return $parts['setting_name']; + } + } + + return false; + } + + public function reset_configuration_sets( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'shipping_provider_name' => '', + 'shipment_type' => '', + 'zone' => '', + ) + ); + + $id_prefix = ''; + + foreach ( $args as $arg => $value ) { + if ( empty( $value ) ) { + continue; + } + + if ( 'shipping_provider_name' === $arg ) { + $id_prefix = '-p-' . $value; + } elseif ( 'shipment_type' === $arg ) { + $id_prefix = $id_prefix . '-s-' . $value; + } elseif ( 'zone' === $arg ) { + $id_prefix = $id_prefix . '-z-' . $value; + } + } + + if ( empty( $id_prefix ) ) { + $this->set_configuration_sets( array() ); + } else { + $configuration_sets = $this->get_configuration_sets( 'edit' ); + + foreach ( $configuration_sets as $set_id => $set ) { + if ( strstr( $set_id, $id_prefix ) ) { + unset( $configuration_sets[ $set_id ] ); + } + } + + $this->set_configuration_sets( $configuration_sets ); + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Labels/DownloadHandler.php b/packages/woocommerce-germanized-shipments/src/Labels/DownloadHandler.php new file mode 100644 index 000000000..a99679a11 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/DownloadHandler.php @@ -0,0 +1,133 @@ + isset( $_GET['force'] ) ? wc_clean( wp_unslash( $_GET['force'] ) ) : 'no', // phpcs:ignore WordPress.Security.NonceVerification.Recommended + 'path' => isset( $_GET['print'] ) ? wc_clean( wp_unslash( $_GET['print'] ) ) : 'no', // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ); + + $args = self::parse_args( $args ); + + if ( current_user_can( 'edit_shop_orders' ) ) { + $handler = new BulkLabel(); + + if ( $path = $handler->get_file() ) { + $filename = $handler->get_filename(); + + self::download( $path, $filename, $args['force'] ); + } + } + } + } + } + + public static function download_label() { + if ( 'wc-gzd-download-shipment-label' === $_GET['action'] && wp_verify_nonce( wp_unslash( $_REQUEST['_wpnonce'] ), 'download-shipment-label' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $shipment_id = absint( $_GET['shipment_id'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + $has_permission = current_user_can( 'edit_shop_orders' ); + + $args = self::parse_args( + array( + 'force' => wc_string_to_bool( isset( $_GET['force'] ) ? wc_clean( wp_unslash( $_GET['force'] ) ) : false ), + ) + ); + + if ( $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + if ( 'return' === $shipment->get_type() && current_user_can( 'view_order', $shipment->get_order_id() ) && $shipment->has_label() ) { + $has_permission = true; + } + + if ( $has_permission ) { + if ( $label = $shipment->get_label() ) { + $file = $label->get_file( $args['path'] ); + $filename = $label->get_filename( $args['path'] ); + + if ( file_exists( $file ) ) { + self::download( $file, $filename, $args['force'] ); + } + } + } + } + } + } + + public static function parse_args( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'force' => false, + 'path' => '', + ) + ); + + $args['force'] = wc_string_to_bool( $args['force'] ); + + return $args; + } + + public static function download( $path, $filename, $force = false ) { + if ( $force ) { + self::force( $path, $filename ); + } else { + self::embed( $path, $filename ); + } + } + + private static function force( $path, $filename ) { + WC_Download_Handler::download_file_force( $path, $filename ); + } + + private static function embed( $file_path, $filename ) { + if ( ob_get_level() ) { + $levels = ob_get_level(); + for ( $i = 0; $i < $levels; $i++ ) { + @ob_end_clean(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + } + } else { + @ob_end_clean(); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + } + + wc_nocache_headers(); + + header( 'X-Robots-Tag: noindex, nofollow', true ); + header( 'Content-type: application/pdf' ); + header( 'Content-Description: File Transfer' ); + header( 'Content-Disposition: inline; filename="' . $filename . '";' ); + header( 'Content-Transfer-Encoding: binary' ); + + $file_size = @filesize( $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + + if ( ! $file_size ) { + return; + } + + header( 'Content-Length: ' . $file_size ); + + @readfile( $file_path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.WP.AlternativeFunctions.file_system_read_readfile + exit(); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Labels/Factory.php b/packages/woocommerce-germanized-shipments/src/Labels/Factory.php new file mode 100644 index 000000000..49d52f456 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/Factory.php @@ -0,0 +1,91 @@ +get_label_data( $label_id ); + + if ( $label_data ) { + $label_type = $label_data->type; + $shipping_provider_name = $label_data->shipping_provider; + } + } + + $shipping_provider_name = apply_filters( 'woocommerce_gzd_shipment_label_shipping_provider_name', $shipping_provider_name, $label_id, $label_type ); + + if ( ! $shipping_provider = wc_gzd_get_shipping_provider( $shipping_provider_name ) ) { + return false; + } + + /** + * Simple shipping provider do not support labels + */ + if ( ! is_a( $shipping_provider, '\Vendidero\Germanized\Shipments\Interfaces\ShippingProviderAuto' ) ) { + return false; + } + + $classname = $shipping_provider->get_label_classname( $label_type ); + + /** + * Filter that allows adjusting the default DHL label classname. + * + * @param string $classname The classname to be used. + * @param integer $label_id The label id. + * @param string $label_type The label type. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $classname = apply_filters( 'woocommerce_gzd_shipment_label_class', $classname, $label_id, $label_type, $shipping_provider ); + + if ( ! class_exists( $classname ) ) { + return false; + } + + try { + return new $classname( $label_id ); + } catch ( Exception $e ) { + wc_caught_exception( $e, __FUNCTION__, array( $label_id, $shipping_provider_name, $label_type ) ); + return false; + } + } + + public static function get_label_id( $label ) { + if ( is_numeric( $label ) ) { + return $label; + } elseif ( $label instanceof Label ) { + return $label->get_id(); + } elseif ( ! empty( $label->label_id ) ) { + return $label->label_id; + } else { + return false; + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Labels/Label.php b/packages/woocommerce-germanized-shipments/src/Labels/Label.php new file mode 100644 index 000000000..ff2b41ea0 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/Label.php @@ -0,0 +1,870 @@ + null, + 'shipment_id' => 0, + 'product_id' => '', + 'parent_id' => 0, + 'number' => '', + 'shipping_provider' => '', + 'weight' => '', + 'net_weight' => '', + 'length' => '', + 'width' => '', + 'height' => '', + 'path' => '', + 'created_via' => '', + 'services' => array(), + 'print_format' => '', + ); + + public function __construct( $data = 0 ) { + parent::__construct( $data ); + + if ( $data instanceof ShipmentLabel ) { + $this->set_id( absint( $data->get_id() ) ); + } elseif ( is_numeric( $data ) ) { + $this->set_id( $data ); + } + + $this->data_store = WC_Data_Store::load( $this->data_store_name ); + + // If we have an ID, load the user from the DB. + if ( $this->get_id() ) { + try { + $this->data_store->read( $this ); + } catch ( Exception $e ) { + $this->set_id( 0 ); + $this->set_object_read( true ); + } + } else { + $this->set_object_read( true ); + } + } + + public function get_type() { + return 'simple'; + } + + /** + * Merge changes with data and clear. + * Overrides WC_Data::apply_changes. + * array_replace_recursive does not work well for license because it merges domains registered instead + * of replacing them. + * + * @since 3.2.0 + */ + public function apply_changes() { + if ( function_exists( 'array_replace' ) ) { + $this->data = array_replace( $this->data, $this->changes ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_replaceFound + } else { // PHP 5.2 compatibility. + foreach ( $this->changes as $key => $change ) { + $this->data[ $key ] = $change; + } + } + $this->changes = array(); + } + + /** + * Prefix for action and filter hooks on data. + * + * @since 3.0.0 + * @return string + */ + protected function get_general_hook_prefix() { + $prefix = 'simple' === $this->get_type() ? '' : $this->get_type() . '_'; + + return "woocommerce_gzd_shipment_{$prefix}label_"; + } + + /** + * Prefix for action and filter hooks on data. + * + * @since 3.0.0 + * @return string + */ + protected function get_hook_prefix() { + return $this->get_general_hook_prefix() . 'get_'; + } + + /** + * Return the date this license was created. + * + * @since 3.0.0 + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return WC_DateTime|null object if the date is set or null if there is no date. + */ + public function get_date_created( $context = 'view' ) { + return $this->get_prop( 'date_created', $context ); + } + + public function get_shipment_id( $context = 'view' ) { + return $this->get_prop( 'shipment_id', $context ); + } + + public function get_shipping_provider( $context = 'view' ) { + return $this->get_prop( 'shipping_provider', $context ); + } + + public function get_shipping_provider_instance() { + $provider = $this->get_shipping_provider(); + + if ( ! empty( $provider ) ) { + return wc_gzd_get_shipping_provider( $provider ); + } + + return false; + } + + public function get_parent_id( $context = 'view' ) { + return $this->get_prop( 'parent_id', $context ); + } + + public function get_created_via( $context = 'view' ) { + return $this->get_prop( 'created_via', $context ); + } + + public function get_product_id( $context = 'view' ) { + return $this->get_prop( 'product_id', $context ); + } + + public function get_number( $context = 'view' ) { + return $this->get_prop( 'number', $context ); + } + + public function get_print_format( $context = 'view' ) { + return $this->get_prop( 'print_format', $context ); + } + + public function has_number() { + $number = $this->get_number(); + + return empty( $number ) ? false : true; + } + + /** + * Returns the weight in kg + * + * @param string $context + * + * @return string + */ + public function get_weight( $context = 'view' ) { + return $this->get_prop( 'weight', $context ); + } + + public function get_net_weight( $context = 'view' ) { + $weight = $this->get_prop( 'net_weight', $context ); + + if ( 'view' === $context && '' === $weight ) { + $weight = $this->get_weight( $context ); + } + + return $weight; + } + + /** + * Returns the length in cm + * + * @param string $context + * + * @return string + */ + public function get_length( $context = 'view' ) { + return $this->get_prop( 'length', $context ); + } + + /** + * Returns the width in cm + * + * @param string $context + * + * @return string + */ + public function get_width( $context = 'view' ) { + return $this->get_prop( 'width', $context ); + } + + /** + * Returns the height in cm + * + * @param string $context + * + * @return string + */ + public function get_height( $context = 'view' ) { + return $this->get_prop( 'height', $context ); + } + + public function get_dimensions( $context = 'view' ) { + return array( + 'length' => $this->get_length( $context ), + 'width' => $this->get_width( $context ), + 'height' => $this->get_height( $context ), + ); + } + + public function has_dimensions() { + $width = $this->get_width(); + $length = $this->get_length(); + $height = $this->get_height(); + + return ( ! empty( $width ) && ! empty( $length ) && ! empty( $height ) ); + } + + public function get_path( $context = 'view', $file_path = '' ) { + return $this->get_prop( 'path', $context ); + } + + public function get_services( $context = 'view' ) { + return $this->get_prop( 'services', $context ); + } + + public function has_service( $service ) { + return ( in_array( $service, $this->get_services(), true ) ); + } + + /** + * Retrieve additional data for a certain service, e.g. + * retrieve the min_age for the DHL VisualCheckOfAge service. + * + * @param $service + * @param $prop + * @param $default + * + * @return mixed + */ + public function get_service_prop( $service, $prop, $default = null, $context = 'view' ) { + $meta_key = "service_{$service}_{$prop}"; + + if ( $this->get_meta( $meta_key, true, $context ) ) { + return $this->get_meta( $meta_key, true, $context ); + } else { + return $default; + } + } + + public function get_shipment() { + if ( is_null( $this->shipment ) ) { + $this->shipment = ( $this->get_shipment_id() > 0 ? wc_gzd_get_shipment( $this->get_shipment_id() ) : false ); + } + + return $this->shipment; + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set the date this license was last updated. + * + * @since 1.0.0 + * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if their is no date. + */ + public function set_date_created( $date = null ) { + $this->set_date_prop( 'date_created', $date ); + } + + public function set_number( $number ) { + $this->set_prop( 'number', $number ); + } + + public function set_product_id( $number ) { + $this->set_prop( 'product_id', $number ); + } + + public function set_print_format( $format ) { + $this->set_prop( 'print_format', $format ); + } + + public function set_shipping_provider( $slug ) { + $this->set_prop( 'shipping_provider', $slug ); + } + + public function set_parent_id( $id ) { + $this->set_prop( 'parent_id', absint( $id ) ); + } + + public function set_created_via( $created_via ) { + $this->set_prop( 'created_via', $created_via ); + } + + public function set_weight( $weight ) { + $this->set_prop( 'weight', '' !== $weight ? wc_format_decimal( $weight ) : '' ); + } + + public function set_net_weight( $weight ) { + $this->set_prop( 'net_weight', '' !== $weight ? wc_format_decimal( $weight ) : '' ); + } + + public function set_width( $width ) { + $this->set_prop( 'width', '' !== $width ? wc_format_decimal( $width ) : '' ); + } + + public function set_length( $length ) { + $this->set_prop( 'length', '' !== $length ? wc_format_decimal( $length ) : '' ); + } + + public function set_height( $height ) { + $this->set_prop( 'height', '' !== $height ? wc_format_decimal( $height ) : '' ); + } + + public function set_path( $path, $file_type = '' ) { + $this->set_prop( 'path', $path ); + } + + public function set_services( $services ) { + $this->set_prop( 'services', empty( $services ) ? array() : (array) $services ); + } + + /** + * Returns linked children labels. + * + * @return ShipmentLabel[] + */ + public function get_children() { + return wc_gzd_get_shipment_labels( array( 'parent_id' => $this->get_id() ) ); + } + + public function has_children() { + $children = $this->get_children(); + + return count( $children ) > 0 ? true : false; + } + + public function add_service( $service ) { + $services = (array) $this->get_services(); + $available_services = array(); + + if ( $provider = $this->get_shipping_provider_instance() ) { + if ( $shipment = $this->get_shipment() ) { + $available_services = $provider->get_available_label_services( $shipment ); + } + } + + if ( ! in_array( $service, $services, true ) && in_array( $service, $available_services, true ) ) { + $services[] = $service; + $this->set_services( $services ); + + return true; + } + + return false; + } + + public function remove_service( $service ) { + $services = (array) $this->get_services(); + + if ( in_array( $service, $services, true ) ) { + $services = array_diff( $services, array( $service ) ); + + $this->set_services( $services ); + return true; + } + + return false; + } + + public function supports_additional_file_type( $file_type ) { + return in_array( $file_type, $this->get_additional_file_types(), true ); + } + + public function get_additional_file_types() { + return array(); + } + + public function get_file( $file_type = '' ) { + if ( ! $path = $this->get_path( 'view', $file_type ) ) { + return false; + } + + return $this->get_file_by_path( $path ); + } + + protected function get_new_filename( $file_type = '' ) { + $file_parts = array( + $this->get_shipping_provider(), + ); + + if ( ! empty( $file_type ) ) { + $file_parts[] = $file_type; + } + + if ( 'simple' !== $this->get_type() ) { + $file_parts[] = $this->get_type(); + } + + $file_parts[] = $this->get_shipment_id(); + + $filename_default = implode( '-', $file_parts ); + $filename_default = $filename_default . '.pdf'; + $filename = apply_filters( "{$this->get_hook_prefix()}filename", $filename_default, $this, $file_type ); + + return sanitize_file_name( $filename ); + } + + public function get_filename( $file_type = '' ) { + if ( ! $path = $this->get_path( 'view', $file_type ) ) { + return $this->get_new_filename( $file_type ); + } + + return basename( $path ); + } + + protected function get_file_by_path( $file ) { + // If the file is relative, prepend upload dir. + if ( $file && 0 !== strpos( $file, '/' ) && ( ( $uploads = Package::get_upload_dir() ) && false === $uploads['error'] ) ) { + $file = $uploads['basedir'] . "/$file"; + + return $file; + } else { + return false; + } + } + + public function set_shipment_id( $shipment_id ) { + // Reset order object + $this->shipment = null; + + $this->set_prop( 'shipment_id', absint( $shipment_id ) ); + } + + /** + * @param Shipment $shipment + */ + public function set_shipment( &$shipment ) { + $this->shipment = $shipment; + + $this->set_prop( 'shipment_id', absint( $shipment->get_id() ) ); + } + + public function get_download_url( $args = array() ) { + $base_url = is_admin() ? admin_url() : trailingslashit( home_url() ); + $download_url = add_query_arg( + array( + 'action' => 'wc-gzd-download-shipment-label', + 'shipment_id' => $this->get_shipment_id(), + ), + wp_nonce_url( $base_url, 'download-shipment-label' ) + ); + + foreach ( $args as $arg => $val ) { + if ( is_bool( $val ) ) { + $args[ $arg ] = wc_bool_to_string( $val ); + } + } + + $download_url = add_query_arg( $args, $download_url ); + + /** + * Filter for shipping providers to adjust the label download URL. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. `$provider` is related to the current shipping provider + * for the shipment (slug). + * + * Example hook name: `woocommerce_gzd_return_shipment_get_dhl_label_download_url` + * + * @param string $url The download URL. + * @param Label $label The current shipment instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return esc_url_raw( apply_filters( "{$this->get_hook_prefix()}download_url", $download_url, $this ) ); + } + + /** + * @return ShipmentError|true + */ + public function fetch() { + $result = new ShipmentError( 'label-fetch-error', _x( 'This label misses the API implementation', 'shipments', 'woocommerce-germanized' ) ); + + return $result; + } + + /** + * @param $stream + * @param string $file_type + * + * @return false|string + */ + public function upload_label_file( $stream, $file_type = '' ) { + try { + Package::set_upload_dir_filter(); + $filename = $this->get_filename( $file_type ); + + $GLOBALS['gzd_shipments_unique_filename'] = $filename; + add_filter( 'wp_unique_filename', '_wc_gzd_shipments_keep_force_filename', 10, 1 ); + + $tmp = wp_upload_bits( $this->get_filename( $file_type ), null, $stream ); + + unset( $GLOBALS['gzd_shipments_unique_filename'] ); + remove_filter( 'wp_unique_filename', '_wc_gzd_shipments_keep_force_filename', 10 ); + + Package::unset_upload_dir_filter(); + + if ( isset( $tmp['file'] ) ) { + $path = $tmp['file']; + $path = Package::get_relative_upload_dir( $path ); + + $this->set_path( $path, $file_type ); + + return $path; + } else { + throw new Exception( _x( 'Error while uploading label.', 'shipments', 'woocommerce-germanized' ) ); + } + } catch ( Exception $e ) { + return false; + } + } + + /** + * @param $url + * @param string $file_type + * + * @return false|string + */ + public function download_label_file( $url, $file_type = '' ) { + $timeout_seconds = 5; + + try { + if ( ! function_exists( 'download_url' ) ) { + include_once ABSPATH . 'wp-admin/includes/file.php'; + } + + if ( ! function_exists( 'download_url' ) ) { + throw new \Exception( _x( 'Error while downloading the PDF file.', 'shipments', 'woocommerce-germanized' ) ); + } + + // Download file to temp dir. + $temp_file = download_url( $url, $timeout_seconds ); + + if ( is_wp_error( $temp_file ) ) { + throw new \Exception( _x( 'Error while downloading the PDF file.', 'shipments', 'woocommerce-germanized' ) ); + } + + $file = array( + 'name' => $this->get_filename( $file_type ), + 'type' => 'application/pdf', + 'tmp_name' => $temp_file, + 'error' => 0, + 'size' => filesize( $temp_file ), + ); + + $overrides = array( + 'test_type' => false, + 'test_form' => false, + 'test_size' => true, + ); + + // Move the temporary file into the uploads directory. + Package::set_upload_dir_filter(); + $results = wp_handle_sideload( $file, $overrides ); + Package::unset_upload_dir_filter(); + + if ( empty( $results['error'] ) ) { + $path = Package::get_relative_upload_dir( $results['file'] ); + + $this->set_path( $path, $file_type ); + + return $path; + } else { + throw new \Exception( $results['error'] ); + } + } catch ( \Exception $e ) { + Package::log( sprintf( 'Error while downloading label file from URL %1$s: %2$s', esc_url( $url ), $e->getMessage() ), 'error' ); + + return false; + } + } + + public function is_trackable() { + return true; + } + + public function supports_third_party_email_notification() { + $supports_email_notification = false; + + if ( ( $shipment = $this->get_shipment() ) && ( $order = $shipment->get_order() ) ) { + $supports_email_notification = wc_gzd_get_shipment_order( $order )->supports_third_party_email_transmission(); + } + + return apply_filters( "{$this->get_general_hook_prefix()}supports_third_party_email_notification", $supports_email_notification, $this ); + } + + /** + * Gets a prop for a getter method. + * + * @since 3.0.0 + * @param string $prop Name of prop to get. + * @param string $address billing or shipping. + * @param string $context What the value is for. Valid values are view and edit. + * @return mixed + */ + protected function get_address_prop( $prop, $address = 'sender_address', $context = 'view' ) { + $value = null; + + if ( isset( $this->changes[ $address ][ $prop ] ) || isset( $this->data[ $address ][ $prop ] ) ) { + $value = isset( $this->changes[ $address ][ $prop ] ) ? $this->changes[ $address ][ $prop ] : $this->data[ $address ][ $prop ]; + + if ( 'view' === $context ) { + /** + * Filter to adjust a specific address property for a DHL label. + * + * The dynamic portion of the hook name, `$this->get_hook_prefix()` constructs an individual + * hook name which uses `woocommerce_gzd_dhl_label_get_` as a prefix. Additionally + * `$address` contains the current address type e.g. sender_address and `$prop` contains the actual + * property e.g. street. + * + * Example hook name: `woocommerce_gzd_dhl_return_label_get_sender_address_street` + * + * @param string $value The address property value. + * @param \Vendidero\Germanized\DHL\Label\Label $label The label object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $value = apply_filters( "{$this->get_hook_prefix()}{$address}_{$prop}", $value, $this ); + } + } + + return $value; + } + + protected function round_customs_item_weight( $value, $precision = 0 ) { + return \Automattic\WooCommerce\Utilities\NumberUtil::round( $value, $precision, 2 ); + } + + protected function get_per_item_weights( $total_weight, $item_weights, $shipment_items ) { + $item_total_weight = array_sum( $item_weights ); + + /** + * Discrepancies detected between item weights and total shipment weight. + * Try to distribute the mismatch between items. + */ + if ( $item_total_weight != $total_weight ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison + $diff = $total_weight - $item_total_weight; + $diff_abs = abs( $diff ); + + if ( $diff_abs > 0 ) { + $diff_left = $diff_abs; + $item_keys = array_keys( $item_weights ); + $current_item_index = 0; + + /** + * Loop over the diff (one step/g a time) and distribute + * the diff evenly for the items included. Respect min weight (1g). + */ + for ( $i = 0; $i < $diff_left; $i++ ) { + if ( ! isset( $item_keys[ $current_item_index ] ) ) { + $current_item_index = 0; + } + + $current_item_key = $item_keys[ $current_item_index ]; + $shipment_item = $shipment_items[ $current_item_key ]; + $item_min_weight = 1 * $shipment_item->get_quantity(); + $diff_to_apply = $diff / $diff_abs; // apply diff in -1/+1 steps + $new_item_weight = $item_weights[ $current_item_key ] + $diff_to_apply; + + if ( $new_item_weight >= $item_min_weight ) { + $item_weights[ $current_item_key ] = $new_item_weight; + } + + $current_item_index = ( $current_item_index >= count( $item_keys ) ) ? 0 : ( $current_item_index + 1 ); + } + } + } + + return $item_weights; + } + + public function get_customs_data( $max_desc_length = 255 ) { + if ( ! $shipment = $this->get_shipment() ) { + return false; + } + + $customs_items = array(); + $item_description = ''; + $total_weight = (int) ceil( (float) wc_get_weight( $this->get_net_weight(), 'g', 'kg' ) ); + $total_gross_weight = (int) ceil( (float) wc_get_weight( $this->get_weight(), 'g', 'kg' ) ); + $item_weights = array(); + $shipment_items = $shipment->get_items(); + $order = $shipment->get_order(); + + foreach ( $shipment_items as $key => $item ) { + $per_item_weight = (int) ceil( (float) wc_get_weight( $item->get_weight(), 'g', $shipment->get_weight_unit() ) ); + $per_item_weight = $per_item_weight * $item->get_quantity(); + $per_item_min_weight = 1 * $item->get_quantity(); + + /** + * Set min weight to 1g to prevent missing weight error messages. + */ + if ( $per_item_weight < $per_item_min_weight ) { + $per_item_weight = $per_item_min_weight; + } + + $item_weights[ $key ] = $per_item_weight; + } + + $item_weights = $this->get_per_item_weights( $total_weight, $item_weights, $shipment_items ); + $item_gross_weights = $this->get_per_item_weights( $total_gross_weight, $item_weights, $shipment_items ); + + $total_weight = 0; + $total_gross_weight = 0; + $total_value = 0; + $use_subtotal = false; + + if ( $order && apply_filters( 'woocommerce_gzd_shipments_order_has_voucher', false, $order ) ) { + $use_subtotal = true; + } + + $use_subtotal = apply_filters( 'woocommerce_gzd_shipments_customs_use_subtotal', $use_subtotal, $this ); + + foreach ( $shipment->get_items() as $key => $item ) { + $product = $item->get_product(); + + if ( $product ) { + $shipment_product = wc_gzd_shipments_get_product( $product ); + } + + $single_item_description = $item->get_customs_description(); + + $item_description .= ! empty( $item_description ) ? ', ' : ''; + $item_description .= $single_item_description; + + // Use total before discounts for customs + $product_total = (float) ( $use_subtotal ? $item->get_subtotal() : $item->get_total() ) / $item->get_quantity(); + + if ( $product_total < 0.01 ) { + // Use the order item data as fallback + if ( ( $order_item = $item->get_order_item() ) && $order ) { + $order_item_total = $use_subtotal ? $order->get_line_subtotal( $order_item, true, false ) : $order->get_line_total( $order_item, true, false ); + $product_total = (float) $order_item_total / $item->get_quantity(); + } + } + + $category = $shipment_product ? $shipment_product->get_main_category() : $item->get_name(); + + if ( empty( $category ) ) { + $category = $item->get_name(); + } + + $product_value = $product_total < 0.01 ? wc_format_decimal( apply_filters( "{$this->get_general_hook_prefix()}customs_item_min_price", 0.01, $item, $this, $shipment ), 2 ) : wc_format_decimal( $product_total, 2 ); + + $customs_items[ $key ] = apply_filters( + "{$this->get_general_hook_prefix()}customs_item", + array( + 'description' => apply_filters( "{$this->get_general_hook_prefix()}item_description", wc_clean( mb_substr( $single_item_description, 0, $max_desc_length ) ), $item, $this, $shipment ), + 'category' => apply_filters( "{$this->get_general_hook_prefix()}item_category", $category, $item, $this, $shipment ), + 'origin_code' => ( $shipment_product && $shipment_product->get_manufacture_country() ) ? $shipment_product->get_manufacture_country() : Package::get_base_country(), + 'tariff_number' => $shipment_product ? $shipment_product->get_hs_code() : '', + 'quantity' => intval( $item->get_quantity() ), + 'weight_in_kg' => $this->round_customs_item_weight( (float) wc_get_weight( $item_weights[ $key ], 'kg', 'g' ), 3 ), + 'weight_in_g' => $item_weights[ $key ], + 'single_weight_in_kg' => $this->round_customs_item_weight( (float) wc_get_weight( $item_weights[ $key ] / $item->get_quantity(), 'kg', 'g' ), 3 ), + 'single_weight_in_g' => ceil( $item_weights[ $key ] / $item->get_quantity() ), + 'gross_weight_in_kg' => $this->round_customs_item_weight( (float) wc_get_weight( $item_gross_weights[ $key ], 'kg', 'g' ), 3 ), + 'gross_weight_in_g' => $item_gross_weights[ $key ], + 'single_value' => $product_value, + 'value' => wc_format_decimal( $product_value * $item->get_quantity(), 2 ), + ), + $item, + $shipment, + $this + ); + + $total_weight += $customs_items[ $key ]['weight_in_g']; + $total_gross_weight += $customs_items[ $key ]['gross_weight_in_g']; + $total_value += (float) $customs_items[ $key ]['value']; + } + + $item_description = mb_substr( $item_description, 0, $max_desc_length ); + + $customs_data = apply_filters( + "{$this->get_general_hook_prefix()}customs_data", + array( + 'shipment_id' => $shipment->get_id(), + 'additional_fee' => wc_format_decimal( $shipment->get_additional_total(), 2 ), + 'place_of_commital' => $shipment->get_sender_city(), + // e.g. EORI number + 'sender_customs_ref_number' => $shipment->get_sender_customs_reference_number(), + 'receiver_customs_ref_number' => $shipment->get_customs_reference_number(), + // Customs UK VAT ID (HMRC) for totals <= 135 GBP + 'sender_customs_uk_vat_id' => $shipment->get_sender_customs_uk_vat_id(), + 'items' => $customs_items, + 'item_total_weight_in_kg' => $this->round_customs_item_weight( (float) wc_get_weight( $total_weight, 'kg', 'g' ), 3 ), + 'item_total_weight_in_g' => $total_weight, + 'item_total_gross_weight_in_kg' => $this->round_customs_item_weight( (float) wc_get_weight( $total_gross_weight, 'kg', 'g' ), 3 ), + 'item_total_gross_weight_in_g' => $total_gross_weight, + 'item_total_value' => $total_value, + 'currency' => $order ? $order->get_currency() : get_woocommerce_currency(), + 'invoice_number' => '', + 'incoterms' => '', + 'export_type' => '', + 'export_reason_description' => $item_description, + 'export_type_description' => $item_description, + 'export_reason' => '', + ), + $this, + $shipment + ); + + return apply_filters( 'woocommerce_gzd_shipments_label_customs_data', $customs_data, $this, $shipment, $max_desc_length ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Labels/Query.php b/packages/woocommerce-germanized-shipments/src/Labels/Query.php new file mode 100644 index 000000000..8a2d8c8eb --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/Query.php @@ -0,0 +1,490 @@ + 10, + 'shipment_id' => '', + 'product_id' => '', + 'shipping_provider' => '', + 'parent_id' => '', + 'type' => wc_gzd_get_shipment_label_types(), + 'number' => '', + 'order' => 'DESC', + 'orderby' => 'date_created', + 'return' => 'objects', + 'page' => 1, + 'offset' => '', + 'paginate' => false, + 'search' => '', + 'search_columns' => array(), + ); + } + + /** + * Get labels matching the current query vars. + * + * @return Label[] objects + * + * @throws \Exception When WC_Data_Store validation fails. + */ + public function get_labels() { + /** + * Filter to adjust query paramaters for a DHL label query. + * + * @param array $query_vars The query arguments. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + $args = apply_filters( 'woocommerce_gzd_shipment_label_query_args', $this->get_query_vars() ); + $args = WC_Data_Store::load( 'shipment-label' )->get_query_args( $args ); + + $this->query( $args ); + + /** + * Filter to adjust query result data for a DHL label query. + * + * @param Label[] $results The results. + * @param array $args The query arguments. + * + * @since 3.0.0 + * @package Vendidero/Germanized/DHL + */ + return apply_filters( 'woocommerce_gzd_shipment_label_query', $this->results, $args ); + } + + public function get_total() { + return $this->total_labels; + } + + /** + * Query shipments. + * + * @param array $query_args + */ + protected function query( $query_args ) { + global $wpdb; + + $this->args = $query_args; + $this->parse_query(); + $this->prepare_query(); + + $qv =& $this->args; + + $this->results = null; + + if ( null === $this->results ) { + $this->request = "SELECT $this->query_fields $this->query_from $this->query_where $this->query_orderby $this->query_limit"; + + if ( is_array( $qv['fields'] ) || 'objects' === $qv['fields'] ) { + $this->results = $wpdb->get_results( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } else { + $this->results = $wpdb->get_col( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } + + if ( isset( $qv['count_total'] ) && $qv['count_total'] ) { + $found_labels_query = 'SELECT FOUND_ROWS()'; + $this->total_labels = (int) $wpdb->get_var( $found_labels_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } + } + + if ( ! $this->results ) { + return; + } + + if ( 'objects' === $qv['fields'] ) { + foreach ( $this->results as $key => $label ) { + $this->results[ $key ] = wc_gzd_get_shipment_label( $label ); + } + } + } + + /** + * Parse the query before preparing it. + */ + protected function parse_query() { + + if ( isset( $this->args['shipment_id'] ) ) { + $this->args['shipment_id'] = absint( $this->args['shipment_id'] ); + } + + if ( isset( $this->args['shipping_provider'] ) ) { + $this->args['shipping_provider'] = sanitize_key( $this->args['shipping_provider'] ); + } + + if ( isset( $this->args['product_id'] ) ) { + $this->args['product_id'] = wc_clean( $this->args['product_id'] ); + } + + if ( isset( $this->args['parent_id'] ) && ! empty( $this->args['parent_id'] ) ) { + $this->args['parent_id'] = absint( $this->args['parent_id'] ); + } + + if ( isset( $this->args['number'] ) ) { + $this->args['number'] = sanitize_key( $this->args['number'] ); + } + + if ( isset( $this->args['type'] ) ) { + $this->args['type'] = (array) $this->args['type']; + $this->args['type'] = array_map( 'wc_clean', $this->args['type'] ); + } + + if ( isset( $this->args['search'] ) ) { + $this->args['search'] = wc_clean( $this->args['search'] ); + + if ( ! isset( $this->args['search_columns'] ) ) { + $this->args['search_columns'] = array(); + } + } + } + + /** + * Prepare the query for DB usage. + */ + protected function prepare_query() { + global $wpdb; + + if ( is_array( $this->args['fields'] ) ) { + $this->args['fields'] = array_unique( $this->args['fields'] ); + + $this->query_fields = array(); + + foreach ( $this->args['fields'] as $field ) { + $field = 'ID' === $field ? 'label_id' : sanitize_key( $field ); + $this->query_fields[] = "$wpdb->gzd_shipment_labels.$field"; + } + + $this->query_fields = implode( ',', $this->query_fields ); + + } elseif ( 'objects' === $this->args['fields'] ) { + $this->query_fields = "$wpdb->gzd_shipment_labels.*"; + } else { + $this->query_fields = "$wpdb->gzd_shipment_labels.label_id"; + } + + if ( isset( $this->args['count_total'] ) && $this->args['count_total'] ) { + $this->query_fields = 'SQL_CALC_FOUND_ROWS ' . $this->query_fields; + } + + $this->query_from = "FROM $wpdb->gzd_shipment_labels"; + $this->query_where = 'WHERE 1=1'; + + // order id + if ( isset( $this->args['shipment_id'] ) ) { + $this->query_where .= $wpdb->prepare( ' AND label_shipment_id = %d', $this->args['shipment_id'] ); + } + + // parent id + if ( isset( $this->args['parent_id'] ) ) { + $this->query_where .= $wpdb->prepare( ' AND label_parent_id = %d', $this->args['parent_id'] ); + } + + // Number + if ( isset( $this->args['number'] ) ) { + $this->query_where .= $wpdb->prepare( " AND label_number IN ('%s')", $this->args['number'] ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder + } + + // Shipping provider + if ( isset( $this->args['shipping_provider'] ) ) { + $this->query_where .= $wpdb->prepare( " AND label_shipping_provider IN ('%s')", $this->args['shipping_provider'] ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder + } + + // Shipping provider + if ( isset( $this->args['product_id'] ) ) { + $this->query_where .= $wpdb->prepare( " AND label_product_id IN ('%s')", $this->args['product_id'] ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder + } + + // type + if ( isset( $this->args['type'] ) ) { + $types = $this->args['type']; + $p_types = array(); + + foreach ( $types as $type ) { + $p_types[] = $wpdb->prepare( 'label_type = %s', $type ); + } + + $where_type = implode( ' OR ', $p_types ); + + if ( ! empty( $where_type ) ) { + $this->query_where .= " AND ($where_type)"; + } + } + + // Search + $search = ''; + + if ( isset( $this->args['search'] ) ) { + $search = trim( $this->args['search'] ); + } + + if ( $search ) { + + $leading_wild = ( ltrim( $search, '*' ) !== $search ); + $trailing_wild = ( rtrim( $search, '*' ) !== $search ); + + if ( $leading_wild && $trailing_wild ) { + $wild = 'both'; + } elseif ( $leading_wild ) { + $wild = 'leading'; + } elseif ( $trailing_wild ) { + $wild = 'trailing'; + } else { + $wild = false; + } + if ( $wild ) { + $search = trim( $search, '*' ); + } + + $search_columns = array(); + + if ( $this->args['search_columns'] ) { + $search_columns = array_intersect( $this->args['search_columns'], array( 'label_id', 'label_path', 'label_number', 'label_shipment_id', 'label_product_id' ) ); + } + + if ( ! $search_columns ) { + if ( is_numeric( $search ) ) { + $search_columns = array( 'label_id', 'label_shipment_id' ); + } else { + $search_columns = array( 'label_id', 'label_path', 'label_number', 'label_shipment_id', 'label_product_id' ); + } + } + + /** + * Filters the columns to search in a DHL LabelQuery search. + * + * The default columns depend on the search term, and include 'label_id', + * 'label_shipment_id', 'label_path' and 'label_number'. + * + * @since 3.0.0 + * + * @param string[] $search_columns Array of column names to be searched. + * @param string $search Text being searched. + * @param LabelQuery $this The current LabelQuery instance. + * + * @package Vendidero/Germanized/DHL + */ + $search_columns = apply_filters( 'woocommerce_gzd_shipment_label_search_columns', $search_columns, $search, $this ); + + $this->query_where .= $this->get_search_sql( $search, $search_columns, $wild ); + } + + // Parse and sanitize 'include', for use by 'orderby' as well as 'include' below. + if ( ! empty( $this->args['include'] ) ) { + $include = wp_parse_id_list( $this->args['include'] ); + } else { + $include = false; + } + + // Meta query. + $this->meta_query = new WP_Meta_Query(); + $this->meta_query->parse_query_vars( $this->args ); + + if ( ! empty( $this->meta_query->queries ) ) { + $clauses = $this->meta_query->get_sql( 'gzd_shipment_label', $wpdb->gzd_shipment_labels, 'label_id', $this ); + $this->query_from .= $clauses['join']; + $this->query_where .= $clauses['where']; + + if ( $this->meta_query->has_or_relation() ) { + $this->query_fields = 'DISTINCT ' . $this->query_fields; + } + } + + // sorting + $this->args['order'] = isset( $this->args['order'] ) ? strtoupper( $this->args['order'] ) : ''; + $order = $this->parse_order( $this->args['order'] ); + + if ( empty( $this->args['orderby'] ) ) { + // Default order is by 'user_login'. + $ordersby = array( 'date_created' => $order ); + } elseif ( is_array( $this->args['orderby'] ) ) { + $ordersby = $this->args['orderby']; + } else { + // 'orderby' values may be a comma- or space-separated list. + $ordersby = preg_split( '/[,\s]+/', $this->args['orderby'] ); + } + + $orderby_array = array(); + + foreach ( $ordersby as $_key => $_value ) { + if ( ! $_value ) { + continue; + } + + if ( is_int( $_key ) ) { + // Integer key means this is a flat array of 'orderby' fields. + $_orderby = $_value; + $_order = $order; + } else { + // Non-integer key means this the key is the field and the value is ASC/DESC. + $_orderby = $_key; + $_order = $_value; + } + + $parsed = $this->parse_orderby( $_orderby ); + + if ( ! $parsed ) { + continue; + } + + $orderby_array[] = $parsed . ' ' . $this->parse_order( $_order ); + } + + // If no valid clauses were found, order by user_login. + if ( empty( $orderby_array ) ) { + $orderby_array[] = "label_id $order"; + } + + $this->query_orderby = 'ORDER BY ' . implode( ', ', $orderby_array ); + + // limit + if ( isset( $this->args['posts_per_page'] ) && $this->args['posts_per_page'] > 0 ) { + if ( isset( $this->args['offset'] ) ) { + $this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $this->args['offset'], $this->args['posts_per_page'] ); + } else { + $this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $this->args['posts_per_page'] * ( $this->args['page'] - 1 ), $this->args['posts_per_page'] ); + } + } + + if ( ! empty( $include ) ) { + // Sanitized earlier. + $ids = implode( ',', $include ); + $this->query_where .= " AND $wpdb->gzd_shipment_labels.label_id IN ($ids)"; + } elseif ( ! empty( $this->args['exclude'] ) ) { + $ids = implode( ',', wp_parse_id_list( $this->args['exclude'] ) ); + $this->query_where .= " AND $wpdb->gzd_shipment_labels.label_id NOT IN ($ids)"; + } + + // Date queries are allowed for the user_registered field. + if ( ! empty( $this->args['date_query'] ) && is_array( $this->args['date_query'] ) ) { + $date_query = new WP_Date_Query( $this->args['date_query'], 'label_date_created' ); + $this->query_where .= $date_query->get_sql(); + } + } + + /** + * Used internally to generate an SQL string for searching across multiple columns + * + * @since 3.0.6 + * + * @global \wpdb $wpdb WordPress database abstraction object. + * + * @param string $string + * @param array $cols + * @param bool $wild Whether to allow wildcard searches. Default is false for Network Admin, true for single site. + * Single site allows leading and trailing wildcards, Network Admin only trailing. + * @return string + */ + protected function get_search_sql( $string, $cols, $wild = false ) { + global $wpdb; + + $searches = array(); + $leading_wild = ( 'leading' === $wild || 'both' === $wild ) ? '%' : ''; + $trailing_wild = ( 'trailing' === $wild || 'both' === $wild ) ? '%' : ''; + $like = $leading_wild . $wpdb->esc_like( $string ) . $trailing_wild; + + foreach ( $cols as $col ) { + if ( 'ID' === $col ) { + $searches[] = $wpdb->prepare( "$col = %s", $string ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } else { + $searches[] = $wpdb->prepare( "$col LIKE %s", $like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } + } + + return ' AND (' . implode( ' OR ', $searches ) . ')'; + } + + /** + * Parse orderby statement. + * + * @param string $orderby + * @return string + */ + protected function parse_orderby( $orderby ) { + global $wpdb; + + $meta_query_clauses = $this->meta_query->get_clauses(); + + $_orderby = ''; + + if ( in_array( $orderby, array( 'number', 'shipment_id', 'date_created' ), true ) ) { + $_orderby = 'label_' . $orderby; + } elseif ( 'ID' === $orderby || 'id' === $orderby ) { + $_orderby = 'label_id'; + } elseif ( 'meta_value' === $orderby || $this->get( 'meta_key' ) === $orderby ) { + $_orderby = "$wpdb->gzd_shipment_labelmeta.meta_value"; + } elseif ( 'meta_value_num' === $orderby ) { + $_orderby = "$wpdb->gzd_shipment_labelmeta.meta_value+0"; + } elseif ( 'include' === $orderby && ! empty( $this->args['include'] ) ) { + $include = wp_parse_id_list( $this->args['include'] ); + $include_sql = implode( ',', $include ); + $_orderby = "FIELD( $wpdb->gzd_shipment_labels.label_id, $include_sql )"; + } elseif ( isset( $meta_query_clauses[ $orderby ] ) ) { + $meta_clause = $meta_query_clauses[ $orderby ]; + $_orderby = sprintf( 'CAST(%s.meta_value AS %s)', esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) ); + } + + return $_orderby; + } + + /** + * Parse order statement. + * + * @param string $order + * @return string + */ + protected function parse_order( $order ) { + if ( ! is_string( $order ) || empty( $order ) ) { + return 'DESC'; + } + + if ( 'ASC' === strtoupper( $order ) ) { + return 'ASC'; + } else { + return 'DESC'; + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Labels/ReturnLabel.php b/packages/woocommerce-germanized-shipments/src/Labels/ReturnLabel.php new file mode 100644 index 000000000..f9c922670 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/ReturnLabel.php @@ -0,0 +1,171 @@ + array(), + ); + + public function get_type() { + return 'return'; + } + + public function get_sender_address( $context = 'view' ) { + return $this->get_prop( 'sender_address', $context ); + } + + /** + * Gets a prop for a getter method. + * + * @since 3.0.0 + * @param string $prop Name of prop to get. + * @param string $address billing or shipping. + * @param string $context What the value is for. Valid values are view and edit. + * @return mixed + */ + protected function get_sender_address_prop( $prop, $context = 'view' ) { + $value = $this->get_address_prop( $prop, 'sender_address', $context ); + + return $value; + } + + /** + * Returns the sender address first line. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_address_1( $context = 'view' ) { + return $this->get_sender_address_prop( 'address_1', $context ); + } + + public function get_sender_address_2( $context = 'view' ) { + return $this->get_sender_address_prop( 'address_2', $context ); + } + + public function get_sender_address_addition() { + $addition = $this->get_sender_address_2(); + $street_addition = $this->get_sender_street_addition(); + + if ( ! empty( $street_addition ) ) { + $addition = $street_addition . ( ! empty( $addition ) ? ' ' . $addition : '' ); + } + + return trim( $addition ); + } + + public function get_sender_street( $context = 'view' ) { + $sender_address = $this->get_sender_address(); + + if ( array_key_exists( 'street', $sender_address ) ) { + return $this->get_sender_address_prop( 'street', $context ); + } + + $split = wc_gzd_split_shipment_street( $this->{'get_sender_address_1'}() ); + + return $split['street']; + } + + public function get_sender_street_number( $context = 'view' ) { + $sender_address = $this->get_sender_address(); + + if ( array_key_exists( 'street_number', $sender_address ) ) { + return $this->get_sender_address_prop( 'street_number', $context ); + } + + $split = wc_gzd_split_shipment_street( $this->{'get_sender_address_1'}() ); + + return $split['number']; + } + + public function get_sender_street_addition( $context = 'view' ) { + $sender_address = $this->get_sender_address(); + + if ( array_key_exists( 'street_addition', $sender_address ) ) { + return $this->get_sender_address_prop( 'street_addition', $context ); + } + + $split = wc_gzd_split_shipment_street( $this->{'get_sender_address_1'}() ); + + return $split['addition']; + } + + public function get_sender_company( $context = 'view' ) { + return $this->get_sender_address_prop( 'company', $context ); + } + + public function get_sender_name( $context = 'view' ) { + $sender_address = $this->get_sender_address(); + + if ( array_key_exists( 'name', $sender_address ) ) { + return $this->get_sender_address_prop( 'name', $context ); + } + + return ''; + } + + public function get_sender_first_name( $context = 'view' ) { + return $this->get_sender_address_prop( 'first_name', $context ); + } + + public function get_sender_last_name( $context = 'view' ) { + return $this->get_sender_address_prop( 'last_name', $context ); + } + + public function get_sender_formatted_full_name() { + if ( empty( $this->get_sender_first_name() ) ) { + return $this->get_sender_name(); + } + + return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce-germanized' ), $this->get_sender_first_name(), $this->get_sender_last_name() ); + } + + public function get_sender_postcode( $context = 'view' ) { + return $this->get_sender_address_prop( 'postcode', $context ); + } + + public function get_sender_city( $context = 'view' ) { + return $this->get_sender_address_prop( 'city', $context ); + } + + public function get_sender_state( $context = 'view' ) { + return $this->get_sender_address_prop( 'state', $context ); + } + + public function get_sender_country( $context = 'view' ) { + return $this->get_sender_address_prop( 'country', $context ); + } + + public function get_sender_phone( $context = 'view' ) { + return $this->get_sender_address_prop( 'phone', $context ); + } + + public function get_sender_email( $context = 'view' ) { + return $this->get_sender_address_prop( 'email', $context ); + } + + public function set_sender_address( $value ) { + $this->set_prop( 'sender_address', empty( $value ) ? array() : (array) $value ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Order.php b/packages/woocommerce-germanized-shipments/src/Order.php new file mode 100644 index 000000000..8ee12353b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Order.php @@ -0,0 +1,1284 @@ +order = $order; + } + + /** + * Returns the Woo WC_Order original object + * + * @return object|WC_Order + */ + public function get_order() { + return $this->order; + } + + /** + * @return WC_DateTime|null + */ + public function get_date_shipped() { + $date_shipped = $this->get_order()->get_meta( '_date_shipped', true ); + + if ( $date_shipped ) { + try { + $date_shipped = new WC_DateTime( "@{$date_shipped}" ); + + // Set local timezone or offset. + if ( get_option( 'timezone_string' ) ) { + $date_shipped->setTimezone( new DateTimeZone( wc_timezone_string() ) ); + } else { + $date_shipped->set_utc_offset( wc_timezone_offset() ); + } + } catch ( Exception $e ) { + $date_shipped = null; + } + } else { + $date_shipped = null; + } + + return $date_shipped; + } + + public function is_shipped() { + $shipping_status = $this->get_shipping_status(); + + return apply_filters( 'woocommerce_gzd_shipment_order_shipping_status', ( in_array( $shipping_status, array( 'shipped', 'delivered' ), true ) || ( 'partially-delivered' === $shipping_status && ! $this->needs_shipping( array( 'sent_only' => true ) ) ) ), $this ); + } + + public function get_last_tracking_id() { + $tracking_id = ''; + + foreach ( array_reverse( $this->get_simple_shipments( true ) ) as $shipment ) { + if ( ! empty( $shipment->get_tracking_id() ) ) { + $tracking_id = $shipment->get_tracking_id(); + break; + } + } + + return apply_filters( 'woocommerce_gzd_shipment_order_last_tracking_id', $tracking_id, $this ); + } + + public function get_shipping_status() { + $status = 'not-shipped'; + $shipments = $this->get_simple_shipments(); + $all_shipments_delivered = false; + $all_shipments_shipped = false; + + if ( ! empty( $shipments ) ) { + $all_shipments_delivered = true; + $all_shipments_shipped = true; + + foreach ( $shipments as $shipment ) { + if ( ! $shipment->has_status( 'delivered' ) ) { + $all_shipments_delivered = false; + } else { + $status = 'partially-delivered'; + } + + if ( ! $shipment->is_shipped() ) { + $all_shipments_shipped = false; + } elseif ( 'partially-delivered' !== $status ) { + $status = 'partially-shipped'; + } + } + } + + $needs_shipping = $this->needs_shipping( array( 'sent_only' => true ) ); + + if ( $all_shipments_delivered && ! $needs_shipping ) { + $status = 'delivered'; + } elseif ( 'partially-delivered' !== $status && ( $all_shipments_shipped && ! $needs_shipping ) ) { + $status = 'shipped'; + } elseif ( ! in_array( $status, array( 'partially-shipped', 'partially-delivered' ), true ) && ! $needs_shipping ) { + $status = 'no-shipping-needed'; + } + + return apply_filters( 'woocommerce_gzd_shipment_order_shipping_status', $status, $this ); + } + + public function supports_third_party_email_transmission() { + $supports_email_transmission = function_exists( 'wc_gzd_order_supports_parcel_delivery_reminder' ) ? wc_gzd_order_supports_parcel_delivery_reminder( $this->get_order() ) : 'yes' === $this->get_order()->get_meta( '_parcel_delivery_opted_in' ); + + /** + * Filter to adjust whether the email address may be transmitted to third-parties, e.g. + * the shipping provider (via label requests) or not. + * + * @param boolean $supports_email_transmission Whether the order supports email transmission or not. + * @param Order $order The order instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_supports_email_transmission', $supports_email_transmission, $this ); + } + + public function has_shipped_shipments() { + $shipments = $this->get_simple_shipments(); + + foreach ( $shipments as $shipment ) { + if ( $shipment->is_shipped() ) { + return true; + } + } + + return false; + } + + public function get_return_status() { + $status = 'open'; + $shipments = $this->get_return_shipments(); + + if ( ! empty( $shipments ) ) { + foreach ( $shipments as $shipment ) { + if ( $shipment->has_status( 'delivered' ) ) { + $status = 'partially-returned'; + break; + } + } + } + + if ( ! $this->needs_return( array( 'delivered_only' => true ) ) && $this->has_shipped_shipments() ) { + $status = 'returned'; + } + + return $status; + } + + public function get_default_return_shipping_provider() { + $default_provider_instance = wc_gzd_get_order_shipping_provider( $this->get_order() ); + $default_provider = $default_provider_instance ? $default_provider_instance->get_name() : ''; + $shipments = $this->get_simple_shipments(); + + foreach ( $shipments as $shipment ) { + if ( $shipment->is_shipped() ) { + $default_provider = $shipment->get_shipping_provider(); + } + } + + return apply_filters( 'woocommerce_gzd_shipment_order_return_default_shipping_provider', $default_provider, $this ); + } + + protected function get_package_data() { + if ( is_null( $this->package_data ) ) { + $items = $this->get_available_items_for_shipment(); + $package_data = array( + 'total' => 0.0, + 'subtotal' => 0.0, + 'weight' => 0.0, + 'volume' => 0.0, + 'products' => array(), + 'shipping_classes' => array(), + 'item_count' => 0, + 'items' => new ItemList(), + ); + + foreach ( $items as $order_item_id => $item ) { + if ( ! $order_item = $this->get_order()->get_item( $order_item_id ) ) { + continue; + } + + $line_total = (float) $order_item->get_total(); + $line_subtotal = (float) $order_item->get_subtotal(); + + if ( $this->get_order()->get_prices_include_tax() ) { + $line_total += (float) $order_item->get_total_tax(); + $line_subtotal += (float) $order_item->get_subtotal_tax(); + } + + $quantity = (int) $item['max_quantity']; + + if ( $product = $order_item->get_product() ) { + $width = ( empty( $product->get_width() ) ? 0 : wc_format_decimal( $product->get_width() ) ) * $quantity; + $length = ( empty( $product->get_length() ) ? 0 : wc_format_decimal( $product->get_length() ) ) * $quantity; + $height = ( empty( $product->get_height() ) ? 0 : wc_format_decimal( $product->get_height() ) ) * $quantity; + $weight = ( empty( $product->get_weight() ) ? 0 : wc_format_decimal( $product->get_weight() ) ) * $quantity; + + $package_data['weight'] += $weight; + $package_data['volume'] += ( $width * $length * $height ); + + if ( $product && ! array_key_exists( $product->get_id(), $package_data['products'] ) ) { + $package_data['products'][ $product->get_id() ] = $product; + + if ( ! empty( $product->get_shipping_class_id() ) ) { + $package_data['shipping_classes'][] = $product->get_shipping_class_id(); + } + } + } + + $package_data['total'] += $line_total; + $package_data['subtotal'] += $line_subtotal; + $package_data['item_count'] += $quantity; + + $box_item = new Packing\OrderItem( $order_item ); + $package_data['items']->insert( $box_item, $quantity ); + } + + $this->package_data = $package_data; + } + + return $this->package_data; + } + + /** + * Create shipments (if needed) based on current packing configuration. + * + * @param string $default_status + * + * @return array|\WP_Error + */ + public function create_shipments( $default_status = 'processing' ) { + $shipments_created = array(); + $errors = new \WP_Error(); + + if ( $this->needs_shipping() ) { + if ( $this->has_auto_packing() ) { + if ( $method = $this->get_builtin_shipping_method() ) { + $packaging_boxes = $method->get_method()->get_available_packaging_boxes( $this->get_package_data() ); + } else { + $available_packaging = wc_gzd_get_packaging_list(); + + if ( $provider = wc_gzd_get_order_shipping_provider( $this ) ) { + $available_packaging = wc_gzd_get_packaging_list( array( 'shipping_provider' => $provider->get_name() ) ); + } + + $packaging_boxes = Helper::get_packaging_boxes( $available_packaging ); + } + + $items = $this->get_items_to_pack_left_for_shipping(); + $packed_boxes = Helper::pack( $items, $packaging_boxes, 'order' ); + + if ( empty( $packaging_boxes ) && 0 === count( $packed_boxes ) ) { + $shipment = wc_gzd_create_shipment( $this, array( 'props' => array( 'status' => $default_status ) ) ); + + if ( ! is_wp_error( $shipment ) ) { + $this->add_shipment( $shipment ); + $shipments_created[ $shipment->get_id() ] = $shipment; + } else { + foreach ( $shipment->get_error_messages() as $code => $message ) { + $errors->add( $code, $message ); + } + } + } else { + if ( 0 === count( $packed_boxes ) ) { + $errors->add( 404, sprintf( _x( 'Seems like none of your packaging options is available for this order.', 'shipments', 'woocommerce-germanized' ), Settings::get_settings_url( 'packaging' ) ) ); + } else { + foreach ( $packed_boxes as $box ) { + $packaging = $box->getBox(); + $items = $box->getItems(); + $shipment_items = array(); + + foreach ( $items as $item ) { + $order_item = $item->getItem(); + + if ( ! isset( $shipment_items[ $order_item->get_id() ] ) ) { + $shipment_items[ $order_item->get_id() ] = 1; + } else { + $shipment_items[ $order_item->get_id() ]++; + } + } + + $shipment = wc_gzd_create_shipment( + $this, + array( + 'items' => $shipment_items, + 'props' => array( + 'packaging_id' => $packaging->get_id(), + 'status' => $default_status, + ), + ) + ); + + if ( ! is_wp_error( $shipment ) ) { + $this->add_shipment( $shipment ); + + $shipments_created[ $shipment->get_id() ] = $shipment; + } else { + foreach ( $shipments_created as $id => $shipment_created ) { + $shipment_created->delete( true ); + $this->remove_shipment( $id ); + } + + foreach ( $shipment->get_error_messages() as $code => $message ) { + $errors->add( $code, $message ); + } + } + } + } + } + } else { + $shipment = wc_gzd_create_shipment( $this, array( 'props' => array( 'status' => $default_status ) ) ); + + if ( ! is_wp_error( $shipment ) ) { + $this->add_shipment( $shipment ); + $shipments_created[ $shipment->get_id() ] = $shipment; + } else { + foreach ( $shipment->get_error_messages() as $code => $message ) { + $errors->add( $code, $message ); + } + } + } + } + + if ( wc_gzd_shipment_wp_error_has_errors( $errors ) ) { + return $errors; + } else { + $this->save(); + } + + return $shipments_created; + } + + public function validate_shipments( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'save' => true, + ) + ); + + foreach ( $this->get_simple_shipments() as $shipment ) { + if ( $shipment->is_editable() ) { + + // Make sure we are working based on the current instance. + $shipment->set_order_shipment( $this ); + $shipment->sync(); + + $this->validate_shipment_item_quantities( $shipment->get_id() ); + } + } + + if ( $args['save'] ) { + $this->save(); + } + } + + /** + * @param Shipment $shipment + * + * @return float + */ + public function calculate_shipment_additional_total( $shipment ) { + $fees_total = 0.0; + + foreach ( $this->get_order()->get_fees() as $item ) { + $fees_total += ( (float) $item->get_total() + (float) $item->get_total_tax() ); + } + + $additional_total = $fees_total + (float) $this->get_order()->get_shipping_total() + (float) $this->get_order()->get_shipping_tax(); + + foreach ( $this->get_simple_shipments() as $simple_shipment ) { + if ( $shipment->get_id() === $simple_shipment->get_id() ) { + continue; + } + + $additional_total -= (float) $simple_shipment->get_additional_total(); + } + + $additional_total = wc_format_decimal( $additional_total, '' ); + + if ( (float) $additional_total < 0.0 ) { + $additional_total = 0.0; + } + + return $additional_total; + } + + public function validate_shipment_item_quantities( $shipment_id = false ) { + $shipment = $shipment_id ? $this->get_shipment( $shipment_id ) : false; + $shipments = ( $shipment_id && $shipment ) ? array( $shipment ) : $this->get_simple_shipments(); + $order_items = $this->get_shippable_items(); + + foreach ( $shipments as $shipment ) { + + if ( ! is_a( $shipment, 'Vendidero\Germanized\Shipments\Shipment' ) ) { + continue; + } + + // Do only check draft shipments + if ( $shipment->is_editable() ) { + foreach ( $shipment->get_items() as $item ) { + + // Order item does not exist + if ( ! isset( $order_items[ $item->get_order_item_id() ] ) ) { + + /** + * Filter to decide whether to keep non-existing OrderItems within + * the Shipment while validating or not. + * + * @param boolean $keep Whether to keep non-existing OrderItems or not. + * @param ShipmentItem $item The shipment item object. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + if ( ! apply_filters( 'woocommerce_gzd_shipment_order_keep_non_order_item', false, $item, $shipment ) ) { + $shipment->remove_item( $item->get_id() ); + } + + continue; + } + + $order_item = $order_items[ $item->get_order_item_id() ]; + $quantity = $this->get_item_quantity_left_for_shipping( + $order_item, + array( + 'shipment_id' => $shipment->get_id(), + 'exclude_current_shipment' => true, + ) + ); + + if ( $quantity <= 0 ) { + $shipment->remove_item( $item->get_id() ); + } else { + $new_quantity = absint( $item->get_quantity() ); + + if ( $item->get_quantity() > $quantity ) { + $new_quantity = $quantity; + } + + $item->sync( array( 'quantity' => $new_quantity ) ); + } + } + + if ( empty( $shipment->get_items() ) ) { + $this->remove_shipment( $shipment->get_id() ); + } + } + } + } + + /** + * @return Shipment[] Shipments + */ + public function get_shipments() { + if ( is_null( $this->shipments ) ) { + $this->shipments = wc_gzd_get_shipments( + array( + 'order_id' => $this->get_order()->get_id(), + 'limit' => -1, + 'orderby' => 'date_created', + 'type' => array( 'simple', 'return' ), + 'order' => 'ASC', + ) + ); + } + + $shipments = (array) $this->shipments; + + return $shipments; + } + + /** + * @return SimpleShipment[] + */ + public function get_simple_shipments( $shipped_only = false ) { + $simple = array(); + + foreach ( $this->get_shipments() as $shipment ) { + if ( 'simple' === $shipment->get_type() ) { + if ( $shipped_only && ! $shipment->is_shipped() ) { + continue; + } + + $simple[] = $shipment; + } + } + + return $simple; + } + + /** + * @return ReturnShipment[] + */ + public function get_return_shipments() { + $returns = array(); + + foreach ( $this->get_shipments() as $shipment ) { + if ( 'return' === $shipment->get_type() ) { + $returns[] = $shipment; + } + } + + return $returns; + } + + public function add_shipment( &$shipment ) { + $this->package_data = null; + + $shipments = $this->get_shipments(); + + $this->shipments[] = $shipment; + } + + public function remove_shipment( $shipment_id ) { + $this->package_data = null; + $shipments = $this->get_shipments(); + + foreach ( $this->shipments as $key => $shipment ) { + if ( $shipment->get_id() === (int) $shipment_id ) { + $this->shipments_to_delete[] = $shipment; + + unset( $this->shipments[ $key ] ); + break; + } + } + } + + /** + * @param $shipment_id + * + * @return bool|SimpleShipment|ReturnShipment + */ + public function get_shipment( $shipment_id ) { + $shipments = $this->get_shipments(); + + foreach ( $shipments as $shipment ) { + + if ( $shipment->get_id() === (int) $shipment_id ) { + return $shipment; + } + } + + return false; + } + + /** + * @param WC_Order_Item $order_item + */ + public function get_item_quantity_left_for_shipping( $order_item, $args = array() ) { + $quantity_left = 0; + $args = wp_parse_args( + $args, + array( + 'sent_only' => false, + 'shipment_id' => 0, + 'exclude_current_shipment' => false, + ) + ); + + if ( is_numeric( $order_item ) ) { + $order_item = $this->get_order()->get_item( $order_item ); + } + + if ( $order_item ) { + $quantity_left = $this->get_shippable_item_quantity( $order_item ); + + foreach ( $this->get_shipments() as $shipment ) { + if ( $args['sent_only'] && ! $shipment->is_shipped() ) { + continue; + } + + if ( $args['exclude_current_shipment'] && $args['shipment_id'] > 0 && ( $shipment->get_id() === $args['shipment_id'] ) ) { + continue; + } + + if ( $item = $shipment->get_item_by_order_item_id( $order_item->get_id() ) ) { + if ( 'return' === $shipment->get_type() ) { + if ( ! $args['sent_only'] && $shipment->is_shipped() ) { + $quantity_left += absint( $item->get_quantity() ); + } + } else { + $quantity_left -= absint( $item->get_quantity() ); + } + } + } + } + + if ( $quantity_left < 0 ) { + $quantity_left = 0; + } + + /** + * Filter to adjust the quantity left for shipment of a specific order item. + * + * @param integer $quantity_left The quantity left for shipment. + * @param WC_Order_Item $order_item The order item object. + * @param Order $this The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_item_quantity_left_for_shipping', $quantity_left, $order_item, $this ); + } + + public function get_item_quantity_sent_by_order_item_id( $order_item_id ) { + $shipments = $this->get_simple_shipments(); + $quantity = 0; + + foreach ( $shipments as $shipment ) { + + if ( ! $shipment->is_shipped() ) { + continue; + } + + if ( $item = $shipment->get_item_by_order_item_id( $order_item_id ) ) { + $quantity += absint( $item->get_quantity() ); + } + } + + return $quantity; + } + + public function order_item_is_non_returnable( $order_item_id ) { + $is_non_returnable = false; + $order_item = is_a( $order_item_id, 'WC_Order_Item' ) ? $order_item_id : $this->get_order()->get_item( $order_item_id ); + + if ( $order_item ) { + if ( is_callable( array( $order_item, 'get_product' ) ) ) { + if ( $product = $order_item->get_product() ) { + $is_non_returnable = wc_gzd_shipments_get_product( $product )->is_non_returnable(); + } + } + } + + return apply_filters( 'woocommerce_gzd_shipment_order_item_is_non_returnable', $is_non_returnable, $order_item_id, $this ); + } + + /** + * @param ShipmentItem $item + */ + public function get_item_quantity_left_for_returning( $order_item_id, $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'delivered_only' => false, + 'shipment_id' => 0, + 'exclude_current_shipment' => false, + ) + ); + + $quantity_left = $this->get_item_quantity_sent_by_order_item_id( $order_item_id ); + + if ( $this->order_item_is_non_returnable( $order_item_id ) ) { + $quantity_left = 0; + } + + foreach ( $this->get_return_shipments() as $shipment ) { + if ( $args['delivered_only'] && ! $shipment->has_status( 'delivered' ) ) { + continue; + } + + if ( $args['exclude_current_shipment'] && $args['shipment_id'] > 0 && ( $shipment->get_id() === $args['shipment_id'] ) ) { + continue; + } + + if ( $shipment_item = $shipment->get_item_by_order_item_id( $order_item_id ) ) { + $quantity_left -= absint( $shipment_item->get_quantity() ); + } + } + + if ( $quantity_left < 0 ) { + $quantity_left = 0; + } + + /** + * Filter to adjust the quantity left for returning of a specific order item. + * + * @param integer $quantity_left The quantity left for shipment. + * @param integer $order_item_id The order item id. + * @param Order $this The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_item_quantity_left_for_returning', $quantity_left, $order_item_id, $this ); + } + + /** + * @param false $legacy_group_by_product_group + * + * @return ItemList|OrderItem[] + */ + public function get_items_to_pack_left_for_shipping( $legacy_group_by_product_group = null ) { + $items_to_be_packed = ! is_null( $legacy_group_by_product_group ) ? array() : $this->get_package_data()['items']; + + if ( ! is_null( $legacy_group_by_product_group ) ) { + foreach ( $this->get_available_items_for_shipment() as $order_item_id => $item ) { + if ( ! $order_item = $this->get_order()->get_item( $order_item_id ) ) { + continue; + } + + $box_item = new Packing\OrderItem( $order_item ); + + $product_group = ''; + + if ( $product = $order_item->get_product() ) { + $product_group = ''; + + if ( 'yes' === get_option( 'woocommerce_gzd_shipments_packing_group_by_shipping_class' ) ) { + $product_group = $product->get_shipping_class(); + } + } + + if ( ! array_key_exists( $product_group, $items_to_be_packed ) ) { + $items_to_be_packed[ $product_group ] = new ItemList(); + } + + $items_to_be_packed[ $product_group ]->insert( $box_item, $item['max_quantity'] ); + } + } + + return apply_filters( 'woocommerce_gzd_shipment_order_items_to_pack_left_for_shipping', $items_to_be_packed ); + } + + /** + * @param bool|Shipment $shipment + * @return array + */ + public function get_available_items_for_shipment( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'disable_duplicates' => false, + 'shipment_id' => 0, + 'sent_only' => false, + 'exclude_current_shipment' => false, + ) + ); + + $items = array(); + $shipment = $args['shipment_id'] ? $this->get_shipment( $args['shipment_id'] ) : false; + + foreach ( $this->get_shippable_items() as $item ) { + $quantity_left = $this->get_item_quantity_left_for_shipping( $item, $args ); + + if ( $shipment ) { + if ( $args['disable_duplicates'] && $shipment->get_item_by_order_item_id( $item->get_id() ) ) { + continue; + } + } + + if ( $quantity_left > 0 ) { + $sku = ''; + + if ( is_callable( array( $item, 'get_product' ) ) ) { + if ( $product = $item->get_product() ) { + $sku = $product->get_sku(); + } + } + + $items[ $item->get_id() ] = array( + 'name' => $item->get_name() . ( ! empty( $sku ) ? ' (' . esc_html( $sku ) . ')' : '' ), + 'max_quantity' => $quantity_left, + ); + } + } + + return $items; + } + + /** + * Returns the first found matching shipment item for a certain order item id. + * + * @param $order_item_id + * + * @return bool|ShipmentItem + */ + public function get_simple_shipment_item( $order_item_id ) { + foreach ( $this->get_simple_shipments() as $shipment ) { + + if ( $item = $shipment->get_item_by_order_item_id( $order_item_id ) ) { + return $item; + } + } + + return false; + } + + public function get_non_returnable_items() { + $items = array(); + + foreach ( $this->get_returnable_items() as $item ) { + if ( $this->order_item_is_non_returnable( $item->get_order_item_id() ) ) { + $sku = $item->get_sku(); + + $items[ $item->get_order_item_id() ] = array( + 'name' => $item->get_name() . ( ! empty( $sku ) ? ' (' . esc_html( $sku ) . ')' : '' ), + 'max_quantity' => 0, + ); + } + } + + return $items; + } + + /** + * @return array + */ + public function get_available_items_for_return( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'disable_duplicates' => false, + 'shipment_id' => 0, + 'delivered_only' => false, + 'exclude_current_shipment' => false, + ) + ); + + $items = array(); + $shipment = $args['shipment_id'] ? $this->get_shipment( $args['shipment_id'] ) : false; + + foreach ( $this->get_returnable_items() as $item ) { + $quantity_left = $this->get_item_quantity_left_for_returning( $item->get_order_item_id(), $args ); + + if ( $shipment ) { + if ( $args['disable_duplicates'] && $shipment->get_item_by_order_item_id( $item->get_order_item_id() ) ) { + continue; + } + } + + if ( $quantity_left > 0 ) { + $sku = $item->get_sku(); + + $items[ $item->get_order_item_id() ] = array( + 'name' => $item->get_name() . ( ! empty( $sku ) ? ' (' . esc_html( $sku ) . ')' : '' ), + 'max_quantity' => $quantity_left, + ); + } + } + + return $items; + } + + public function item_needs_shipping( $order_item, $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'sent_only' => false, + ) + ); + + $needs_shipping = false; + + if ( $this->get_item_quantity_left_for_shipping( $order_item, $args ) > 0 ) { + $needs_shipping = true; + } + + /** + * Filter to decide whether an order item needs shipping or not. + * + * @param boolean $needs_shipping Whether the item needs shipping or not. + * @param WC_Order_Item $item The order item object. + * @param array $args Additional arguments to be considered. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_item_needs_shipping', $needs_shipping, $order_item, $args, $this ); + } + + /** + * Checks whether an item needs return or not by checking the quantity left for return. + * + * @param ShipmentItem $item + * @param array $args + * + * @return mixed|void + */ + public function item_needs_return( $item, $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'delivered_only' => false, + ) + ); + + $needs_return = false; + + if ( $this->get_item_quantity_left_for_returning( $item->get_order_item_id(), $args ) > 0 ) { + $needs_return = true; + } + + /** + * Filter to decide whether a shipment item needs return or not. + * + * @param boolean $needs_return Whether the item needs return or not. + * @param ShipmentItem $item The order item object. + * @param array $args Additional arguments to be considered. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_item_needs_return', $needs_return, $item, $args, $this ); + } + + /** + * Returns the return request key added to allow a guest customer to add + * a new return request to a certain order. + * + * @return mixed + */ + public function get_order_return_request_key() { + return $this->get_order()->get_meta( '_return_request_key' ); + } + + /** + * Removes the return request key from the order. Saves the order. + */ + public function delete_order_return_request_key() { + $this->get_order()->delete_meta_data( '_return_request_key' ); + $this->get_order()->save(); + } + + /** + * Returns items that are ready for shipping (defaults to non-virtual line items). + * + * @return WC_Order_Item[] Shippable items. + */ + public function get_shippable_items() { + $items = $this->get_order()->get_items( 'line_item' ); + + foreach ( $items as $key => $item ) { + $product = is_callable( array( $item, 'get_product' ) ) ? $item->get_product() : false; + + if ( $product ) { + if ( $product->is_virtual() || $this->get_shippable_item_quantity( $item ) <= 0 ) { + unset( $items[ $key ] ); + } + } + } + + $items = array_filter( $items ); + + /** + * Filter to adjust shippable order items for a specific order. + * By default excludes virtual items. + * + * @param WC_Order_Item[] $items Array containing shippable order items. + * @param WC_Order $order The order object. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_shippable_items', $items, $this->get_order(), $this ); + } + + /** + * Returns items that are ready for return. By default only shipped (or delivered) items are returnable. + * + * @return ShipmentItem[] Shippable items. + */ + public function get_returnable_items() { + $items = array(); + + foreach ( $this->get_simple_shipments() as $shipment ) { + if ( ! $shipment->is_shipped() ) { + continue; + } + + foreach ( $shipment->get_items() as $item ) { + if ( $this->order_item_is_non_returnable( $item->get_order_item_id() ) ) { + continue; + } + + if ( ! isset( $items[ $item->get_order_item_id() ] ) ) { + $new_item = clone $item; + $items[ $item->get_order_item_id() ] = $new_item; + } else { + $new_quantity = absint( $items[ $item->get_order_item_id() ]->get_quantity() ) + absint( $item->get_quantity() ); + $items[ $item->get_order_item_id() ]->set_quantity( $new_quantity ); + } + } + } + + /** + * Filter to adjust returnable items for a specific order. + * + * @param ShipmentItem[] $items Array containing shippable order items. + * @param WC_Order $order The order object. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_returnable_items', $items, $this->get_order(), $this ); + } + + public function get_shippable_item_quantity( $order_item ) { + $refunded_qty = absint( $this->get_order()->get_qty_refunded_for_item( $order_item->get_id() ) ); + + // Make sure we are safe to substract quantity for logical purposes + if ( $refunded_qty < 0 ) { + $refunded_qty *= -1; + } + + $quantity_left = absint( $order_item->get_quantity() ) - $refunded_qty; + + /** + * Filter that allows adjusting the quantity left for shipping or a specific order item. + * + * @param integer $quantity_left The quantity left for shipping. + * @param WC_Order_Item $item The order item object. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_item_shippable_quantity', $quantity_left, $order_item, $this ); + } + + /** + * Returns the total number of shippable items. + * + * @return mixed|void + */ + public function get_shippable_item_count() { + $count = 0; + + foreach ( $this->get_shippable_items() as $item ) { + $count += $this->get_shippable_item_quantity( $item ); + } + + /** + * Filters the total number of shippable items available in an order. + * + * @param integer $count The total number of items. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_shippable_item_count', $count, $this ); + } + + /** + * Returns the number of total returnable items. + * + * @return mixed|void + */ + public function get_returnable_item_count() { + $count = 0; + + foreach ( $this->get_returnable_items() as $item ) { + $count += absint( $item->get_quantity() ); + } + + /** + * Filters the total number of returnable items available in an order. + * + * @param integer $count The total number of items. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_returnable_item_count', $count, $this ); + } + + protected function has_local_pickup() { + $shipping_methods = $this->get_order()->get_shipping_methods(); + $has_pickup = false; + + /** + * Filters which shipping methods are considered local pickup method + * which by default do not require shipment. + * + * @param string[] $pickup_methods Array of local pickup shipping method ids. + * + * @since 3.1.6 + * @package Vendidero/Germanized/Shipments + */ + $pickup_methods = apply_filters( 'woocommerce_gzd_shipment_local_pickup_shipping_methods', array( 'local_pickup' ) ); + + foreach ( $shipping_methods as $shipping_method ) { + if ( in_array( $shipping_method->get_method_id(), $pickup_methods, true ) ) { + $has_pickup = true; + break; + } + } + + return $has_pickup; + } + + /** + * @return ProviderMethod|false + */ + public function get_builtin_shipping_method() { + $method = false; + + if ( Package::is_packing_supported() ) { + $shipping_method_id = wc_gzd_get_shipment_order_shipping_method_id( $this->get_order() ); + + if ( 'shipping_provider_' === substr( $shipping_method_id, 0, 18 ) ) { + if ( $method = MethodHelper::get_provider_method( $shipping_method_id ) ) { + return $method; + } + } + } + + return $method; + } + + public function has_auto_packing() { + $has_auto_packing = false; + + if ( Package::is_packing_supported() ) { + $has_auto_packing = Helper::enable_auto_packing(); + + if ( ! $has_auto_packing ) { + if ( self::get_builtin_shipping_method() ) { + $has_auto_packing = true; + } + } + } + + return apply_filters( 'woocommerce_gzd_shipment_order_has_auto_packing', $has_auto_packing, $this->get_order(), $this ); + } + + /** + * Checks whether the order needs shipping or not by checking quantity + * for every line item. + * + * @param bool $sent_only Whether to only include shipments treated as sent or not. + * + * @return bool Whether the order needs shipping or not. + */ + public function needs_shipping( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'sent_only' => false, + ) + ); + + $order_items = $this->get_shippable_items(); + $needs_shipping = false; + $has_pickup = $this->has_local_pickup(); + + if ( ! $has_pickup ) { + foreach ( $order_items as $order_item ) { + if ( $this->item_needs_shipping( $order_item, $args ) ) { + $needs_shipping = true; + break; + } + } + } + + /** + * Filter to decide whether an order needs shipping or not. + * + * @param boolean $needs_shipping Whether the order needs shipping or not. + * @param WC_Order $order The order object. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_needs_shipping', $needs_shipping, $this->get_order(), $this ); + } + + /** + * Checks whether the order needs return or not by checking quantity + * for every line item. + * + * @return bool Whether the order needs shipping or not. + */ + public function needs_return( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'delivered_only' => false, + ) + ); + + $items = $this->get_returnable_items(); + $needs_return = false; + + foreach ( $items as $item ) { + if ( $this->item_needs_return( $item, $args ) ) { + $needs_return = true; + break; + } + } + + /** + * Filter to decide whether an order needs return or not. + * + * @param boolean $needs_return Whether the order needs return or not. + * @param WC_Order $order The order object. + * @param Order $order The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_order_needs_return', $needs_return, $this->get_order(), $this ); + } + + public function save() { + if ( ! empty( $this->shipments_to_delete ) ) { + foreach ( $this->shipments_to_delete as $shipment ) { + $shipment->delete( true ); + } + } + + foreach ( $this->shipments as $shipment ) { + $shipment->save(); + } + + $this->package_data = null; + } + + /** + * Call child methods if the method does not exist. + * + * @param $method + * @param $args + * + * @return bool|mixed + */ + public function __call( $method, $args ) { + if ( method_exists( $this->order, $method ) ) { + return call_user_func_array( array( $this->order, $method ), $args ); + } + + return false; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/PDFMerger.php b/packages/woocommerce-germanized-shipments/src/PDFMerger.php new file mode 100644 index 000000000..41131dfc2 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/PDFMerger.php @@ -0,0 +1,142 @@ +_pdf = new Fpdi(); + } + + /** + * Add file to this pdf + * + * @param string $filename Filename of the source file + * @param mixed $pages Range of files (if not set, all pages where imported) + */ + public function add( $filename, $pages = array(), $width = 210 ) { + if ( file_exists( $filename ) ) { + $page_count = $this->_pdf->setSourceFile( $filename ); + + for ( $i = 1; $i <= $page_count; $i ++ ) { + if ( $this->_isPageInRange( $i, $pages ) ) { + $this->_addPage( $i, $width ); + } + } + } + + return $this; + } + + /** + * Output merged pdf + * + * @param string $type + */ + public function output( $filename, $type = 'I' ) { + return $this->_pdf->Output( $type, $filename ); + } + + /** + * Force download merged pdf as file + * + * @param $filename + * + * @return string + */ + public function download( $filename ) { + return $this->output( $filename, 'D' ); + } + + /** + * Save merged pdf + * + * @param $filename + * + * @return string + */ + public function save( $filename ) { + return $this->output( $filename, 'F' ); + } + + /** + * Add single page + * + * @param $page_number + * + * @throws PdfReaderException + */ + private function _addPage( $page_number, $width = 210 ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore,WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + $page_id = $this->_pdf->importPage( $page_number ); + $size = $this->_pdf->getTemplateSize( $page_id ); + + $orientation = isset( $size['orientation'] ) ? $size['orientation'] : ''; + + $this->_pdf->addPage( $orientation, $size ); + + if ( ! isset( $size['width'] ) || empty( $size['width'] ) ) { + $this->_pdf->useImportedPage( $page_id, 0, 0, $width, null, true ); + } else { + $this->_pdf->useImportedPage( $page_id ); + } + } + + + /** + * Check if a specific page should be merged. + * If pages are empty, all pages will be merged + * + * @return bool + */ + private function _isPageInRange( $page_number, $pages = array() ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore,WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + if ( empty( $pages ) ) { + return true; + } + + foreach ( $pages as $range ) { + if ( in_array( $page_number, $this->_getRange( $range ), true ) ) { + return true; + } + } + + return false; + } + + + /** + * Get range by given value + * + * @param mixed $value + * + * @return array + */ + private function _getRange( $value = null ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore,WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + $value = preg_replace( '/[^0-9\-.]/is', '', $value ); + + if ( '' === $value ) { + return false; + } + + $value = explode( '-', $value ); + + if ( 1 === count( $value ) ) { + return $value; + } + + return range( $value[0] > $value[1] ? $value[1] : $value[0], $value[0] > $value[1] ? $value[0] : $value[1] ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/PDFSplitter.php b/packages/woocommerce-germanized-shipments/src/PDFSplitter.php new file mode 100644 index 000000000..ef518b1dd --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/PDFSplitter.php @@ -0,0 +1,177 @@ +_pdf = new Fpdi(); + + try { + + if ( $stream ) { + $file = StreamReader::createByString( $file ); + $this->filename = $filename; + } else { + $this->filename = basename( $this->file ); + } + + $this->file = $file; + $this->pagecount = $this->_pdf->setSourceFile( $file ); // How many pages? + + } catch ( PdfParserException $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } + } + + public function get_page_count() { + return $this->pagecount; + } + + public function split() { + $new_files = array(); + + try { + // Split each page into a new PDF + for ( $i = 1; $i <= $this->pagecount; $i++ ) { + + $new_pdf = new Fpdi(); + $new_pdf->AddPage(); + $new_pdf->setSourceFile( $this->file ); + $new_pdf->useTemplate( $new_pdf->importPage( $i ), 0, 0, 210, null, true ); + + $new_files[] = $new_pdf->Output( 'S', $this->filename ); + } + } catch ( PdfParserException $e ) { + return false; + } + + return $new_files; + } + + /** + * Add file to this pdf + * + * @param string $filename Filename of the source file + * @param mixed $pages Range of files (if not set, all pages where imported) + */ + public function add( $filename, $pages = array() ) { + if ( file_exists( $filename ) ) { + $page_count = $this->_pdf->setSourceFile( $filename ); + for ( $i = 1; $i <= $page_count; $i++ ) { + if ( $this->_isPageInRange( $i, $pages ) ) { + $this->_addPage( $i ); + } + } + } + return $this; + } + + /** + * Output merged pdf + * + * @param string $type + */ + public function output( $filename, $type = 'I' ) { + return $this->_pdf->Output( $type, $filename ); + } + + /** + * Force download merged pdf as file + * + * @param $filename + * @return string + */ + public function download( $filename ) { + return $this->output( $filename, 'D' ); + } + + /** + * Save merged pdf + * + * @param $filename + * @return string + */ + public function save( $filename ) { + return $this->output( $filename, 'F' ); + } + + /** + * Add single page + * + * @param $page_number + * @throws PdfReaderException + */ + private function _addPage( $page_number ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore,WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + $page_id = $this->_pdf->importPage( $page_number ); + $this->_pdf->addPage(); + $this->_pdf->useImportedPage( $page_id ); + } + + + /** + * Check if a specific page should be merged. + * If pages are empty, all pages will be merged + * + * @return bool + */ + private function _isPageInRange( $page_number, $pages = array() ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore,WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + if ( empty( $pages ) ) { + return true; + } + + foreach ( $pages as $range ) { + if ( in_array( $page_number, $this->_getRange( $range ), true ) ) { + return true; + } + } + + return false; + } + + + /** + * Get range by given value + * + * @param mixed $value + * @return array + */ + private function _getRange( $value = null ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore,WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid + $value = preg_replace( '/[^0-9\-.]/is', '', $value ); + + if ( '' === $value ) { + return false; + } + + $value = explode( '-', $value ); + + if ( 1 === count( $value ) ) { + return $value; + } + + return range( $value[0] > $value[1] ? $value[1] : $value[0], $value[0] > $value[1] ? $value[0] : $value[1] ); + } + +} diff --git a/packages/woocommerce-germanized-shipments/src/Package.php b/packages/woocommerce-germanized-shipments/src/Package.php new file mode 100644 index 000000000..b4b581ed3 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Package.php @@ -0,0 +1,779 @@ +get_shipping_provider(); + } + + return $args; + } + + public static function add_return_shipment_guest_endpoints( $template, $template_name ) { + global $wp; + + if ( 'myaccount/form-login.php' === $template_name ) { + try { + $key = ( isset( $_GET['key'] ) ? wc_clean( wp_unslash( $_GET['key'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $order_id = false; + $callback = false; + + if ( isset( $wp->query_vars['add-return-shipment'] ) ) { + $callback = 'woocommerce_gzd_shipments_template_add_return_shipment'; + $order_id = absint( $wp->query_vars['add-return-shipment'] ); + } + + if ( $callback && $order_id && ( $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) && ! empty( $key ) ) { + + // Order return key is invalid. + if ( ! wc_gzd_customer_can_add_return_shipment( $order_id ) ) { + throw new Exception( _x( 'Sorry, this order is invalid and cannot be returned.', 'shipments', 'woocommerce-germanized' ) ); + } else { + call_user_func_array( $callback, array( 'order_id' => $order_id ) ); + $template = self::get_path() . '/templates/global/empty.php'; + } + } + } catch ( Exception $e ) { + wc_add_notice( $e->getMessage(), 'error' ); + } + } + + return $template; + } + + public static function register_shortcodes() { + add_shortcode( 'gzd_return_request_form', array( __CLASS__, 'return_request_form' ) ); + } + + public static function return_request_form( $args = array() ) { + $defaults = array( + 'message' => '', + 'hidden' => false, + ); + + $args = wp_parse_args( $args, $defaults ); + $notices = function_exists( 'wc_print_notices' ) ? wc_print_notices( true ) : ''; + $html = ''; + + // Output notices in case notices have not been outputted yet. + if ( ! empty( $notices ) ) { + $html .= 'get_date_start()->date_i18n( wc_date_format() ) ); ?> – get_date_end()->date_i18n( wc_date_format() ) ); ?>: get_total_weight(), wc_gzd_get_packaging_weight_unit() ) ); ?> (get_total_count() ) ); ?>)
++ + | ||
---|---|---|
get_id() > 0 ? $packaging->get_description() : _x( 'Unknown', 'shipments-packaging-title', 'woocommerce-germanized' ) ) ); ?> | +get_packaging_weight( $packaging_id ), wc_gzd_get_packaging_weight_unit() ) ); ?> | +get_packaging_count( $packaging_id ) ); ?> | +
get_total_packaging_weight_by_country( $country ), wc_gzd_get_packaging_weight_unit() ) ); ?> (get_total_packaging_count_by_country( $country ) ) ); ?>)
++ + | ||
---|---|---|
get_id() > 0 ? $packaging->get_description() : _x( 'Unknown', 'shipments-packaging-title', 'woocommerce-germanized' ) ) ); ?> | +get_packaging_weight( $packaging_id, $country ), wc_gzd_get_packaging_weight_unit() ) ); ?> | +get_packaging_count( $packaging_id, $country ) ); ?> | +
Find pending actions', 'shipments', 'woocommerce-germanized' ), esc_html( $details['shipment_count'] ), ( $details['next_date'] ? esc_html( $details['next_date']->date_i18n( wc_date_format() . ' @ ' . wc_time_format() ) ) : esc_html_x( 'Not yet known', 'shipments', 'woocommerce-germanized' ) ), esc_url( $details['link'] ) ); ?>
+ +', '
' ), '', $entry->display_value ), + 'label' => $entry->display_key, + 'order_item_meta_id' => $meta_id, + ); + } + + $args = wp_parse_args( + $args, + array( + 'order_item_id' => $item->get_id(), + 'quantity' => 1, + 'name' => $item->get_name(), + 'sku' => $product ? $product->get_sku() : '', + 'total' => $total + $tax_total, + 'subtotal' => $subtotal + $tax_subtotal, + 'weight' => $product ? wc_get_weight( $product->get_weight(), $shipment->get_weight_unit() ) : '', + 'length' => $product ? wc_get_dimension( $product->get_length(), $shipment->get_dimension_unit() ) : '', + 'width' => $product ? wc_get_dimension( $product->get_width(), $shipment->get_dimension_unit() ) : '', + 'height' => $product ? wc_get_dimension( $product->get_height(), $shipment->get_dimension_unit() ) : '', + 'hs_code' => $s_product ? $s_product->get_hs_code() : '', + 'customs_description' => $s_product ? $s_product->get_customs_description() : '', + 'manufacture_country' => $s_product ? $s_product->get_manufacture_country() : '', + 'attributes' => $attributes, + ) + ); + } + + $this->set_props( $args ); + + /** + * Action that fires after a shipment item has been synced. Syncing is used to + * keep the shipment item in sync with the corresponding order item or parent shipment item. + * + * @param WC_Order_Item|ShipmentItem $item The order item object or parent shipment item. + * @param array $args Array containing props in key => value pairs which have been updated. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_item_synced', $this, $item, $args ); + } + + public function get_order_item() { + if ( is_null( $this->order_item ) && 0 < $this->get_order_item_id() ) { + if ( $shipment = $this->get_shipment() ) { + + if ( $order = $shipment->get_order() ) { + $this->order_item = $order->get_item( $this->get_order_item_id() ); + } + } + } + + $item = ( $this->order_item ) ? $this->order_item : false; + + return $item; + } + + public function get_product() { + if ( is_null( $this->product ) && 0 < $this->get_product_id() ) { + $this->product = wc_get_product( $this->get_product_id() ); + } + + $product = ( $this->product ) ? $this->product : false; + + return $product; + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set order ID. + * + * @param int $value Order ID. + */ + public function set_shipment_id( $value ) { + $this->order_item = null; + $this->shipment = null; + + $this->set_prop( 'shipment_id', absint( $value ) ); + } + + /** + * Set order ID. + * + * @param int $value Order ID. + */ + public function set_order_item_id( $value ) { + $this->set_prop( 'order_item_id', absint( $value ) ); + } + + /** + * Set order ID. + * + * @param int $value Order ID. + */ + public function set_total( $value ) { + $value = wc_format_decimal( $value ); + + if ( ! is_numeric( $value ) ) { + $value = 0; + } + + $this->set_prop( 'total', $value ); + } + + /** + * Set order ID. + * + * @param int $value Order ID. + */ + public function set_subtotal( $value ) { + $value = wc_format_decimal( $value ); + + if ( ! is_numeric( $value ) ) { + $value = 0; + } + + $this->set_prop( 'subtotal', $value ); + } + + /** + * Set order ID. + * + * @param int $value Order ID. + */ + public function set_product_id( $value ) { + $this->product = null; + $this->set_prop( 'product_id', absint( $value ) ); + } + + /** + * Set parent id. + * + * @param int $value parent id. + */ + public function set_parent_id( $value ) { + $this->set_prop( 'parent_id', absint( $value ) ); + } + + public function set_sku( $sku ) { + $this->set_prop( 'sku', $sku ); + } + + /** + * Set weight in kg + * + * @param $weight + */ + public function set_weight( $weight ) { + $this->set_prop( 'weight', '' === $weight ? '' : wc_format_decimal( $weight ) ); + } + + /** + * Set width in cm + * + * @param $weight + */ + public function set_width( $width ) { + $this->set_prop( 'width', '' === $width ? '' : wc_format_decimal( $width ) ); + } + + /** + * Set length in cm + * + * @param $weight + */ + public function set_length( $length ) { + $this->set_prop( 'length', '' === $length ? '' : wc_format_decimal( $length ) ); + } + + /** + * Set height in cm + * + * @param $weight + */ + public function set_height( $height ) { + $this->set_prop( 'height', '' === $height ? '' : wc_format_decimal( $height ) ); + } + + public function get_dimensions( $context = 'view' ) { + return array( + 'length' => $this->get_length( $context ), + 'width' => $this->get_width( $context ), + 'height' => $this->get_height( $context ), + ); + } + + public function set_quantity( $quantity ) { + $this->set_prop( 'quantity', absint( $quantity ) ); + } + + public function set_name( $name ) { + $this->set_prop( 'name', $name ); + } + + public function set_hs_code( $code ) { + $this->set_prop( 'hs_code', $code ); + } + + public function set_customs_description( $description ) { + $this->set_prop( 'customs_description', $description ); + } + + public function set_manufacture_country( $country ) { + $this->set_prop( 'manufacture_country', wc_strtoupper( $country ) ); + } + + /** + * Set attributes + * + * @param $attributes + */ + public function set_attributes( $attributes ) { + $this->set_prop( 'attributes', (array) $attributes ); + } + + /* + |-------------------------------------------------------------------------- + | Other Methods + |-------------------------------------------------------------------------- + */ +} diff --git a/packages/woocommerce-germanized-shipments/src/ShipmentQuery.php b/packages/woocommerce-germanized-shipments/src/ShipmentQuery.php new file mode 100644 index 000000000..eb3ec91b3 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShipmentQuery.php @@ -0,0 +1,604 @@ + array_keys( wc_gzd_get_shipment_statuses() ), + 'limit' => 10, + 'order_id' => '', + 'parent_id' => '', + 'product_ids' => '', + 'product_category' => '', + 'type' => 'simple', + 'country' => '', + 'tracking_id' => '', + 'order' => 'DESC', + 'orderby' => 'date_created', + 'shipping_provider' => '', + 'return' => 'objects', + 'page' => 1, + 'offset' => '', + 'paginate' => false, + 'search' => '', + 'search_columns' => array(), + ); + } + + /** + * Get shipments matching the current query vars. + * + * @return Shipment[] Array containing Shipments. + */ + public function get_shipments() { + /** + * Filter to adjust query arguments passed to a Shipment query. + * + * @param array $args The arguments passed. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $args = apply_filters( 'woocommerce_gzd_shipment_query_args', $this->get_query_vars() ); + $args = WC_Data_Store::load( 'shipment' )->get_query_args( $args ); + + $this->query( $args ); + + /** + * Filter to adjust the Shipment query result. + * + * @param Shipment[] $results Shipment results. + * @param array $args The arguments passed. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_query', $this->results, $args ); + } + + public function get_total() { + return $this->total_shipments; + } + + public function get_max_num_pages() { + return $this->max_num_pages; + } + + /** + * Query shipments. + * + * @param array $query_args + */ + protected function query( $query_args ) { + global $wpdb; + + $this->args = $query_args; + $this->parse_query(); + $this->prepare_query(); + + $qv =& $this->args; + + $this->results = null; + + if ( null === $this->results ) { + $clauses = array( + 'fields' => $this->query_fields, + 'from' => $this->query_from, + 'where' => $this->query_where, + 'orderby' => $this->query_orderby, + 'limits' => $this->query_limit, + ); + + /** + * Filters all query clauses for a shipment query at once, for convenience. + * + * @param string[] $clauses Associative array of the clauses for the query. + * @param ShipmentQuery $query The ShipmentQuery instance. + */ + $clauses = (array) apply_filters( 'woocommerce_gzd_shipment_query_clauses', $clauses, $this ); + + $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : ''; + $from = isset( $clauses['from'] ) ? $clauses['from'] : ''; + $where = isset( $clauses['where'] ) ? $clauses['where'] : ''; + $groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : ''; + $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : ''; + $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : ''; + + $this->request = "SELECT $fields $from $where $groupby $orderby $limits"; + + if ( is_array( $qv['fields'] ) || 'objects' === $qv['fields'] ) { + $this->results = $wpdb->get_results( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } else { + $this->results = $wpdb->get_col( $this->request ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } + + if ( isset( $qv['count_total'] ) && $qv['count_total'] ) { + $found_shipments_query = 'SELECT FOUND_ROWS()'; + $this->total_shipments = (int) $wpdb->get_var( $found_shipments_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $this->max_num_pages = ceil( $this->total_shipments / $qv['posts_per_page'] ); + } + } + + if ( ! $this->results ) { + return; + } + + if ( 'objects' === $qv['fields'] ) { + foreach ( $this->results as $key => $shipment ) { + $this->results[ $key ] = wc_gzd_get_shipment( $shipment ); + } + } + } + + /** + * Parse the query before preparing it. + */ + protected function parse_query() { + if ( isset( $this->args['order_id'] ) ) { + $this->args['order_id'] = (array) $this->args['order_id']; + $this->args['order_id'] = array_map( 'absint', $this->args['order_id'] ); + } + + if ( isset( $this->args['shipping_provider'] ) ) { + $this->args['shipping_provider'] = wc_clean( $this->args['shipping_provider'] ); + } + + if ( isset( $this->args['parent_id'] ) ) { + $this->args['parent_id'] = absint( $this->args['parent_id'] ); + } + + if ( isset( $this->args['product_ids'] ) ) { + $this->args['product_ids'] = (array) $this->args['product_ids']; + $this->args['product_ids'] = array_map( 'absint', $this->args['product_ids'] ); + } + + if ( isset( $this->args['product_category'] ) ) { + $this->args['product_category'] = (array) $this->args['product_category']; + $this->args['product_category'] = array_map( 'absint', $this->args['product_category'] ); + } + + if ( isset( $this->args['tracking_id'] ) ) { + $this->args['tracking_id'] = sanitize_key( $this->args['tracking_id'] ); + } + + if ( isset( $this->args['status'] ) ) { + $this->args['status'] = (array) $this->args['status']; + $this->args['status'] = array_map( 'sanitize_key', $this->args['status'] ); + } + + if ( isset( $this->args['type'] ) ) { + $this->args['type'] = (array) $this->args['type']; + $this->args['type'] = array_map( 'wc_clean', $this->args['type'] ); + } + + if ( isset( $this->args['country'] ) ) { + $countries = isset( WC()->countries ) ? WC()->countries : false; + + if ( $countries && is_a( $countries, 'WC_Countries' ) ) { + + // Reverse search by country name + if ( $key = array_search( $this->args['country'], $countries->get_countries(), true ) ) { + $this->args['country'] = $key; + } + } + + // Country Code ISO + $this->args['country'] = strtoupper( substr( $this->args['country'], 0, 2 ) ); + } + + if ( isset( $this->args['search'] ) ) { + $this->args['search'] = wc_clean( $this->args['search'] ); + + if ( ! isset( $this->args['search_columns'] ) ) { + $this->args['search_columns'] = array(); + } + } + + if ( isset( $this->args['orderby'] ) ) { + if ( 'weight' === $this->args['orderby'] ) { + $this->args['meta_query'][] = array( + 'relation' => 'OR', + array( + 'key' => '_weight', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => '_weight', + 'compare' => '>=', + 'value' => 0, + ), + ); + + $this->args['orderby'] = 'meta_value_num'; + } + } + } + + /** + * Prepare the query for DB usage. + */ + protected function prepare_query() { + global $wpdb; + + if ( is_array( $this->args['fields'] ) ) { + $this->args['fields'] = array_unique( $this->args['fields'] ); + + $this->query_fields = array(); + + foreach ( $this->args['fields'] as $field ) { + $field = 'ID' === $field ? 'shipment_id' : sanitize_key( $field ); + $this->query_fields[] = "$wpdb->gzd_shipments.$field"; + } + + $this->query_fields = implode( ',', $this->query_fields ); + + } elseif ( 'objects' === $this->args['fields'] ) { + $this->query_fields = "$wpdb->gzd_shipments.*"; + } else { + $this->query_fields = "$wpdb->gzd_shipments.shipment_id"; + } + + if ( isset( $this->args['count_total'] ) && $this->args['count_total'] ) { + $this->query_fields = 'SQL_CALC_FOUND_ROWS ' . $this->query_fields; + } + + $this->query_from = "FROM $wpdb->gzd_shipments"; + $this->query_where = 'WHERE 1=1'; + + // order id + if ( isset( $this->args['order_id'] ) ) { + $order_ids = array_filter( array_map( 'absint', $this->args['order_id'] ) ); + $placeholders = implode( ',', array_fill( 0, count( $order_ids ), '%d' ) ); + $this->query_where .= $wpdb->prepare( " AND shipment_order_id IN ({$placeholders})", ...$order_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + } + + // order id + if ( isset( $this->args['shipping_provider'] ) ) { + $this->query_where .= $wpdb->prepare( ' AND shipment_shipping_provider = %s', $this->args['shipping_provider'] ); + } + + // tracking id + if ( isset( $this->args['tracking_id'] ) ) { + $this->query_where .= $wpdb->prepare( " AND shipment_tracking_id IN ('%s')", $this->args['tracking_id'] ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder + } + + // parent id + if ( isset( $this->args['parent_id'] ) ) { + $this->query_where .= $wpdb->prepare( ' AND shipment_parent_id = %d', $this->args['parent_id'] ); + } + + // product ids + if ( isset( $this->args['product_ids'] ) ) { + $product_ids_placeholders = implode( ', ', array_fill( 0, count( $this->args['product_ids'] ), '%d' ) ); + + $this->query_from .= " JOIN {$wpdb->prefix}woocommerce_gzd_shipment_items as shipment_items ON ( shipment_items.shipment_id = {$wpdb->prefix}woocommerce_gzd_shipments.shipment_id ) "; + $this->query_where .= $wpdb->prepare( " AND shipment_items.shipment_item_product_id IN ({$product_ids_placeholders})", $this->args['product_ids'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + } + + // product category + if ( isset( $this->args['product_category'] ) ) { + $product_category_placeholders = implode( ', ', array_fill( 0, count( $this->args['product_category'] ), '%d' ) ); + + $this->query_from .= " JOIN {$wpdb->prefix}woocommerce_gzd_shipment_items AS shipment_items ON {$wpdb->prefix}woocommerce_gzd_shipments.shipment_id = shipment_items.shipment_id "; + $this->query_from .= " JOIN {$wpdb->prefix}term_relationships AS term_relationships ON term_relationships.object_id = shipment_items.shipment_item_product_id "; + $this->query_where .= $wpdb->prepare( " AND term_relationships.term_taxonomy_id IN ({$product_category_placeholders})", $this->args['product_category'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + } + + // country + if ( isset( $this->args['country'] ) ) { + $this->query_where .= $wpdb->prepare( " AND shipment_country IN ('%s')", $this->args['country'] ); // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedSimplePlaceholder + } + + // type + if ( isset( $this->args['type'] ) ) { + $types = $this->args['type']; + $p_types = array(); + + foreach ( $types as $type ) { + $p_types[] = $wpdb->prepare( 'shipment_type = %s', $type ); + } + + $where_type = implode( ' OR ', $p_types ); + + if ( ! empty( $where_type ) ) { + $this->query_where .= " AND ($where_type)"; + } + } + + // status + if ( isset( $this->args['status'] ) ) { + $stati = $this->args['status']; + $p_status = array(); + + foreach ( $stati as $status ) { + $p_status[] = $wpdb->prepare( 'shipment_status = %s', $status ); + } + + $where_status = implode( ' OR ', $p_status ); + + if ( ! empty( $where_status ) ) { + $this->query_where .= " AND ($where_status)"; + } + } + + // Search + $search = ''; + + if ( isset( $this->args['search'] ) ) { + $search = trim( $this->args['search'] ); + } + + if ( $search ) { + + $leading_wild = ( ltrim( $search, '*' ) !== $search ); + $trailing_wild = ( rtrim( $search, '*' ) !== $search ); + + if ( $leading_wild && $trailing_wild ) { + $wild = 'both'; + } elseif ( $leading_wild ) { + $wild = 'leading'; + } elseif ( $trailing_wild ) { + $wild = 'trailing'; + } else { + $wild = false; + } + if ( $wild ) { + $search = trim( $search, '*' ); + } + + $search_columns = array(); + + if ( $this->args['search_columns'] ) { + $search_columns = array_intersect( $this->args['search_columns'], array( 'shipment_id', 'shipment_country', 'shipment_tracking_id', 'shipment_order_id', 'shipment_shipping_provider', 'shipment_shipping_method', 'shipment_search_index' ) ); + } + + if ( ! $search_columns ) { + if ( is_numeric( $search ) ) { + $search_columns = array( 'shipment_id', 'shipment_order_id', 'shipment_tracking_id' ); + } elseif ( strlen( $search ) === 2 ) { + $search_columns = array( 'shipment_country' ); + } else { + $search_columns = array( 'shipment_id', 'shipment_country', 'shipment_tracking_id', 'shipment_order_id', 'shipment_search_index' ); + } + } + + /** + * Filters the columns to search in a ShipmentQuery search. + * + * The default columns depend on the search term, and include 'shipment_id', 'shipment_country', + * 'shipment_tracking_id', 'shipment_order_id', 'shipment_shipping_provider' and 'shipment_shipping_method'. + * + * @since 3.0.0 + * + * @param string[] $search_columns Array of column names to be searched. + * @param string $search Text being searched. + * @param ShipmentQuery $this The current ShipmentQuery instance. + * + * @package Vendidero/Germanized/Shipments + */ + $search_columns = apply_filters( 'woocommerce_gzd_shipment_search_columns', $search_columns, $search, $this ); + + $this->query_where .= $this->get_search_sql( $search, $search_columns, $wild ); + } + + // Parse and sanitize 'include', for use by 'orderby' as well as 'include' below. + if ( ! empty( $this->args['include'] ) ) { + $include = wp_parse_id_list( $this->args['include'] ); + } else { + $include = false; + } + + // Meta query. + $this->meta_query = new WP_Meta_Query(); + $this->meta_query->parse_query_vars( $this->args ); + + if ( ! empty( $this->meta_query->queries ) ) { + $clauses = $this->meta_query->get_sql( 'gzd_shipment', $wpdb->gzd_shipments, 'shipment_id', $this ); + $this->query_from .= $clauses['join']; + $this->query_where .= $clauses['where']; + + if ( $this->meta_query->has_or_relation() ) { + $this->query_fields = 'DISTINCT ' . $this->query_fields; + } + } + + // sorting + $this->args['order'] = isset( $this->args['order'] ) ? strtoupper( $this->args['order'] ) : ''; + $order = $this->parse_order( $this->args['order'] ); + + if ( empty( $this->args['orderby'] ) ) { + // Default order is by 'user_login'. + $ordersby = array( 'date_created' => $order ); + } elseif ( is_array( $this->args['orderby'] ) ) { + $ordersby = $this->args['orderby']; + } else { + // 'orderby' values may be a comma- or space-separated list. + $ordersby = preg_split( '/[,\s]+/', $this->args['orderby'] ); + } + + $orderby_array = array(); + + foreach ( $ordersby as $_key => $_value ) { + if ( ! $_value ) { + continue; + } + + if ( is_int( $_key ) ) { + // Integer key means this is a flat array of 'orderby' fields. + $_orderby = $_value; + $_order = $order; + } else { + // Non-integer key means this the key is the field and the value is ASC/DESC. + $_orderby = $_key; + $_order = $_value; + } + + $parsed = $this->parse_orderby( $_orderby ); + + if ( ! $parsed ) { + continue; + } + + $orderby_array[] = $parsed . ' ' . $this->parse_order( $_order ); + } + + // If no valid clauses were found, order by user_login. + if ( empty( $orderby_array ) ) { + $orderby_array[] = "shipment_id $order"; + } + + $this->query_orderby = 'ORDER BY ' . implode( ', ', $orderby_array ); + + // limit + if ( isset( $this->args['posts_per_page'] ) && $this->args['posts_per_page'] > 0 ) { + if ( isset( $this->args['offset'] ) ) { + $this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $this->args['offset'], $this->args['posts_per_page'] ); + } else { + $this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $this->args['posts_per_page'] * ( $this->args['page'] - 1 ), $this->args['posts_per_page'] ); + } + } + + if ( ! empty( $include ) ) { + // Sanitized earlier. + $ids = implode( ',', $include ); + $this->query_where .= " AND $wpdb->gzd_shipments.shipment_id IN ($ids)"; + } elseif ( ! empty( $this->args['exclude'] ) ) { + $ids = implode( ',', wp_parse_id_list( $this->args['exclude'] ) ); + $this->query_where .= " AND $wpdb->gzd_shipments.shipment_id NOT IN ($ids)"; + } + + // Date queries are allowed for the user_registered field. + if ( ! empty( $this->args['date_query'] ) && is_array( $this->args['date_query'] ) ) { + $date_query = new WP_Date_Query( $this->args['date_query'], 'shipment_date_created' ); + $this->query_where .= $date_query->get_sql(); + } + } + + /** + * Used internally to generate an SQL string for searching across multiple columns + * + * @since 3.0.6 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param string $string + * @param array $cols + * @param bool $wild Whether to allow wildcard searches. Default is false for Network Admin, true for single site. + * Single site allows leading and trailing wildcards, Network Admin only trailing. + * @return string + */ + protected function get_search_sql( $string, $cols, $wild = false ) { + global $wpdb; + + $searches = array(); + $leading_wild = ( 'leading' === $wild || 'both' === $wild ) ? '%' : ''; + $trailing_wild = ( 'trailing' === $wild || 'both' === $wild ) ? '%' : ''; + $like = $leading_wild . $wpdb->esc_like( $string ) . $trailing_wild; + + foreach ( $cols as $col ) { + if ( 'ID' === $col ) { + $searches[] = $wpdb->prepare( "$wpdb->gzd_shipments.$col = %s", $string ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } else { + $searches[] = $wpdb->prepare( "$wpdb->gzd_shipments.$col LIKE %s", $like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + } + } + + return ' AND (' . implode( ' OR ', $searches ) . ')'; + } + + /** + * Parse orderby statement. + * + * @param string $orderby + * @return string + */ + protected function parse_orderby( $orderby ) { + global $wpdb; + + $meta_query_clauses = $this->meta_query->get_clauses(); + $_orderby = ''; + + if ( in_array( $orderby, array( 'country', 'status', 'tracking_id', 'date_created', 'order_id' ), true ) ) { + $_orderby = 'shipment_' . $orderby; + } elseif ( 'date' === $orderby ) { + $_orderby = 'shipment_date_created'; + } elseif ( 'ID' === $orderby || 'id' === $orderby ) { + $_orderby = 'shipment_id'; + } elseif ( 'meta_value' === $orderby || $this->get( 'meta_key' ) === $orderby ) { + $_orderby = "$wpdb->gzd_shipmentmeta.meta_value"; + } elseif ( 'meta_value_num' === $orderby ) { + $_orderby = "$wpdb->gzd_shipmentmeta.meta_value+0"; + } elseif ( 'include' === $orderby && ! empty( $this->args['include'] ) ) { + $include = wp_parse_id_list( $this->args['include'] ); + $include_sql = implode( ',', $include ); + $_orderby = "FIELD( $wpdb->gzd_shipments.shipment_id, $include_sql )"; + } elseif ( isset( $meta_query_clauses[ $orderby ] ) ) { + $meta_clause = $meta_query_clauses[ $orderby ]; + $_orderby = sprintf( 'CAST(%s.meta_value AS %s)', esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) ); + } + + return $_orderby; + } + + /** + * Parse order statement. + * + * @param string $order + * @return string + */ + protected function parse_order( $order ) { + if ( ! is_string( $order ) || empty( $order ) ) { + return 'DESC'; + } + + if ( 'ASC' === strtoupper( $order ) ) { + return 'ASC'; + } else { + return 'DESC'; + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ShipmentReturnItem.php b/packages/woocommerce-germanized-shipments/src/ShipmentReturnItem.php new file mode 100644 index 000000000..14bba66d1 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShipmentReturnItem.php @@ -0,0 +1,32 @@ + '', + ); + + public function get_type() { + return 'return'; + } + + public function get_return_reason_code( $context = 'view' ) { + return $this->get_prop( 'return_reason_code', $context ); + } + + public function set_return_reason_code( $code ) { + $this->set_prop( 'return_reason_code', $code ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ShippingMethod/MethodHelper.php b/packages/woocommerce-germanized-shipments/src/ShippingMethod/MethodHelper.php new file mode 100644 index 000000000..6ee526eed --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShippingMethod/MethodHelper.php @@ -0,0 +1,642 @@ +get_shipping_providers() as $provider ) { + $methods[ "shipping_provider_{$provider->get_name()}" ] = new ShippingMethod( 0, $provider ); + } + + return $methods; + } + + public static function register_cart_items_to_pack( $cart_contents ) { + if ( ! Package::is_packing_supported() || apply_filters( 'woocommerce_gzd_shipments_disable_cart_packing', false ) ) { + return $cart_contents; + } + + foreach ( $cart_contents as $index => $content ) { + $package_data = array( + 'total' => 0.0, + 'subtotal' => 0.0, + 'weight' => 0.0, + 'volume' => 0.0, + 'products' => array(), + 'shipping_classes' => array(), + 'item_count' => 0, + ); + + $items = new ItemList(); + $bundled_by_map = array(); + + foreach ( $content['contents'] as $content_key => $item ) { + $item = apply_filters( 'woocommerce_gzd_shipments_cart_item', $item, $content_key ); + + /** + * Product Bundles cart item compatibility: + * In case the current item belongs to a parent bundle item (which contains the actual price) + * copy the pricing data from the parent once, e.g. for the first bundled item. + */ + if ( isset( $item['bundled_by'] ) && 0.0 === (float) $item['line_total'] && function_exists( 'wc_pb_get_bundled_cart_item_container' ) ) { + $bundled_by = $item['bundled_by']; + + if ( ! in_array( $bundled_by, $bundled_by_map, true ) ) { + if ( $container = wc_pb_get_bundled_cart_item_container( $item ) ) { + $item['line_total'] = (float) $container['line_total']; + $item['line_subtotal'] = (float) $container['line_subtotal']; + $item['line_tax'] = (float) $container['line_tax']; + $item['line_subtotal_tax'] = (float) $container['line_subtotal_tax']; + + $bundled_by_map[] = $bundled_by; + } + } + } + + $product = $item['data']; + + if ( ! is_a( $product, 'WC_Product' ) ) { + continue; + } + + $line_total = (float) $item['line_total']; + $line_subtotal = (float) $item['line_subtotal']; + + if ( wc()->cart->display_prices_including_tax() ) { + $line_total += (float) $item['line_tax']; + $line_subtotal += (float) $item['line_subtotal_tax']; + } + + $quantity = (int) ceil( (float) $item['quantity'] ); + $width = ( empty( $product->get_width() ) ? 0 : wc_format_decimal( $product->get_width() ) ) * $quantity; + $length = ( empty( $product->get_length() ) ? 0 : wc_format_decimal( $product->get_length() ) ) * $quantity; + $height = ( empty( $product->get_height() ) ? 0 : wc_format_decimal( $product->get_height() ) ) * $quantity; + $weight = ( empty( $product->get_weight() ) ? 0 : wc_format_decimal( $product->get_weight() ) ) * $quantity; + + $package_data['total'] += $line_total; + $package_data['subtotal'] += $line_subtotal; + $package_data['weight'] += $weight; + $package_data['volume'] += ( $width * $length * $height ); + $package_data['item_count'] += $quantity; + + if ( $product && ! array_key_exists( $product->get_id(), $package_data['products'] ) ) { + $package_data['products'][ $product->get_id() ] = $product; + + if ( ! empty( $product->get_shipping_class_id() ) ) { + $package_data['shipping_classes'][] = $product->get_shipping_class_id(); + } + } + + $cart_item = new CartItem( $item, wc()->cart->display_prices_including_tax() ); + $items->insert( $cart_item, $quantity ); + } + + /** + * In case prices have already been calculated, use the official + * Woo API for better compatibility with extensions, e.g. Bundles. + */ + if ( 0 !== WC()->cart->get_subtotal() ) { + $total = (float) WC()->cart->get_cart_contents_total(); + $subtotal = (float) WC()->cart->get_subtotal(); + + if ( wc()->cart->display_prices_including_tax() ) { + $total += (float) WC()->cart->get_cart_contents_tax(); + $subtotal += (float) WC()->cart->get_subtotal_tax(); + } + + $package_data['total'] = NumberUtil::round_to_precision( $total ); + $package_data['subtotal'] = NumberUtil::round_to_precision( $subtotal ); + } + + $cart_contents[ $index ]['package_data'] = $package_data; + $cart_contents[ $index ]['items_to_pack'] = $items; + } + + return $cart_contents; + } + + public static function render_method_configuration_sets() { + return ''; + } + + public static function set_method_filters( $methods ) { + foreach ( $methods as $method => $class ) { + if ( self::method_is_excluded( $method ) ) { + continue; + } + + /** + * Update during save + */ + add_filter( 'woocommerce_shipping_' . $method . '_instance_settings_values', array( __CLASS__, 'filter_method_settings' ), 10, 2 ); + /** + * Register additional setting fields + */ + add_filter( 'woocommerce_shipping_instance_form_fields_' . $method, array( __CLASS__, 'add_method_settings' ), 10, 1 ); + /** + * Lazy-load option values + */ + add_filter( 'woocommerce_shipping_' . $method . '_instance_option', array( __CLASS__, 'filter_method_option_value' ), 10, 3 ); + + /** + * Use this filter as a backup to support plugins like Flexible Shipping which may override methods + */ + add_filter( 'woocommerce_settings_api_form_fields_' . $method, array( __CLASS__, 'add_method_settings' ), 10, 1 ); + } + + return $methods; + } + + /** + * @param \WC_Shipping_Method|string|integer $method + * + * @return ProviderMethod|false + */ + public static function get_provider_method( $maybe_method ) { + $original_id = $maybe_method; + $method = false; + $method_id = ''; + $instance_id = 0; + + if ( is_a( $original_id, 'WC_Shipping_Rate' ) ) { + $instance_id = $original_id->get_instance_id(); + $method_id = $original_id->get_method_id(); + } elseif ( is_a( $original_id, 'WC_Shipping_Method' ) ) { + $instance_id = $original_id->get_instance_id(); + $method_id = $original_id->id; + $method = $original_id; + } elseif ( ! is_numeric( $original_id ) && is_string( $original_id ) ) { + if ( strpos( $original_id, ':' ) !== false ) { + $expl = explode( ':', $original_id ); + $instance_id = ( ( ! empty( $expl ) && count( $expl ) > 1 ) ? (int) $expl[1] : 0 ); + $method_id = ( ! empty( $expl ) ) ? $expl[0] : $original_id; + } else { + /** + * Plugins like Flexible Shipping use underscores to separate instance ids. + * Example: flexible_shipping_4_1. In this case, 4 ist the instance id. + * method_id: flexible_shipping + * instance_id: 4 + * + * On the other hand legacy shipping methods may be string only, e.g. an instance id might not exist. + * Example: local_pickup_plus + * method: local_pickup_plus + * instance_id: 0 + */ + $expl = explode( '_', $original_id ); + $numbers = array_values( array_filter( $expl, 'is_numeric' ) ); + $method_id = rtrim( preg_replace( '/[0-9]+/', '', $original_id ), '_' ); + + if ( ! empty( $numbers ) ) { + $instance_id = absint( $numbers[0] ); + } else { + $instance_id = 0; + } + } + } elseif ( is_numeric( $original_id ) ) { + $instance_id = absint( $original_id ); + } + + $method_key = $method_id . '_' . $instance_id; + + if ( array_key_exists( $method_key, self::$methods ) ) { + return self::$methods[ $method_key ]; + } else { + if ( ! is_a( $method, 'WC_Shipping_Method' ) && ! empty( $instance_id ) ) { + // Make sure shipping zones are loaded + include_once WC_ABSPATH . 'includes/class-wc-shipping-zones.php'; + + $method = \WC_Shipping_Zones::get_shipping_method( $instance_id ); + } + + /** + * Fallback for legacy shipping methods that do not support instance ids. + */ + if ( ! $method && empty( $instance_id ) && ! empty( $method_id ) ) { + $shipping_methods = WC()->shipping()->get_shipping_methods(); + + if ( array_key_exists( $method_id, $shipping_methods ) ) { + $method = $shipping_methods[ $method_id ]; + } + } + + if ( ! is_a( $method, 'WC_Shipping_Method' ) ) { + self::$methods[ $method_key ] = new ProviderMethodPlaceholder( + array( + 'id' => $method_id, + 'instance_id' => $instance_id, + ) + ); + } else { + self::$methods[ $method_key ] = new ProviderMethod( $method ); + } + } + + return self::$methods[ $method_key ]; + } + + public static function method_is_excluded( $method ) { + $is_excluded = false; + $excluded = apply_filters( 'woocommerce_gzd_shipments_get_methods_excluded_from_provider_settings', array( 'pr_dhl_paket', 'flexible_shipping_info' ) ); + + if ( in_array( $method, $excluded, true ) ) { + $is_excluded = true; + } elseif ( 'shipping_provider_' === substr( $method, 0, 18 ) ) { + $is_excluded = true; + } + + return apply_filters( 'woocommerce_gzd_shipments_shipping_method_is_excluded_from_provider_settings', $is_excluded, $method ); + } + + public static function validate_method_zone_override( $value ) { + return ! is_null( $value ) ? 'yes' : 'no'; + } + + /** + * @param mixed $value + * @param mixed $setting_id + * @param \WC_Shipping_Method $method + * + * @return mixed + */ + public static function filter_method_option_value( $value, $setting_id, $method ) { + $shipping_method = self::get_provider_method( $method ); + + if ( $shipping_method->is_configuration_set_setting( $setting_id ) ) { + if ( $configuration_set = $shipping_method->get_configuration_set( $setting_id ) ) { + $suffix = $shipping_method->get_configuration_setting_suffix( $setting_id ); + + if ( 'override' === $suffix ) { + return 'yes'; + } else { + return $configuration_set->has_setting( $setting_id ) ? $configuration_set->get_setting( $setting_id ) : $value; + } + } + } + + return $value; + } + + /** + * @param array $p_settings + * @param \WC_Shipping_Method $shipping_method + * + * @return array + */ + public static function filter_method_settings( $p_settings, $shipping_method ) { + $shipping_provider = isset( $p_settings['shipping_provider'] ) ? $p_settings['shipping_provider'] : ''; + $method = self::get_provider_method( $shipping_method ); + + $method->set_shipping_provider( $shipping_provider ); + + foreach ( $p_settings as $setting_id => $setting_val ) { + if ( 'configuration_sets' === $setting_id ) { + unset( $p_settings[ $setting_id ] ); + } elseif ( $method->is_configuration_set_setting( $setting_id ) ) { + $args = $method->get_configuration_set_args_by_id( $setting_id ); + + if ( ! empty( $args['shipping_provider_name'] ) && $args['shipping_provider_name'] === $method->get_shipping_provider() ) { + if ( 'override' === $args['setting_name'] ) { + if ( wc_string_to_bool( $setting_val ) ) { + if ( $config_set = $method->get_or_create_configuration_set( $args ) ) { + $config_set->update_setting( $setting_id, $setting_val ); + } + } else { + $method->reset_configuration_sets( $args ); + } + } elseif ( $config_set = $method->get_configuration_set( $args ) ) { + $config_set->update_setting( $setting_id, $setting_val ); + } + } + + unset( $p_settings[ $setting_id ] ); + } + } + + $p_settings['configuration_sets'] = $method->get_configuration_sets(); + + /** + * Force reloading instance default settings to prevent cached values + */ + $shipping_method->instance_settings = array(); + + return $p_settings; + } + + public static function add_method_settings( $p_settings ) { + $wc = WC(); + + /** + * Prevent undefined index notices during REST API calls. + * + * @see WC_REST_Shipping_Zone_Methods_V2_Controller::get_settings() + */ + if ( is_callable( array( $wc, 'is_rest_api_request' ) ) && $wc->is_rest_api_request() ) { + return $p_settings; + } + + $shipping_provider_settings = self::get_method_settings(); + + return array_merge( $p_settings, $shipping_provider_settings ); + } + + protected static function load_all_method_settings() { + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false; + $load_all_setting_fields = false; + + if ( $screen && isset( $screen->id ) && 'woocommerce_page_wc-settings' === $screen->id ) { + $load_all_setting_fields = true; + } + + if ( + doing_action( 'wp_ajax_woocommerce_shipping_zone_methods_save_settings' ) || + doing_action( 'wp_ajax_woocommerce_shipping_zone_add_method' ) || + doing_action( 'wp_ajax_woocommerce_shipping_zone_remove_method' ) + ) { + $load_all_setting_fields = true; + } + + return $load_all_setting_fields; + } + + public static function get_method_settings( $force_load_all = false ) { + $load_all_settings = $force_load_all ? true : self::load_all_method_settings(); + $method_settings = array( + 'label_configuration_set_shipping_provider_title' => array( + 'title' => _x( 'Shipping Provider Settings', 'shipments', 'woocommerce-germanized' ), + 'type' => 'title', + 'id' => 'label_configuration_set_shipping_provider_title', + 'default' => '', + 'description' => _x( 'Adjust shipping provider settings used for managing shipments.', 'shipments', 'woocommerce-germanized' ), + ), + 'shipping_provider' => array( + 'title' => _x( 'Shipping Provider', 'shipments', 'woocommerce-germanized' ), + 'type' => 'select', + /** + * Filter to adjust default shipping provider pre-selected within shipping provider method settings. + * + * @param string $provider_name The shipping provider name e.g. dhl. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + 'default' => apply_filters( 'woocommerce_gzd_shipping_provider_method_default_provider', '' ), + 'options' => wc_gzd_get_shipping_provider_select(), + 'description' => _x( 'Choose a shipping provider which will be selected by default for an eligible shipment.', 'shipments', 'woocommerce-germanized' ), + ), + 'configuration_sets' => array( + 'title' => '', + 'type' => 'shipping_provider_method_configuration_sets', + 'default' => array(), + ), + ); + + if ( $load_all_settings ) { + if ( is_null( self::$provider_method_settings ) ) { + self::$provider_method_settings = array(); + + foreach ( wc_gzd_get_shipping_providers() as $provider ) { + if ( ! $provider->is_activated() ) { + continue; + } + + self::$provider_method_settings[ $provider->get_name() ] = $provider->get_shipping_method_settings(); + } + } + + $supported_zones = array_keys( wc_gzd_get_shipping_label_zones() ); + + foreach ( self::$provider_method_settings as $provider => $zone_settings ) { + $provider_tabs = array(); + $provider_inner_settings = array(); + + foreach ( $zone_settings as $zone => $shipment_type_settings ) { + if ( ! in_array( $zone, $supported_zones, true ) ) { + continue; + } + + foreach ( $shipment_type_settings as $shipment_type => $settings ) { + if ( ! isset( $provider_inner_settings[ $shipment_type ] ) ) { + $provider_inner_settings[ $shipment_type ] = array(); + } + + $provider_inner_settings[ $shipment_type ] = array_merge( $provider_inner_settings[ $shipment_type ], $settings ); + $provider_tabs[ $provider . '_' . $shipment_type ] = wc_gzd_get_shipment_label_title( $shipment_type ); + } + } + + if ( ! empty( $provider_inner_settings ) ) { + $tabs_open_id = "label_config_set_tabs_{$provider}"; + + $method_settings = array_merge( + $method_settings, + array( + $tabs_open_id => array( + 'id' => $tabs_open_id, + 'tabs' => $provider_tabs, + 'type' => 'shipping_provider_method_tabs', + 'default' => '', + 'display_only' => true, + 'provider' => $provider, + ), + ) + ); + + $count = 0; + + foreach ( $provider_inner_settings as $shipment_type => $settings ) { + $count ++; + + $tabs_open_id = "label_config_set_tabs_{$provider}_{$shipment_type}_open"; + $tabs_close_id = "label_config_set_tabs_{$provider}_{$shipment_type}_close"; + + $method_settings = array_merge( + $method_settings, + array( + $tabs_open_id => array( + 'id' => $tabs_open_id, + 'type' => 'shipping_provider_method_tabs_open', + 'tab' => $provider . '_' . $shipment_type, + 'default' => '', + 'provider' => $provider, + 'active' => 1 === $count ? true : false, + ), + ) + ); + + $method_settings = array_merge( $method_settings, $settings ); + + $method_settings = array_merge( + $method_settings, + array( + $tabs_close_id => array( + 'id' => $tabs_close_id, + 'type' => 'shipping_provider_method_tabs_close', + 'tab' => $provider . '_' . $shipment_type, + 'default' => '', + 'provider' => $provider, + ), + ) + ); + } + } + } + } + + /** + * Append a stop title to make sure the table is closed within settings. + */ + $method_settings = array_merge( + apply_filters( 'woocommerce_gzd_shipping_provider_method_admin_settings', $method_settings, $load_all_settings ), + array( + 'label_configuration_set_shipping_provider_stop_title' => array( + 'title' => '', + 'id' => 'label_configuration_set_shipping_provider_stop_title', + 'type' => 'title', + 'default' => '', + ), + ) + ); + + return $method_settings; + } + + public static function render_method_tab_content_close( $html, $key, $value, $method ) { + return '+ +
++ | + + | ++ + | ++ + | ++ + | ++ + | +|
---|---|---|---|---|---|---|
+ + + + | +
' . implode( ', ', array_keys( $this->get_tracking_placeholders() ) ) . '
' ) . '' . implode( ', ', array_keys( $this->get_tracking_placeholders() ) ) . '
' ) . '[gzd_return_request_form]
' ) . 'get_formatted_sender_full_name() ) ); ?>
+ + + +get_billing_first_name() ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch ?>
+ ++ get_order_number() ) ); ?> +
+ + + + + + + +get_billing_first_name() ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch ?>
+ ++ +
+ + + +get_billing_first_name() ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch ?>
+ ++ +
+ + + +get_billing_first_name() ) ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch ?>
+ ++ +
+ +
+ $shipment ) :
+ $count++;
+ ?>
+ 1 ) : ?>
+
+
+
+ get_est_delivery_date() ) : ?>
+ get_est_delivery_date(), wc_date_format() ) ); ?> + + + has_tracking() ) : ?> + get_tracking_url() ) : ?> + + + + has_tracking_instruction() ) : ?> +get_tracking_instruction() ); ?> + + + + + + |
+
get_return_instructions() ) ) . PHP_EOL ); ?>
+ diff --git a/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-address.php b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-address.php new file mode 100644 index 000000000..f355c2f80 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-address.php @@ -0,0 +1,34 @@ + + ++ + + + get_formatted_address() ); ?> + + | +
+ | + |
---|
+
+
+ get_est_delivery_date() ) : ?>
+ get_est_delivery_date(), wc_date_format() ) ); ?> + + + get_tracking_url() ) : ?> + + + + has_tracking_instruction() ) : ?> +get_tracking_instruction() ); ?> + + |
+
+ + |
---|
+ + + + + + get_type() ), $shipment->get_shipment_number() ) ); ?> + + + + + + + get_status() ) ); ?> + + has_tracking() && ! $shipment->has_status( 'delivered' ) ) : ?> + + + + + + $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + echo '' . esc_html( $action['name'] ) . ''; + } + } + ?> + + | + +
+ ' . $shipment->get_shipment_number() . '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '' . wc_format_datetime( $shipment->get_date_created() ) . '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '' . wc_gzd_get_shipment_status_name( $shipment->get_status() ) . '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + ?> +
+ + diff --git a/packages/woocommerce-germanized-shipments/templates/shipment/add-return-shipment-item.php b/packages/woocommerce-germanized-shipments/templates/shipment/add-return-shipment-item.php new file mode 100644 index 000000000..d4339fa15 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/shipment/add-return-shipment-item.php @@ -0,0 +1,90 @@ + +get_phone() ); ?>
+ + + get_email() ) : ?> +get_email() ); ?>
+ + + +get_tracking_instruction() ); ?>
+ + ++ | + |
---|
+ composer install',
+ '' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '
'
+ );
+ ?>
+