From f35778f865e7f207d23690eb8c4f5f0db3e5025d Mon Sep 17 00:00:00 2001 From: vendidero Date: Tue, 25 Apr 2023 12:44:43 +0200 Subject: [PATCH] Adding /packages directory to release --- none | 9 + package-lock.json | 2 +- .../woocommerce-eu-tax-helper/src/Helper.php | 937 ++++ .../assets/css/admin.css | 48 + .../assets/css/admin.min.css | 1 + .../assets/css/admin.scss | 76 + .../assets/css/parcel-finder.css | 155 + .../assets/css/parcel-finder.min.css | 1 + .../assets/css/parcel-finder.scss | 218 + .../assets/css/preferred-services.css | 156 + .../assets/css/preferred-services.min.css | 1 + .../assets/css/preferred-services.scss | 230 + .../assets/img/dhl-official.png | Bin 0 -> 41839 bytes .../assets/img/packstation.png | Bin 0 -> 6273 bytes .../assets/img/parcelshop.png | Bin 0 -> 5538 bytes .../assets/img/post_office.png | Bin 0 -> 4151 bytes .../assets/img/wp-int-eu-preview.png | Bin 0 -> 23720 bytes .../assets/img/wp-int-preview.png | Bin 0 -> 49144 bytes .../assets/js/admin-deutsche-post-label.js | 110 + .../js/admin-deutsche-post-label.min.js | 1 + .../assets/js/admin-internetmarke.js | 266 ++ .../assets/js/admin-internetmarke.min.js | 1 + .../assets/js/parcel-finder.js | 309 ++ .../assets/js/parcel-finder.min.js | 1 + .../assets/js/parcel-locator.js | 505 +++ .../assets/js/parcel-locator.min.js | 1 + .../assets/js/preferred-services.js | 79 + .../assets/js/preferred-services.min.js | 1 + ...undenversand-api-3.4.0-schema-bcs_base.xsd | 3760 +++++++++++++++++ ...undenversand-api-3.4.0-schema-cis_base.xsd | 1126 +++++ .../geschaeftskundenversand-api-3.4.0.wsdl | 340 ++ .../i18n/holidays.php | 57 + .../woocommerce-germanized-dhl/i18n/iso.php | 260 ++ .../includes/wc-gzd-dhl-core-functions.php | 948 +++++ .../includes/wc-gzd-dhl-legacy-functions.php | 342 ++ .../woocommerce-germanized-dhl/license.txt | 699 +++ .../src/Admin/Admin.php | 337 ++ .../src/Admin/Importer/DHL.php | 195 + .../src/Admin/Importer/Internetmarke.php | 54 + .../src/Admin/Status.php | 100 + .../woocommerce-germanized-dhl/src/Ajax.php | 98 + .../src/Api/AuthSoap.php | 66 + .../src/Api/ImPartnerInformation.php | 31 + .../src/Api/ImProductList.php | 505 +++ .../src/Api/ImProductsSoap.php | 40 + .../src/Api/ImRefundSoap.php | 99 + .../src/Api/ImWarenpostIntRest.php | 388 ++ .../src/Api/Internetmarke.php | 855 ++++ .../src/Api/LabelSoap.php | 842 ++++ .../src/Api/LocationFinder.php | 210 + .../src/Api/Paket.php | 299 ++ .../src/Api/ParcelRest.php | 53 + .../src/Api/Rest.php | 198 + .../src/Api/ReturnRest.php | 179 + .../src/Api/Soap.php | 88 + .../src/Install.php | 286 ++ .../src/Label/DHL.php | 413 ++ .../src/Label/DHLInlayReturn.php | 15 + .../src/Label/DHLReturn.php | 91 + .../src/Label/DeutschePost.php | 223 + .../src/Label/DeutschePostReturn.php | 38 + .../src/Label/Label.php | 173 + .../src/Label/ReturnLabel.php | 88 + .../src/Legacy/DataStores/Label.php | 589 +++ .../src/Legacy/DownloadHandler.php | 82 + .../src/Legacy/Helper.php | 24 + .../src/Legacy/LabelFactory.php | 29 + .../src/Legacy/LabelQuery.php | 477 +++ .../woocommerce-germanized-dhl/src/Order.php | 403 ++ .../src/Package.php | 1101 +++++ .../src/ParcelLocator.php | 1406 ++++++ .../src/ParcelServices.php | 548 +++ .../src/Product.php | 7 + .../src/ShippingProvider/DHL.php | 2075 +++++++++ .../src/ShippingProvider/DeutschePost.php | 746 ++++ .../src/ShippingProvider/ShippingMethod.php | 101 + .../checkout/dhl/parcel-finder-result.php | 57 + .../templates/checkout/dhl/parcel-finder.php | 68 + .../checkout/dhl/preferred-services.php | 132 + .../woocommerce-germanized-dhl.php | 72 + .../assets/css/admin.css | 950 +++++ .../assets/css/admin.min.css | 1 + .../assets/css/admin.scss | 1471 +++++++ .../js/admin-shipment-label-backbone.js | 327 ++ .../js/admin-shipment-label-backbone.min.js | 1 + .../assets/js/admin-shipment.js | 525 +++ .../assets/js/admin-shipment.min.js | 1 + .../assets/js/admin-shipments-table.js | 218 + .../assets/js/admin-shipments-table.min.js | 1 + .../assets/js/admin-shipments.js | 738 ++++ .../assets/js/admin-shipments.min.js | 1 + .../js/admin-shipping-provider-method.js | 119 + .../js/admin-shipping-provider-method.min.js | 1 + .../assets/js/admin-shipping-providers.js | 220 + .../assets/js/admin-shipping-providers.min.js | 1 + .../html-order-add-return-shipment-items.php | 18 + .../views/html-order-shipment-content.php | 261 ++ .../views/html-order-shipment-item-count.php | 19 + .../admin/views/html-order-shipment-item.php | 69 + .../admin/views/html-order-shipment-list.php | 36 + .../html-order-shipment-packaging-select.php | 41 + .../admin/views/html-order-shipment.php | 31 + .../admin/views/html-order-shipments.php | 94 + .../views/html-settings-provider-list.php | 76 + .../html-shipment-label-backbone-error.php | 22 + .../html-shipment-label-backbone-form.php | 17 + .../label/html-shipment-label-backbone.php | 34 + .../admin/views/label/html-shipment-label.php | 49 + ...customer-guest-return-shipment-request.php | 214 + ...ail-customer-return-shipment-delivered.php | 234 + ...-wc-gzd-email-customer-return-shipment.php | 267 ++ .../class-wc-gzd-email-customer-shipment.php | 415 ++ ...-gzd-email-new-return-shipment-request.php | 221 + .../includes/wc-gzd-label-functions.php | 152 + .../includes/wc-gzd-packaging-functions.php | 60 + .../includes/wc-gzd-shipment-functions.php | 1527 +++++++ .../wc-gzd-shipment-template-hooks.php | 28 + .../wc-gzd-shipments-template-functions.php | 204 + .../license.txt | 699 +++ .../src/AddressSplitter.php | 272 ++ .../src/Admin/Admin.php | 1193 ++++++ .../src/Admin/BulkActionHandler.php | 163 + .../src/Admin/BulkLabel.php | 215 + .../src/Admin/MetaBox.php | 178 + .../src/Admin/ProviderSettings.php | 260 ++ .../src/Admin/ReturnTable.php | 101 + .../src/Admin/Settings.php | 612 +++ .../src/Admin/Table.php | 1200 ++++++ .../src/Ajax.php | 1558 +++++++ .../src/Api.php | 215 + .../src/Automation.php | 257 ++ .../src/DataStores/Label.php | 558 +++ .../src/DataStores/Packaging.php | 694 +++ .../src/DataStores/Shipment.php | 712 ++++ .../src/DataStores/ShipmentItem.php | 354 ++ .../src/DataStores/ShippingProvider.php | 492 +++ .../src/Emails.php | 232 + .../src/FormHandler.php | 337 ++ .../src/Install.php | 361 ++ .../src/Interfaces/ShipmentLabel.php | 99 + .../src/Interfaces/ShipmentReturnLabel.php | 19 + .../src/Interfaces/ShippingProvider.php | 104 + .../src/Interfaces/ShippingProviderAuto.php | 47 + .../src/Labels/Automation.php | 134 + .../src/Labels/DownloadHandler.php | 133 + .../src/Labels/Factory.php | 91 + .../src/Labels/Label.php | 861 ++++ .../src/Labels/Query.php | 490 +++ .../src/Labels/ReturnLabel.php | 119 + .../src/Order.php | 1009 +++++ .../src/PDFMerger.php | 142 + .../src/PDFSplitter.php | 177 + .../src/Package.php | 740 ++++ .../src/Packaging.php | 339 ++ .../src/Packaging/AsyncReportGenerator.php | 195 + .../src/Packaging/Report.php | 422 ++ .../src/Packaging/ReportHelper.php | 516 +++ .../src/Packaging/ReportQueue.php | 337 ++ .../src/PackagingFactory.php | 65 + .../src/Packing/Helper.php | 31 + .../src/Packing/OrderItem.php | 117 + .../src/Packing/PackagingBox.php | 147 + .../src/Packing/ShipmentItem.php | 111 + .../src/Product.php | 130 + .../src/Rest/ShipmentsController.php | 2057 +++++++++ .../src/ReturnReason.php | 46 + .../src/ReturnShipment.php | 478 +++ .../src/Shipment.php | 2820 +++++++++++++ .../src/ShipmentError.php | 88 + .../src/ShipmentFactory.php | 88 + .../src/ShipmentItem.php | 605 +++ .../src/ShipmentQuery.php | 586 +++ .../src/ShipmentReturnItem.php | 32 + .../src/ShippingProvider/Auto.php | 570 +++ .../src/ShippingProvider/Helper.php | 217 + .../src/ShippingProvider/Method.php | 413 ++ .../ShippingProvider/MethodPlaceholder.php | 24 + .../src/ShippingProvider/Simple.php | 1231 ++++++ .../src/SimpleShipment.php | 379 ++ .../src/Validation.php | 234 + .../src/WPMLHelper.php | 166 + .../admin-new-return-shipment-request.php | 60 + ...customer-guest-return-shipment-request.php | 50 + .../customer-return-shipment-delivered.php | 61 + .../emails/customer-return-shipment.php | 67 + .../templates/emails/customer-shipment.php | 69 + .../emails/email-order-shipments.php | 53 + .../email-return-shipment-instructions.php | 26 + .../emails/email-shipment-address.php | 34 + .../emails/email-shipment-details.php | 87 + .../templates/emails/email-shipment-items.php | 114 + .../emails/email-shipment-tracking.php | 42 + .../admin-new-return-shipment-request.php | 41 + ...customer-guest-return-shipment-request.php | 40 + .../customer-return-shipment-delivered.php | 43 + .../emails/plain/customer-return-shipment.php | 48 + .../emails/plain/customer-shipment.php | 49 + .../emails/plain/email-order-shipments.php | 47 + .../email-return-shipment-instructions.php | 23 + .../emails/plain/email-shipment-address.php | 20 + .../emails/plain/email-shipment-details.php | 43 + .../emails/plain/email-shipment-items.php | 46 + .../emails/plain/email-shipment-tracking.php | 33 + .../templates/global/empty.php | 21 + .../templates/global/form-return-request.php | 51 + .../myaccount/add-return-shipment.php | 96 + .../templates/myaccount/order-shipments.php | 53 + .../templates/myaccount/shipments.php | 113 + .../templates/myaccount/view-shipment.php | 42 + .../shipment/add-return-shipment-item.php | 90 + .../shipment/shipment-details-address.php | 52 + .../shipment/shipment-details-item.php | 93 + .../shipment/shipment-details-tracking.php | 44 + .../templates/shipment/shipment-details.php | 123 + .../shipment/shipment-return-instructions.php | 41 + .../woocommerce-germanized-shipments.php | 76 + 216 files changed, 62447 insertions(+), 1 deletion(-) create mode 100644 none create mode 100644 packages/woocommerce-eu-tax-helper/src/Helper.php create mode 100644 packages/woocommerce-germanized-dhl/assets/css/admin.css create mode 100644 packages/woocommerce-germanized-dhl/assets/css/admin.min.css create mode 100644 packages/woocommerce-germanized-dhl/assets/css/admin.scss create mode 100644 packages/woocommerce-germanized-dhl/assets/css/parcel-finder.css create mode 100644 packages/woocommerce-germanized-dhl/assets/css/parcel-finder.min.css create mode 100644 packages/woocommerce-germanized-dhl/assets/css/parcel-finder.scss create mode 100644 packages/woocommerce-germanized-dhl/assets/css/preferred-services.css create mode 100644 packages/woocommerce-germanized-dhl/assets/css/preferred-services.min.css create mode 100644 packages/woocommerce-germanized-dhl/assets/css/preferred-services.scss create mode 100755 packages/woocommerce-germanized-dhl/assets/img/dhl-official.png create mode 100755 packages/woocommerce-germanized-dhl/assets/img/packstation.png create mode 100755 packages/woocommerce-germanized-dhl/assets/img/parcelshop.png create mode 100755 packages/woocommerce-germanized-dhl/assets/img/post_office.png create mode 100644 packages/woocommerce-germanized-dhl/assets/img/wp-int-eu-preview.png create mode 100644 packages/woocommerce-germanized-dhl/assets/img/wp-int-preview.png create mode 100644 packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.min.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.min.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/parcel-finder.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/parcel-finder.min.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/parcel-locator.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/parcel-locator.min.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/preferred-services.js create mode 100644 packages/woocommerce-germanized-dhl/assets/js/preferred-services.min.js create mode 100644 packages/woocommerce-germanized-dhl/assets/wsdl/geschaeftskundenversand-api-3.4.0-schema-bcs_base.xsd create mode 100644 packages/woocommerce-germanized-dhl/assets/wsdl/geschaeftskundenversand-api-3.4.0-schema-cis_base.xsd create mode 100644 packages/woocommerce-germanized-dhl/assets/wsdl/geschaeftskundenversand-api-3.4.0.wsdl create mode 100644 packages/woocommerce-germanized-dhl/i18n/holidays.php create mode 100644 packages/woocommerce-germanized-dhl/i18n/iso.php create mode 100644 packages/woocommerce-germanized-dhl/includes/wc-gzd-dhl-core-functions.php create mode 100644 packages/woocommerce-germanized-dhl/includes/wc-gzd-dhl-legacy-functions.php create mode 100644 packages/woocommerce-germanized-dhl/license.txt create mode 100644 packages/woocommerce-germanized-dhl/src/Admin/Admin.php create mode 100644 packages/woocommerce-germanized-dhl/src/Admin/Importer/DHL.php create mode 100644 packages/woocommerce-germanized-dhl/src/Admin/Importer/Internetmarke.php create mode 100644 packages/woocommerce-germanized-dhl/src/Admin/Status.php create mode 100644 packages/woocommerce-germanized-dhl/src/Ajax.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/AuthSoap.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/ImPartnerInformation.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/ImProductList.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/ImProductsSoap.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/ImRefundSoap.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/ImWarenpostIntRest.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/Internetmarke.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/LabelSoap.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/LocationFinder.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/Paket.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/ParcelRest.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/Rest.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/ReturnRest.php create mode 100644 packages/woocommerce-germanized-dhl/src/Api/Soap.php create mode 100644 packages/woocommerce-germanized-dhl/src/Install.php create mode 100644 packages/woocommerce-germanized-dhl/src/Label/DHL.php create mode 100644 packages/woocommerce-germanized-dhl/src/Label/DHLInlayReturn.php create mode 100644 packages/woocommerce-germanized-dhl/src/Label/DHLReturn.php create mode 100644 packages/woocommerce-germanized-dhl/src/Label/DeutschePost.php create mode 100644 packages/woocommerce-germanized-dhl/src/Label/DeutschePostReturn.php create mode 100644 packages/woocommerce-germanized-dhl/src/Label/Label.php create mode 100644 packages/woocommerce-germanized-dhl/src/Label/ReturnLabel.php create mode 100644 packages/woocommerce-germanized-dhl/src/Legacy/DataStores/Label.php create mode 100644 packages/woocommerce-germanized-dhl/src/Legacy/DownloadHandler.php create mode 100644 packages/woocommerce-germanized-dhl/src/Legacy/Helper.php create mode 100644 packages/woocommerce-germanized-dhl/src/Legacy/LabelFactory.php create mode 100644 packages/woocommerce-germanized-dhl/src/Legacy/LabelQuery.php create mode 100644 packages/woocommerce-germanized-dhl/src/Order.php create mode 100644 packages/woocommerce-germanized-dhl/src/Package.php create mode 100644 packages/woocommerce-germanized-dhl/src/ParcelLocator.php create mode 100644 packages/woocommerce-germanized-dhl/src/ParcelServices.php create mode 100644 packages/woocommerce-germanized-dhl/src/Product.php create mode 100644 packages/woocommerce-germanized-dhl/src/ShippingProvider/DHL.php create mode 100644 packages/woocommerce-germanized-dhl/src/ShippingProvider/DeutschePost.php create mode 100644 packages/woocommerce-germanized-dhl/src/ShippingProvider/ShippingMethod.php create mode 100644 packages/woocommerce-germanized-dhl/templates/checkout/dhl/parcel-finder-result.php create mode 100644 packages/woocommerce-germanized-dhl/templates/checkout/dhl/parcel-finder.php create mode 100644 packages/woocommerce-germanized-dhl/templates/checkout/dhl/preferred-services.php create mode 100644 packages/woocommerce-germanized-dhl/woocommerce-germanized-dhl.php create mode 100644 packages/woocommerce-germanized-shipments/assets/css/admin.css create mode 100644 packages/woocommerce-germanized-shipments/assets/css/admin.min.css create mode 100644 packages/woocommerce-germanized-shipments/assets/css/admin.scss create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipment-label-backbone.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipment-label-backbone.min.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipment.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipment.min.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipments-table.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipments-table.min.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipments.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipments.min.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipping-provider-method.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipping-provider-method.min.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipping-providers.js create mode 100644 packages/woocommerce-germanized-shipments/assets/js/admin-shipping-providers.min.js create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-add-return-shipment-items.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-content.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item-count.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-list.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-packaging-select.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipments.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/html-settings-provider-list.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-error.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-form.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone.php create mode 100644 packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label.php create mode 100644 packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-guest-return-shipment-request.php create mode 100644 packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment-delivered.php create mode 100644 packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-new-return-shipment-request.php create mode 100644 packages/woocommerce-germanized-shipments/includes/wc-gzd-label-functions.php create mode 100644 packages/woocommerce-germanized-shipments/includes/wc-gzd-packaging-functions.php create mode 100644 packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-functions.php create mode 100644 packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-template-hooks.php create mode 100644 packages/woocommerce-germanized-shipments/includes/wc-gzd-shipments-template-functions.php create mode 100644 packages/woocommerce-germanized-shipments/license.txt create mode 100644 packages/woocommerce-germanized-shipments/src/AddressSplitter.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/Admin.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/BulkActionHandler.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/BulkLabel.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/MetaBox.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/ProviderSettings.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/ReturnTable.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/Settings.php create mode 100644 packages/woocommerce-germanized-shipments/src/Admin/Table.php create mode 100644 packages/woocommerce-germanized-shipments/src/Ajax.php create mode 100644 packages/woocommerce-germanized-shipments/src/Api.php create mode 100644 packages/woocommerce-germanized-shipments/src/Automation.php create mode 100644 packages/woocommerce-germanized-shipments/src/DataStores/Label.php create mode 100644 packages/woocommerce-germanized-shipments/src/DataStores/Packaging.php create mode 100644 packages/woocommerce-germanized-shipments/src/DataStores/Shipment.php create mode 100644 packages/woocommerce-germanized-shipments/src/DataStores/ShipmentItem.php create mode 100644 packages/woocommerce-germanized-shipments/src/DataStores/ShippingProvider.php create mode 100644 packages/woocommerce-germanized-shipments/src/Emails.php create mode 100644 packages/woocommerce-germanized-shipments/src/FormHandler.php create mode 100644 packages/woocommerce-germanized-shipments/src/Install.php create mode 100644 packages/woocommerce-germanized-shipments/src/Interfaces/ShipmentLabel.php create mode 100644 packages/woocommerce-germanized-shipments/src/Interfaces/ShipmentReturnLabel.php create mode 100644 packages/woocommerce-germanized-shipments/src/Interfaces/ShippingProvider.php create mode 100644 packages/woocommerce-germanized-shipments/src/Interfaces/ShippingProviderAuto.php create mode 100644 packages/woocommerce-germanized-shipments/src/Labels/Automation.php create mode 100644 packages/woocommerce-germanized-shipments/src/Labels/DownloadHandler.php create mode 100644 packages/woocommerce-germanized-shipments/src/Labels/Factory.php create mode 100644 packages/woocommerce-germanized-shipments/src/Labels/Label.php create mode 100644 packages/woocommerce-germanized-shipments/src/Labels/Query.php create mode 100644 packages/woocommerce-germanized-shipments/src/Labels/ReturnLabel.php create mode 100644 packages/woocommerce-germanized-shipments/src/Order.php create mode 100644 packages/woocommerce-germanized-shipments/src/PDFMerger.php create mode 100644 packages/woocommerce-germanized-shipments/src/PDFSplitter.php create mode 100644 packages/woocommerce-germanized-shipments/src/Package.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packaging.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packaging/AsyncReportGenerator.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packaging/Report.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packaging/ReportHelper.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packaging/ReportQueue.php create mode 100644 packages/woocommerce-germanized-shipments/src/PackagingFactory.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packing/Helper.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packing/OrderItem.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packing/PackagingBox.php create mode 100644 packages/woocommerce-germanized-shipments/src/Packing/ShipmentItem.php create mode 100644 packages/woocommerce-germanized-shipments/src/Product.php create mode 100644 packages/woocommerce-germanized-shipments/src/Rest/ShipmentsController.php create mode 100644 packages/woocommerce-germanized-shipments/src/ReturnReason.php create mode 100644 packages/woocommerce-germanized-shipments/src/ReturnShipment.php create mode 100644 packages/woocommerce-germanized-shipments/src/Shipment.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShipmentError.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShipmentFactory.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShipmentItem.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShipmentQuery.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShipmentReturnItem.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShippingProvider/Auto.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShippingProvider/Helper.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShippingProvider/Method.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShippingProvider/MethodPlaceholder.php create mode 100644 packages/woocommerce-germanized-shipments/src/ShippingProvider/Simple.php create mode 100644 packages/woocommerce-germanized-shipments/src/SimpleShipment.php create mode 100644 packages/woocommerce-germanized-shipments/src/Validation.php create mode 100644 packages/woocommerce-germanized-shipments/src/WPMLHelper.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/admin-new-return-shipment-request.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/customer-guest-return-shipment-request.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/customer-return-shipment-delivered.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/customer-return-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/customer-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/email-order-shipments.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/email-return-shipment-instructions.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/email-shipment-address.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/email-shipment-details.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/email-shipment-items.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/email-shipment-tracking.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/admin-new-return-shipment-request.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/customer-guest-return-shipment-request.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment-delivered.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/customer-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/email-order-shipments.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/email-return-shipment-instructions.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-address.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-details.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-items.php create mode 100644 packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-tracking.php create mode 100644 packages/woocommerce-germanized-shipments/templates/global/empty.php create mode 100644 packages/woocommerce-germanized-shipments/templates/global/form-return-request.php create mode 100644 packages/woocommerce-germanized-shipments/templates/myaccount/add-return-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/templates/myaccount/order-shipments.php create mode 100644 packages/woocommerce-germanized-shipments/templates/myaccount/shipments.php create mode 100644 packages/woocommerce-germanized-shipments/templates/myaccount/view-shipment.php create mode 100644 packages/woocommerce-germanized-shipments/templates/shipment/add-return-shipment-item.php create mode 100644 packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-address.php create mode 100644 packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-item.php create mode 100644 packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-tracking.php create mode 100644 packages/woocommerce-germanized-shipments/templates/shipment/shipment-details.php create mode 100644 packages/woocommerce-germanized-shipments/templates/shipment/shipment-return-instructions.php create mode 100644 packages/woocommerce-germanized-shipments/woocommerce-germanized-shipments.php diff --git a/none b/none new file mode 100644 index 000000000..c91e1052d --- /dev/null +++ b/none @@ -0,0 +1,9 @@ +{ + "version": 3, + "file": "assets/css/admin.css", + "sources": [ + "assets/css/admin.scss" + ], + "names": [], + "mappings": "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" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index be73728ad..9a274e840 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "woocommerce-germanized", - "version": "3.9.2", + "version": "3.12.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/woocommerce-eu-tax-helper/src/Helper.php b/packages/woocommerce-eu-tax-helper/src/Helper.php new file mode 100644 index 000000000..10656f979 --- /dev/null +++ b/packages/woocommerce-eu-tax-helper/src/Helper.php @@ -0,0 +1,937 @@ +countries ) { + return array(); + } + + $countries = WC()->countries->get_european_union_countries(); + + return $countries; + } + + public static function get_eu_vat_countries() { + $vat_countries = WC()->countries ? WC()->countries->get_european_union_countries( 'eu_vat' ) : array(); + + return apply_filters( 'woocommerce_eu_tax_helper_eu_vat_countries', $vat_countries ); + } + + public static function is_northern_ireland( $country, $postcode = '' ) { + if ( 'GB' === $country && 'BT' === strtoupper( substr( trim( $postcode ), 0, 2 ) ) ) { + return true; + } elseif ( 'IX' === $country ) { + return true; + } + + return false; + } + + public static function is_eu_vat_country( $country, $postcode = '' ) { + $country = wc_strtoupper( $country ); + $postcode = wc_normalize_postcode( $postcode ); + $is_eu_vat_country = in_array( $country, self::get_eu_vat_countries(), true ); + + if ( self::is_northern_ireland( $country, $postcode ) ) { + $is_eu_vat_country = true; + } elseif ( self::is_eu_vat_postcode_exemption( $country, $postcode ) ) { + $is_eu_vat_country = false; + } + + return apply_filters( 'woocommerce_eu_tax_helper_is_eu_vat_country', $is_eu_vat_country, $country, $postcode ); + } + + public static function is_third_country( $country, $postcode = '' ) { + $is_third_country = true; + + /** + * In case the base country is within EU consider all non-EU VAT countries as third countries. + * In any other case consider every non-base-country as third country. + */ + if ( in_array( self::get_base_country(), self::get_eu_vat_countries(), true ) ) { + $is_third_country = ! self::is_eu_vat_country( $country, $postcode ); + } else { + $is_third_country = self::get_base_country() !== $country; + } + + return apply_filters( 'woocommerce_eu_tax_helper_is_third_country', $is_third_country, $country, $postcode ); + } + + public static function is_eu_country( $country ) { + return in_array( $country, self::get_eu_countries(), true ); + } + + public static function is_eu_vat_postcode_exemption( $country, $postcode = '' ) { + $country = wc_strtoupper( $country ); + $postcode = wc_normalize_postcode( $postcode ); + $exemptions = self::get_vat_postcode_exemptions_by_country(); + $is_exempt = false; + + if ( ! empty( $postcode ) && in_array( $country, self::get_eu_vat_countries(), true ) ) { + if ( array_key_exists( $country, $exemptions ) ) { + $wildcards = wc_get_wildcard_postcodes( $postcode, $country ); + + foreach ( $exemptions[ $country ] as $exempt_postcode ) { + if ( in_array( $exempt_postcode, $wildcards, true ) ) { + $is_exempt = true; + break; + } + } + } + } + + return $is_exempt; + } + + /** + * Get VAT exemptions (of EU countries) for certain postcodes (e.g. canary islands) + * + * @see https://www.hk24.de/produktmarken/beratung-service/recht-und-steuern/steuerrecht/umsatzsteuer-mehrwertsteuer/umsatzsteuer-mehrwertsteuer-international/verfahrensrecht/territoriale-besonderheiten-umsatzsteuer-zollrecht-1167674 + * @see https://github.com/woocommerce/woocommerce/issues/5143 + * @see https://ec.europa.eu/taxation_customs/business/vat/eu-vat-rules-topic/territorial-status-eu-countries-certain-territories_en + * + * @return \string[][] + */ + public static function get_vat_postcode_exemptions_by_country( $country = '' ) { + $country = wc_strtoupper( $country ); + + $exemptions = array( + 'DE' => array( + '27498', // Helgoland + '78266', // Büsingen am Hochrhein + ), + 'ES' => array( + '35*', // Canary Islands + '38*', // Canary Islands + '51*', // Ceuta + '52*', // Melilla + ), + 'GR' => array( + '63086', // Mount Athos + '63087', // Mount Athos + ), + 'FR' => array( + '971*', // Guadeloupe + '972*', // Martinique + '973*', // French Guiana + '974*', // Réunion + '976*', // Mayotte + ), + 'IT' => array( + '22060', // Livigno, Campione d’Italia + '23030', // Lake Lugano + ), + 'FI' => array( + '22*', // Aland islands + ), + ); + + if ( empty( $country ) ) { + return $exemptions; + } elseif ( array_key_exists( $country, $exemptions ) ) { + return $exemptions[ $country ]; + } else { + return array(); + } + } + + /** + * @param integer|\WC_Order $order + * + * @return array + */ + public static function get_order_taxable_location( $order ) { + $order = is_a( $order, 'WC_Order' ) ? $order : wc_get_order( $order ); + + $taxable_address = array( + WC()->countries->get_base_country(), + WC()->countries->get_base_state(), + WC()->countries->get_base_postcode(), + WC()->countries->get_base_city(), + ); + + if ( ! $order ) { + return $taxable_address; + } + + $tax_based_on = get_option( 'woocommerce_tax_based_on' ); + + if ( is_a( $order, 'WC_Order_Refund' ) ) { + $order = wc_get_order( $order->get_parent_id() ); + + if ( ! $order ) { + return $taxable_address; + } + } + + /** + * Shipping address data does not exist + */ + if ( 'shipping' === $tax_based_on && ! $order->get_shipping_country() ) { + $tax_based_on = 'billing'; + } + + $is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $order->get_meta( 'is_vat_exempt' ), $order ); + + /** + * In case the order is a VAT exempt, calculate net prices based on taxes from base country. + */ + if ( $is_vat_exempt ) { + $tax_based_on = 'base'; + } + + $country = 'shipping' === $tax_based_on ? $order->get_shipping_country() : $order->get_billing_country(); + + if ( 'base' !== $tax_based_on && ! empty( $country ) ) { + $taxable_address = array( + $country, + 'billing' === $tax_based_on ? $order->get_billing_state() : $order->get_shipping_state(), + 'billing' === $tax_based_on ? $order->get_billing_postcode() : $order->get_shipping_postcode(), + 'billing' === $tax_based_on ? $order->get_billing_city() : $order->get_shipping_city(), + ); + } + + return $taxable_address; + } + + public static function get_taxable_location() { + $is_admin_order_request = self::is_admin_order_request(); + + if ( $is_admin_order_request ) { + $taxable_address = array( + WC()->countries->get_base_country(), + WC()->countries->get_base_state(), + WC()->countries->get_base_postcode(), + WC()->countries->get_base_city(), + ); + + if ( isset( $_POST['order_id'] ) && ( $order = wc_get_order( absint( $_POST['order_id'] ) ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $taxable_address = self::get_order_taxable_location( $order ); + } + + return $taxable_address; + } else { + return \WC_Tax::get_tax_location(); + } + } + + public static function is_admin_order_ajax_request() { + $order_actions = array( 'woocommerce_calc_line_taxes', 'woocommerce_save_order_items', 'add_coupon_discount', 'refund_line_items', 'delete_refund' ); + + return isset( $_POST['action'], $_POST['order_id'] ) && ( strstr( wc_clean( wp_unslash( $_POST['action'] ) ), '_order_' ) || in_array( wc_clean( wp_unslash( $_POST['action'] ) ), $order_actions, true ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + public static function is_admin_order_request() { + return is_admin() && current_user_can( 'edit_shop_orders' ) && self::is_admin_order_ajax_request(); + } + + public static function current_request_has_vat_exempt() { + $is_admin_order_request = self::is_admin_order_request(); + $is_vat_exempt = false; + + if ( $is_admin_order_request ) { + if ( $order = wc_get_order( absint( $_POST['order_id'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotValidated + $is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $order->get_meta( 'is_vat_exempt' ), $order ); + } + } else { + if ( WC()->customer && WC()->customer->is_vat_exempt() ) { + $is_vat_exempt = true; + } + } + + return $is_vat_exempt; + } + + public static function get_base_country() { + if ( WC()->countries ) { + return WC()->countries->get_base_country(); + } else { + return wc_get_base_location()['country']; + } + } + + /** + * Returns a list of EU countries except base country. + * + * @return string[] + */ + public static function get_non_base_eu_countries( $include_gb = false ) { + $countries = self::get_eu_vat_countries(); + + /** + * Include GB to allow Northern Ireland + */ + if ( $include_gb && ! in_array( 'GB', $countries, true ) ) { + $countries = array_merge( $countries, array( 'GB' ) ); + } + + $base_country = self::get_base_country(); + $countries = array_diff( $countries, array( $base_country ) ); + + return $countries; + } + + public static function country_supports_eu_vat( $country, $postcode = '' ) { + return self::is_eu_vat_country( $country, $postcode ); + } + + public static function import_oss_tax_rates( $tax_class_slug_names = array() ) { + self::import_tax_rates_internal( true, $tax_class_slug_names ); + } + + public static function import_default_tax_rates( $tax_class_slug_names = array() ) { + self::import_tax_rates_internal( false, $tax_class_slug_names ); + } + + public static function import_tax_rates( $tax_class_slug_names = array() ) { + self::import_tax_rates_internal( self::oss_procedure_is_enabled(), $tax_class_slug_names ); + } + + protected static function parse_tax_class_slug_names( $tax_class_slug_names = array() ) { + return wp_parse_args( + $tax_class_slug_names, + array( + 'reduced' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_reduced_name', __( 'Reduced rate', 'woocommerce' ) ), // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + 'greater-reduced' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_greater_reduced_name', _x( 'Greater reduced rate', 'tax-helper-tax-class-name', 'woocommerce-germanized' ) ), + 'super-reduced' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_super_reduced_name', _x( 'Super reduced rate', 'tax-helper-tax-class-name', 'woocommerce-germanized' ) ), + 'zero' => apply_filters( 'woocommerce_eu_tax_helper_tax_class_zero_name', __( 'Zero rate', 'woocommerce' ) ), // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + ) + ); + } + + protected static function import_tax_rates_internal( $is_oss = true, $tax_class_slug_names = array() ) { + self::clear_cache(); + + $tax_class_slugs = self::get_tax_class_slugs( $tax_class_slug_names ); + $tax_class_slug_names = self::parse_tax_class_slug_names( $tax_class_slug_names ); + $eu_rates = self::get_eu_tax_rates(); + + foreach ( $tax_class_slugs as $tax_class_type => $class ) { + /** + * Maybe create missing tax classes + */ + if ( false === $class ) { + switch ( $tax_class_type ) { + case 'reduced': + /* translators: Do not translate */ + \WC_Tax::create_tax_class( $tax_class_slug_names['reduced'] ); + break; + case 'greater-reduced': + \WC_Tax::create_tax_class( $tax_class_slug_names['greater-reduced'] ); + break; + case 'super-reduced': + \WC_Tax::create_tax_class( $tax_class_slug_names['super-reduced'] ); + break; + case 'zero': + \WC_Tax::create_tax_class( $tax_class_slug_names['zero'] ); + break; + } + } + + $new_rates = array(); + + if ( 'zero' === $tax_class_type ) { + $new_rates = array( + array( + 'country' => '*', + 'rate' => 0.0, + 'name' => '', + ), + ); + } else { + foreach ( $eu_rates as $country => $rates_data ) { + /** + * Use base country rates in case OSS is disabled + */ + if ( ! $is_oss ) { + $base_country = self::get_base_country(); + + if ( isset( $eu_rates[ $base_country ] ) ) { + /** + * In case the country includes multiple rules (e.g. postcode exempts) by default + * do only use the last rule (which does not include exempts) to construct non-base country tax rules. + */ + if ( $base_country !== $country ) { + $base_country_base_rate = array_values( array_slice( $eu_rates[ $base_country ], -1 ) )[0]; + + foreach ( $rates_data as $key => $rate_data ) { + $rates_data[ $key ] = array_replace_recursive( $rate_data, $base_country_base_rate ); + + foreach ( $tax_class_slugs as $tmp_class_type => $class_data ) { + /** + * Do not include tax classes which are not supported by the base country. + */ + if ( isset( $rates_data[ $key ][ $tmp_class_type ] ) && ! isset( $base_country_base_rate[ $tmp_class_type ] ) ) { + unset( $rates_data[ $key ][ $tmp_class_type ] ); + } elseif ( isset( $rates_data[ $key ][ $tmp_class_type ] ) ) { + /** + * Replace tax class data with base data to make sure that reduced + * classes have the same dimensions + */ + $rates_data[ $key ][ $tmp_class_type ] = $base_country_base_rate[ $tmp_class_type ]; + + /** + * In case this is an exempt make sure to replace with zero tax rates + */ + if ( isset( $rate_data['is_exempt'] ) && $rate_data['is_exempt'] ) { + if ( is_array( $rates_data[ $key ][ $tmp_class_type ] ) ) { + foreach ( $rates_data[ $key ][ $tmp_class_type ] as $k => $rate ) { + $rates_data[ $key ][ $tmp_class_type ][ $k ] = 0; + } + } else { + $rates_data[ $key ][ $tmp_class_type ] = 0; + } + } + } + } + } + } + } else { + continue; + } + } + + /** + * Each country may contain multiple tax rates + */ + foreach ( $rates_data as $rates ) { + $rates = wp_parse_args( + $rates, + array( + 'name' => '', + 'postcodes' => array(), + 'reduced' => array(), + ) + ); + + if ( ! empty( $rates['postcode'] ) ) { + foreach ( $rates['postcode'] as $postcode ) { + $tax_rate = self::get_single_tax_rate_data( $tax_class_type, $rates, $country, $postcode ); + + if ( false !== $tax_rate ) { + $new_rates[] = $tax_rate; + } + } + } else { + $tax_rate = self::get_single_tax_rate_data( $tax_class_type, $rates, $country ); + + if ( false !== $tax_rate ) { + $new_rates[] = $tax_rate; + } + } + } + } + } + + self::import_rates( $new_rates, $class, $tax_class_type ); + } + } + + private static function get_single_tax_rate_data( $tax_class_type, $rates, $country, $postcode = '' ) { + $rates = wp_parse_args( + $rates, + array( + 'name' => '', + 'reduced' => array(), + ) + ); + + $single_rate = array( + 'name' => $rates['name'], + 'rate' => false, + 'country' => $country, + 'postcode' => $postcode, + ); + + switch ( $tax_class_type ) { + case 'greater-reduced': + if ( count( $rates['reduced'] ) > 1 ) { + $single_rate['rate'] = $rates['reduced'][1]; + } + break; + case 'reduced': + if ( ! empty( $rates['reduced'] ) ) { + $single_rate['rate'] = $rates['reduced'][0]; + } + break; + default: + if ( isset( $rates[ $tax_class_type ] ) ) { + $single_rate['rate'] = $rates[ $tax_class_type ]; + } + break; + } + + if ( false === $single_rate['rate'] ) { + return false; + } + + return $single_rate; + } + + protected static function clear_cache() { + $cache_key = \WC_Cache_Helper::get_cache_prefix( 'taxes' ) . 'eu_tax_helper_tax_class_slugs'; + + wp_cache_delete( $cache_key, 'taxes' ); + } + + public static function get_tax_class_slugs( $tax_class_slug_names = array() ) { + $tax_class_slug_names = self::parse_tax_class_slug_names( $tax_class_slug_names ); + $cache_key = \WC_Cache_Helper::get_cache_prefix( 'taxes' ) . 'eu_tax_helper_tax_class_slugs'; + $slugs = wp_cache_get( $cache_key, 'taxes' ); + + if ( false === $slugs ) { + $reduced_tax_class = false; + $greater_reduced_tax_class = false; + $super_reduced_tax_class = false; + $zero_tax_class = false; + $tax_classes = \WC_Tax::get_tax_class_slugs(); + + /** + * Try to determine the reduced tax rate class + */ + foreach ( $tax_classes as $slug ) { + if ( strstr( $slug, 'virtual' ) ) { + continue; + } + + if ( ! $greater_reduced_tax_class && strstr( $slug, sanitize_title( 'Greater reduced rate' ) ) ) { + $greater_reduced_tax_class = $slug; + } elseif ( ! $greater_reduced_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['greater-reduced'] ) ) ) { + $greater_reduced_tax_class = $slug; + } elseif ( ! $super_reduced_tax_class && strstr( $slug, sanitize_title( 'Super reduced rate' ) ) ) { + $super_reduced_tax_class = $slug; + } elseif ( ! $super_reduced_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['super-reduced'] ) ) ) { + $super_reduced_tax_class = $slug; + } elseif ( ! $reduced_tax_class && strstr( $slug, sanitize_title( 'Reduced rate' ) ) ) { + $reduced_tax_class = $slug; + } elseif ( ! $reduced_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['reduced'] ) ) ) { // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + $reduced_tax_class = $slug; + } elseif ( ! $reduced_tax_class && strstr( $slug, 'reduced' ) ) { + $reduced_tax_class = $slug; + } elseif ( ! $zero_tax_class && strstr( $slug, sanitize_title( $tax_class_slug_names['zero'] ) ) ) { + $zero_tax_class = $slug; + } elseif ( ! $zero_tax_class && strstr( $slug, 'zero' ) ) { + $zero_tax_class = $slug; + } + } + + $slugs = array( + 'reduced' => $reduced_tax_class, + 'greater-reduced' => $greater_reduced_tax_class, + 'super-reduced' => $super_reduced_tax_class, + 'standard' => '', + 'zero' => $zero_tax_class, + ); + + wp_cache_set( $cache_key, $slugs, 'taxes' ); + } + + return apply_filters( 'woocommerce_eu_tax_helper_tax_rate_class_slugs', $slugs ); + } + + public static function get_tax_type_by_country_rate( $rate_percentage, $country ) { + $country = strtoupper( $country ); + + /** + * Map northern ireland to GB + */ + if ( 'XI' === $country ) { + $country = 'GB'; + } + + $eu_rates = self::get_eu_tax_rates(); + $tax_type = 'standard'; + + if ( array_key_exists( $country, $eu_rates ) ) { + $rates = $eu_rates[ $country ]; + + foreach ( $rates as $rate ) { + foreach ( $rate as $tax_rate_type => $tax_rate_percent ) { + if ( ( is_array( $tax_rate_percent ) && in_array( $rate_percentage, $tax_rate_percent, true ) ) || (float) $tax_rate_percent === (float) $rate_percentage ) { + $tax_type = $tax_rate_type; + break; + } + } + } + } + + return apply_filters( 'woocommerce_eu_tax_helper_country_rate_tax_type', $tax_type, $country, $rate_percentage ); + } + + public static function get_eu_tax_rates() { + /** + * @see https://europa.eu/youreurope/business/taxation/vat/vat-rules-rates/index_en.htm + * + * Include Great Britain to allow including Norther Ireland + */ + $rates = array( + 'AT' => array( + array( + 'standard' => 20, + 'reduced' => array( 10, 13 ), + ), + ), + 'BE' => array( + array( + 'standard' => 21, + 'reduced' => array( 6, 12 ), + ), + ), + 'BG' => array( + array( + 'standard' => 20, + 'reduced' => array( 9 ), + ), + ), + 'CY' => array( + array( + 'standard' => 19, + 'reduced' => array( 5, 9 ), + ), + ), + 'CZ' => array( + array( + 'standard' => 21, + 'reduced' => array( 10, 15 ), + ), + ), + 'DE' => array( + array( + 'standard' => 19, + 'reduced' => array( 7 ), + ), + ), + 'DK' => array( + array( + 'standard' => 25, + 'reduced' => array(), + ), + ), + 'EE' => array( + array( + 'standard' => 20, + 'reduced' => array( 9 ), + ), + ), + 'GR' => array( + array( + 'standard' => 24, + 'reduced' => array( 6, 13 ), + ), + ), + 'ES' => array( + array( + 'standard' => 21, + 'reduced' => array( 10 ), + 'super-reduced' => 4, + ), + ), + 'FI' => array( + array( + 'standard' => 24, + 'reduced' => array( 10, 14 ), + ), + ), + 'FR' => array( + array( + 'standard' => 20, + 'reduced' => array( 5.5, 10 ), + 'super-reduced' => 2.1, + ), + ), + 'HR' => array( + array( + 'standard' => 25, + 'reduced' => array( 5, 13 ), + ), + ), + 'HU' => array( + array( + 'standard' => 27, + 'reduced' => array( 5, 18 ), + ), + ), + 'IE' => array( + array( + 'standard' => 23, + 'reduced' => array( 9, 13.5 ), + 'super-reduced' => 4.8, + ), + ), + 'IT' => array( + array( + 'standard' => 22, + 'reduced' => array( 5, 10 ), + 'super-reduced' => 4, + ), + ), + 'LT' => array( + array( + 'standard' => 21, + 'reduced' => array( 5, 9 ), + ), + ), + 'LU' => array( + array( + 'standard' => 16, + 'reduced' => array( 7 ), + 'super-reduced' => 3, + ), + ), + 'LV' => array( + array( + 'standard' => 21, + 'reduced' => array( 12, 5 ), + ), + ), + 'MC' => array( + array( + 'standard' => 20, + 'reduced' => array( 5.5, 10 ), + 'super-reduced' => 2.1, + ), + ), + 'MT' => array( + array( + 'standard' => 18, + 'reduced' => array( 5, 7 ), + ), + ), + 'NL' => array( + array( + 'standard' => 21, + 'reduced' => array( 9 ), + ), + ), + 'PL' => array( + array( + 'standard' => 23, + 'reduced' => array( 5, 8 ), + ), + ), + 'PT' => array( + array( + // Madeira + 'postcode' => array( '90*', '91*', '92*', '93*', '94*' ), + 'standard' => 22, + 'reduced' => array( 5, 12 ), + 'name' => _x( 'Madeira', 'tax-helper', 'woocommerce-germanized' ), + ), + array( + // Acores + 'postcode' => array( '95*', '96*', '97*', '98*', '99*' ), + 'standard' => 18, + 'reduced' => array( 4, 9 ), + 'name' => _x( 'Acores', 'tax-helper', 'woocommerce-germanized' ), + ), + array( + 'standard' => 23, + 'reduced' => array( 6, 13 ), + ), + ), + 'RO' => array( + array( + 'standard' => 19, + 'reduced' => array( 5, 9 ), + ), + ), + 'SE' => array( + array( + 'standard' => 25, + 'reduced' => array( 6, 12 ), + ), + ), + 'SI' => array( + array( + 'standard' => 22, + 'reduced' => array( 9.5 ), + ), + ), + 'SK' => array( + array( + 'standard' => 20, + 'reduced' => array( 10 ), + ), + ), + 'GB' => array( + array( + 'standard' => 20, + 'reduced' => array( 5 ), + 'postcode' => array( 'BT*' ), + 'name' => _x( 'Northern Ireland', 'tax-helper', 'woocommerce-germanized' ), + ), + ), + ); + + foreach ( self::get_vat_postcode_exemptions_by_country() as $country => $exempt_postcodes ) { + if ( array_key_exists( $country, $rates ) ) { + $default_rate = array_values( $rates[ $country ] )[0]; + + $postcode_exempt = array( + 'postcode' => $exempt_postcodes, + 'standard' => 0, + 'reduced' => count( $default_rate['reduced'] ) > 1 ? array( 0, 0 ) : array( 0 ), + 'name' => _x( 'Exempt', 'tax-helper-rate-import', 'woocommerce-germanized' ), + 'is_exempt' => true, + ); + + if ( array_key_exists( 'super-reduced', $default_rate ) ) { + $postcode_exempt['super-reduced'] = 0; + } + + // Prepend before other tax rates + $rates[ $country ] = array_merge( array( $postcode_exempt ), $rates[ $country ] ); + } + } + + return $rates; + } + + /** + * @param \stdClass $rate + * + * @return bool + */ + public static function tax_rate_is_northern_ireland( $rate ) { + if ( 'GB' === $rate->tax_rate_country && isset( $rate->postcode ) && ! empty( $rate->postcode ) ) { + foreach ( $rate->postcode as $postcode ) { + if ( self::is_northern_ireland( $rate->tax_rate_country, $postcode ) ) { + return true; + } + } + } + + return false; + } + + public static function import_rates( $rates, $tax_class = '', $tax_class_type = '' ) { + global $wpdb; + + $eu_countries = self::get_eu_vat_countries(); + + /** + * Delete EU tax rates and make sure tax rate locations are deleted too + */ + foreach ( \WC_Tax::get_rates_for_tax_class( $tax_class ) as $rate_id => $rate ) { + if ( in_array( $rate->tax_rate_country, $eu_countries, true ) || self::tax_rate_is_northern_ireland( $rate ) || ( 'GB' === $rate->tax_rate_country && 'GB' !== self::get_base_country() ) ) { + \WC_Tax::_delete_tax_rate( $rate_id ); + } elseif ( 'zero' === $tax_class_type && empty( $rate->tax_rate_country ) ) { + \WC_Tax::_delete_tax_rate( $rate_id ); + } + } + + $count = 0; + + foreach ( $rates as $rate ) { + $rate = wp_parse_args( + $rate, + array( + 'rate' => 0, + 'country' => '', + 'postcode' => '', + 'name' => '', + ) + ); + + $iso = wc_strtoupper( $rate['country'] ); + $vat_desc = '*' !== $iso ? $iso : ''; + + if ( ! empty( $rate['name'] ) ) { + $vat_desc = ( ! empty( $vat_desc ) ? $vat_desc . ' ' : '' ) . $rate['name']; + } + + $vat_rate = wc_format_decimal( $rate['rate'], false, true ); + + $tax_rate_name = apply_filters( 'woocommerce_eu_tax_helper_import_tax_rate_name', sprintf( _x( 'VAT %1$s %% %2$s', 'tax-helper-rate-import', 'woocommerce-germanized' ), $vat_rate, $vat_desc ), $rate['rate'], $iso, $tax_class, $rate ); + + $_tax_rate = array( + 'tax_rate_country' => $iso, + 'tax_rate_state' => '', + 'tax_rate' => (string) number_format( (float) wc_clean( $rate['rate'] ), 4, '.', '' ), + 'tax_rate_name' => $tax_rate_name, + 'tax_rate_compound' => 0, + 'tax_rate_priority' => 1, + 'tax_rate_order' => $count++, + 'tax_rate_shipping' => ( strstr( $tax_class, 'virtual' ) ? 0 : 1 ), + 'tax_rate_class' => $tax_class, + ); + + $new_tax_rate_id = \WC_Tax::_insert_tax_rate( $_tax_rate ); + + if ( ! empty( $rate['postcode'] ) ) { + \WC_Tax::_update_tax_rate_postcodes( $new_tax_rate_id, $rate['postcode'] ); + } + } + } + + /** + * @param $rate_id + * @param \WC_Order $order + */ + public static function get_tax_rate_percent( $rate_id, $order ) { + $taxes = $order->get_taxes(); + $percentage = null; + + foreach ( $taxes as $tax ) { + if ( (int) $tax->get_rate_id() === (int) $rate_id ) { + if ( is_callable( array( $tax, 'get_rate_percent' ) ) ) { + $percentage = $tax->get_rate_percent(); + } + } + } + + /** + * WC_Order_Item_Tax::get_rate_percent returns null by default. + * Fallback to global tax rates (DB) in case the percentage is not available within order data. + */ + if ( is_null( $percentage ) || '' === $percentage ) { + $rate_percentage = self::get_tax_rate_percentage( $rate_id ); + + if ( false !== $rate_percentage ) { + $percentage = $rate_percentage; + } + } + + if ( ! is_numeric( $percentage ) ) { + $percentage = 0; + } + + return $percentage; + } + + public static function get_tax_rate_percentage( $rate_id ) { + $percentage = false; + + if ( is_callable( array( 'WC_Tax', 'get_rate_percent_value' ) ) ) { + $percentage = \WC_Tax::get_rate_percent_value( $rate_id ); + } elseif ( is_callable( array( 'WC_Tax', 'get_rate_percent' ) ) ) { + $percentage = filter_var( \WC_Tax::get_rate_percent( $rate_id ), FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION ); + } + + return $percentage; + } +} diff --git a/packages/woocommerce-germanized-dhl/assets/css/admin.css b/packages/woocommerce-germanized-dhl/assets/css/admin.css new file mode 100644 index 000000000..30fa46b20 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/admin.css @@ -0,0 +1,48 @@ +.germanized-create-label .wc-gzd-shipment-im-additional-services p.label { + margin-top: 10px; + width: 100%; + display: block; + margin-bottom: 5px; + font-weight: bold; } + +.germanized-create-label .wc-gzd-dhl-im-product-data { + margin-top: 2em; + min-width: 700px; + margin-left: -1rem !important; + margin-right: -1rem !important; } + .germanized-create-label .wc-gzd-dhl-im-product-data .column { + padding-left: 1rem !important; + padding-right: 1rem !important; } + .germanized-create-label .wc-gzd-dhl-im-product-data .column p:first-child { + margin-top: 1.5em !important; } + .germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-price { + background: #ffd633; + border-radius: 4px; + padding: .5em 1em; } + .germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-price .amount { + font-size: 18px; + font-weight: bold; } + .germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-price .price-suffix { + display: block; + font-size: 11px; + line-height: 15px; } + .germanized-create-label .wc-gzd-dhl-im-product-data .col-dimensions { + color: #999; } + .germanized-create-label .wc-gzd-dhl-im-product-data .col-preview .image-preview img { + height: auto; + max-height: 140px; } + .germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-information-text, .germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-description { + font-size: 11px; + color: #999; + line-height: 1.5em; } + +.germanized-create-label .show-services-trigger { + font-weight: bold; + margin-top: 15px; + margin-bottom: 0; + display: block; + text-align: right; + vertical-align: middle; + line-height: 20px; } + .germanized-create-label .show-services-trigger a { + text-decoration: none; } \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/admin.min.css b/packages/woocommerce-germanized-dhl/assets/css/admin.min.css new file mode 100644 index 000000000..59195f1cd --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/admin.min.css @@ -0,0 +1 @@ +.germanized-create-label .wc-gzd-shipment-im-additional-services p.label{margin-top:10px;width:100%;display:block;margin-bottom:5px;font-weight:700}.germanized-create-label .wc-gzd-dhl-im-product-data{margin-top:2em;min-width:700px;margin-left:-1rem!important;margin-right:-1rem!important}.germanized-create-label .wc-gzd-dhl-im-product-data .column{padding-left:1rem!important;padding-right:1rem!important}.germanized-create-label .wc-gzd-dhl-im-product-data .column p:first-child{margin-top:1.5em!important}.germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-price{background:#ffd633;border-radius:4px;padding:.5em 1em}.germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-price .amount{font-size:18px;font-weight:700}.germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-price .price-suffix{display:block;font-size:11px;line-height:15px}.germanized-create-label .wc-gzd-dhl-im-product-data .col-dimensions{color:#999}.germanized-create-label .wc-gzd-dhl-im-product-data .col-preview .image-preview img{height:auto;max-height:140px}.germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-description,.germanized-create-label .wc-gzd-dhl-im-product-data .wc-gzd-dhl-im-product-information-text{font-size:11px;color:#999;line-height:1.5em}.germanized-create-label .show-services-trigger{font-weight:700;margin-top:15px;margin-bottom:0;display:block;text-align:right;vertical-align:middle;line-height:20px}.germanized-create-label .show-services-trigger a{text-decoration:none} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/admin.scss b/packages/woocommerce-germanized-dhl/assets/css/admin.scss new file mode 100644 index 000000000..32abfae9b --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/admin.scss @@ -0,0 +1,76 @@ +.germanized-create-label { + .wc-gzd-shipment-im-additional-services { + p.label { + margin-top: 10px; + width: 100%; + display: block; + margin-bottom: 5px; + font-weight: bold; + } + } + + .wc-gzd-dhl-im-product-data { + margin-top: 2em; + min-width: 700px; + margin-left: -1rem !important; + margin-right: -1rem !important; + + .column { + padding-left: 1rem !important; + padding-right: 1rem !important; + + p:first-child { + margin-top: 1.5em !important; + } + } + + .wc-gzd-dhl-im-product-price { + background: #ffd633; + border-radius: 4px; + padding: .5em 1em; + + .amount { + font-size: 18px; + font-weight: bold; + } + .price-suffix { + display: block; + font-size: 11px; + line-height: 15px; + } + } + + .col-dimensions { + color: #999; + } + + .col-preview { + .image-preview { + img { + height: auto; + max-height: 140px; + } + } + } + + .wc-gzd-dhl-im-product-information-text, .wc-gzd-dhl-im-product-description { + font-size: 11px; + color: #999; + line-height: 1.5em; + } + } + + .show-services-trigger { + font-weight: bold; + margin-top: 15px; + margin-bottom: 0; + display: block; + text-align: right; + vertical-align: middle; + line-height: 20px; + + a { + text-decoration: none; + } + } +} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.css b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.css new file mode 100644 index 000000000..1fad22ff7 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.css @@ -0,0 +1,155 @@ +#dhl-parcel-finder-wrapper { + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transform: translateZ(0); + width: 100%; + height: 100%; + left: 0; + position: fixed; + top: 0; + z-index: 99992; + visibility: hidden; } + #dhl-parcel-finder-wrapper * { + box-sizing: border-box; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-bg-overlay { + background: #1e1e1e; + opacity: 0; + transition-duration: inherit; + transition-property: opacity; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; } + #dhl-parcel-finder-wrapper.open { + visibility: visible; } + #dhl-parcel-finder-wrapper.open #dhl-parcel-finder-bg-overlay { + opacity: .87; + transition-timing-function: cubic-bezier(0.22, 0.61, 0.36, 1); } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-inner { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + overflow: hidden; + z-index: 99994; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-inner-wrapper { + padding: 6px 6px 0; + display: block; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + height: 100%; + left: 0; + outline: none; + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + text-align: center; + top: 0; + transition-property: transform,opacity; + white-space: normal; + width: 100%; + z-index: 99994; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-inner-wrapper::before { + content: ""; + display: inline-block; + height: 100%; + margin-right: -.25em; + vertical-align: middle; + width: 0; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-inner-wrapper #dhl-parcel-finder { + width: 95%; + height: 95%; + display: inline-block; + background: #fff; + margin: 0 0 6px; + max-width: 100%; + overflow: auto; + padding: 34px; + position: relative; + text-align: left; + vertical-align: middle; } + #dhl-parcel-finder-wrapper .dhl-parcel-finder-close { + background: transparent; + border: 0; + border-radius: 0; + color: #555; + cursor: pointer; + height: 44px; + margin: 0; + padding: 6px; + position: absolute; + right: 0; + top: 0; + width: 44px; + z-index: 10; } + #dhl-parcel-finder-wrapper .dhl-parcel-finder-close svg { + fill: transparent; + opacity: .8; + stroke: currentColor; + stroke-width: 1.5; + transition: stroke .1s; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-map { + width: 100%; + height: 85%; + position: relative !important; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content #bodyContent { + line-height: 1.5em; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content address { + margin-bottom: 0; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content .parcel-title { + padding-top: 0; + margin-bottom: .5em; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content .parcel-subtitle { + font-size: 0.8125rem; + color: #767676; + font-weight: 800; + letter-spacing: 0.15em; + text-transform: uppercase; + padding: 1em 0 0; } + #dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content .dhl-parcelshop-select-btn { + width: 100%; + margin-top: 10px; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field { + margin-right: 1.5em; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.large { + min-width: 20%; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.finder-pickup-type { + margin-right: 25px; + min-width: 150px; + display: flex; + align-items: center; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.finder-pickup-type.hidden { + display: none; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.finder-pickup-type .icon { + width: 40px; + height: 40px; + display: inline-block; + background-repeat: no-repeat; + background-size: contain; + margin-left: 10px; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button { + text-align: right; + margin-right: 0; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button .button { + width: 100%; + margin: 0; } + +@media (max-width: 700px) { + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field { + width: 100%; + margin-right: 0; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.packstation, #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.parcelshop { + width: auto; + margin-right: 1.5em; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button { + text-align: left; } + #dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button .button { + width: 100%; + margin: 0; } } \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.min.css b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.min.css new file mode 100644 index 000000000..555775f6e --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.min.css @@ -0,0 +1 @@ +#dhl-parcel-finder-wrapper{-webkit-backface-visibility:hidden;backface-visibility:hidden;transform:translateZ(0);width:100%;height:100%;left:0;position:fixed;top:0;z-index:99992;visibility:hidden}#dhl-parcel-finder-wrapper *{box-sizing:border-box}#dhl-parcel-finder-wrapper #dhl-parcel-finder-bg-overlay{background:#1e1e1e;opacity:0;transition-duration:inherit;transition-property:opacity;bottom:0;left:0;position:absolute;right:0;top:0}#dhl-parcel-finder-wrapper.open{visibility:visible}#dhl-parcel-finder-wrapper.open #dhl-parcel-finder-bg-overlay{opacity:.87;transition-timing-function:cubic-bezier(.22,.61,.36,1)}#dhl-parcel-finder-wrapper #dhl-parcel-finder-inner{bottom:0;left:0;position:absolute;right:0;top:0;overflow:hidden;z-index:99994}#dhl-parcel-finder-wrapper #dhl-parcel-finder-inner-wrapper{padding:6px 6px 0;display:block;-webkit-backface-visibility:hidden;backface-visibility:hidden;height:100%;left:0;outline:0;overflow:auto;-webkit-overflow-scrolling:touch;position:absolute;text-align:center;top:0;transition-property:transform,opacity;white-space:normal;width:100%;z-index:99994}#dhl-parcel-finder-wrapper #dhl-parcel-finder-inner-wrapper::before{content:"";display:inline-block;height:100%;margin-right:-.25em;vertical-align:middle;width:0}#dhl-parcel-finder-wrapper #dhl-parcel-finder-inner-wrapper #dhl-parcel-finder{width:95%;height:95%;display:inline-block;background:#fff;margin:0 0 6px;max-width:100%;overflow:auto;padding:34px;position:relative;text-align:left;vertical-align:middle}#dhl-parcel-finder-wrapper .dhl-parcel-finder-close{background:0 0;border:0;border-radius:0;color:#555;cursor:pointer;height:44px;margin:0;padding:6px;position:absolute;right:0;top:0;width:44px;z-index:10}#dhl-parcel-finder-wrapper .dhl-parcel-finder-close svg{fill:transparent;opacity:.8;stroke:currentColor;stroke-width:1.5;transition:stroke .1s}#dhl-parcel-finder-wrapper #dhl-parcel-finder-map{width:100%;height:85%;position:relative!important}#dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content #bodyContent{line-height:1.5em}#dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content address{margin-bottom:0}#dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content .parcel-title{padding-top:0;margin-bottom:.5em}#dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content .parcel-subtitle{font-size:.8125rem;color:#767676;font-weight:800;letter-spacing:.15em;text-transform:uppercase;padding:1em 0 0}#dhl-parcel-finder-wrapper #dhl-parcel-finder-map #parcel-content .dhl-parcelshop-select-btn{width:100%;margin-top:10px}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form{display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field{margin-right:1.5em}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.large{min-width:20%}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.finder-pickup-type{margin-right:25px;min-width:150px;display:flex;align-items:center}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.finder-pickup-type.hidden{display:none}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.finder-pickup-type .icon{width:40px;height:40px;display:inline-block;background-repeat:no-repeat;background-size:contain;margin-left:10px}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button{text-align:right;margin-right:0}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button .button{width:100%;margin:0}@media (max-width:700px){#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field{width:100%;margin-right:0}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.packstation,#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field.parcelshop{width:auto;margin-right:1.5em}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button{text-align:left}#dhl-parcel-finder-wrapper form#dhl-parcel-finder-form .form-field#dhl-search-button .button{width:100%;margin:0}} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.scss b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.scss new file mode 100644 index 000000000..9a7c9833b --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/parcel-finder.scss @@ -0,0 +1,218 @@ +#dhl-parcel-finder-wrapper { + backface-visibility: hidden; + transform: translateZ(0); + width: 100%; + height: 100%; + left: 0; + position: fixed; + top: 0; + z-index: 99992; + visibility: hidden; + + * { + box-sizing: border-box; + } + + #dhl-parcel-finder-bg-overlay { + background: #1e1e1e; + opacity: 0; + transition-duration: inherit; + transition-property: opacity; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + } + + &.open { + visibility: visible; + + #dhl-parcel-finder-bg-overlay { + opacity: .87; + transition-timing-function: cubic-bezier(.22,.61,.36,1); + } + } + + #dhl-parcel-finder-inner { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + overflow: hidden; + z-index: 99994; + } + + #dhl-parcel-finder-inner-wrapper { + padding: 6px 6px 0; + display: block; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + height: 100%; + left: 0; + outline: none; + overflow: auto; + -webkit-overflow-scrolling: touch; + position: absolute; + text-align: center; + top: 0; + transition-property: transform,opacity; + white-space: normal; + width: 100%; + z-index: 99994; + + &::before { + content: ""; + display: inline-block; + height: 100%; + margin-right: -.25em; + vertical-align: middle; + width: 0; + } + + #dhl-parcel-finder { + width: 95%; + height: 95%; + display: inline-block; + background: #fff; + margin: 0 0 6px; + max-width: 100%; + overflow: auto; + padding: 34px; + position: relative; + text-align: left; + vertical-align: middle; + } + } + + .dhl-parcel-finder-close { + background: transparent; + border: 0; + border-radius: 0; + color: #555; + cursor: pointer; + height: 44px; + margin: 0; + padding: 6px; + position: absolute; + right: 0; + top: 0; + width: 44px; + z-index: 10; + + svg { + fill: transparent; + opacity: .8; + stroke: currentColor; + stroke-width: 1.5; + transition: stroke .1s; + } + } + + #dhl-parcel-finder-map { + width: 100%; + height: 85%; + position: relative !important; + + #parcel-content { + + #bodyContent { + line-height: 1.5em; + } + + address { + margin-bottom: 0; + } + + .parcel-title { + padding-top: 0; + margin-bottom: .5em; + } + + .parcel-subtitle { + font-size: 0.8125rem; + color: #767676; + font-weight: 800; + letter-spacing: 0.15em; + text-transform: uppercase; + padding: 1em 0 0; + } + + .dhl-parcelshop-select-btn { + width: 100%; + margin-top: 10px; + } + } + } + + form#dhl-parcel-finder-form { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + + .form-field { + margin-right: 1.5em; + + &.large { + min-width: 20%; + } + + &.finder-pickup-type { + margin-right: 25px; + min-width: 150px; + display: flex; + align-items: center; + + &.hidden { + display: none; + } + + .icon { + width: 40px; + height: 40px; + display: inline-block; + background-repeat: no-repeat; + background-size: contain; + margin-left: 10px; + } + } + + &#dhl-search-button { + text-align: right; + margin-right: 0; + + .button { + width: 100%; + margin: 0; + } + } + } + } +} + +@media( max-width: 700px ) { + #dhl-parcel-finder-wrapper { + form#dhl-parcel-finder-form { + .form-field { + width: 100%; + margin-right: 0; + + &.packstation, &.parcelshop { + width: auto; + margin-right: 1.5em; + } + + &#dhl-search-button { + text-align: left; + + .button { + width: 100%; + margin: 0; + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/preferred-services.css b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.css new file mode 100644 index 000000000..1e9a62142 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.css @@ -0,0 +1,156 @@ +#tiptip_holder { + display: none; + z-index: 8675309; + position: absolute; + top: 0; + /*rtl:ignore*/ + left: 0; } + #tiptip_holder.tip_top { + padding-bottom: 5px; } + #tiptip_holder.tip_top #tiptip_arrow_inner { + margin-top: -7px; + margin-left: -6px; + border-top-color: #333; } + #tiptip_holder.tip_bottom { + padding-top: 5px; } + #tiptip_holder.tip_bottom #tiptip_arrow_inner { + margin-top: -5px; + margin-left: -6px; + border-bottom-color: #333; } + #tiptip_holder.tip_right { + padding-left: 5px; } + #tiptip_holder.tip_right #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -5px; + border-right-color: #333; } + #tiptip_holder.tip_left { + padding-right: 5px; } + #tiptip_holder.tip_left #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -7px; + border-left-color: #333; } + +#tiptip_content { + color: #fff; + font-size: 0.8em; + max-width: 150px; + background: #333; + text-align: center; + border-radius: 3px; + padding: 0.618em 1em; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); } + #tiptip_content code { + padding: 1px; + background: #888; } + +#tiptip_arrow, +#tiptip_arrow_inner { + position: absolute; + border-color: transparent; + border-style: solid; + border-width: 6px; + height: 0; + width: 0; } + +.dhl-preferred-service-content { + margin-top: 1em; } + .dhl-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 { + margin-bottom: 1em; } + .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 { + margin: 0; + padding: 0; + max-height: 100px; + max-width: 100px; + background: #FFCC00; } + .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-title { + font-weight: bold; + font-size: 1em; + 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 { + flex-basis: 10%; + display: inline-block; + text-align: center; + padding: 10px 0 0; + margin: 0 8px 8px 0; + background-color: #e3e3e3; } + .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li label { + position: relative; + display: flex; + flex-direction: column; + flex-wrap: wrap; + padding: 5px 10px; + font-size: .9em; + font-weight: bold; + background-color: #eef4f2; + cursor: pointer; + margin: 0; + color: #5f7285; } + .dhl-preferred-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] { + opacity: 0; + width: 1px; + height: 1px; + position: absolute; } + .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li input[type=radio]:checked ~ label { + background-color: #FFCC00; } + .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times.dhl-preferred-service-time li { + flex-grow: inherit; + flex-basis: 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] { + width: 100%; + margin-bottom: .5em; } + .dhl-preferred-service-content .dhl-preferred-service-item .woocommerce-help-tip { + background: #ffcc00; + display: inline-block; + font-size: 1em; + font-style: normal; + height: 18px; + padding: 3px; + margin-top: -5px; + margin-left: 5px; + border-radius: 50%; + line-height: 18px; + position: relative; + vertical-align: middle; + width: 18px; } + .dhl-preferred-service-content .dhl-preferred-service-item .woocommerce-help-tip::after { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: center; + cursor: help; + content: "?"; } + .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-location-types, .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-delivery-types { + list-style: none; + margin: 0; + margin-bottom: 1em; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; } + .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-location-types li, .dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-delivery-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; } \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/preferred-services.min.css b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.min.css new file mode 100644 index 000000000..ef224e57d --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.min.css @@ -0,0 +1 @@ +#tiptip_holder{display:none;z-index:8675309;position:absolute;top:0;left:0}#tiptip_holder.tip_top{padding-bottom:5px}#tiptip_holder.tip_top #tiptip_arrow_inner{margin-top:-7px;margin-left:-6px;border-top-color:#333}#tiptip_holder.tip_bottom{padding-top:5px}#tiptip_holder.tip_bottom #tiptip_arrow_inner{margin-top:-5px;margin-left:-6px;border-bottom-color:#333}#tiptip_holder.tip_right{padding-left:5px}#tiptip_holder.tip_right #tiptip_arrow_inner{margin-top:-6px;margin-left:-5px;border-right-color:#333}#tiptip_holder.tip_left{padding-right:5px}#tiptip_holder.tip_left #tiptip_arrow_inner{margin-top:-6px;margin-left:-7px;border-left-color:#333}#tiptip_content{color:#fff;font-size:.8em;max-width:150px;background:#333;text-align:center;border-radius:3px;padding:.618em 1em;box-shadow:0 1px 3px rgba(0,0,0,.2)}#tiptip_content code{padding:1px;background:#888}#tiptip_arrow,#tiptip_arrow_inner{position:absolute;border-color:transparent;border-style:solid;border-width:6px;height:0;width:0}.dhl-preferred-service-content{margin-top:1em}.dhl-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{margin-bottom:1em}.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{margin:0;padding:0;max-height:100px;max-width:100px;background:#fc0}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-title{font-weight:700;font-size:1em;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{flex-basis:10%;display:inline-block;text-align:center;padding:10px 0 0;margin:0 8px 8px 0;background-color:#e3e3e3}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-service-times li label{position:relative;display:flex;flex-direction:column;flex-wrap:wrap;padding:5px 10px;font-size:.9em;font-weight:700;background-color:#eef4f2;cursor:pointer;margin:0;color:#5f7285}.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]{opacity:0;width:1px;height:1px;position:absolute}.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-grow:inherit;flex-basis: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]{width:100%;margin-bottom:.5em}.dhl-preferred-service-content .dhl-preferred-service-item .woocommerce-help-tip{background:#fc0;display:inline-block;font-size:1em;font-style:normal;height:18px;padding:3px;margin-top:-5px;margin-left:5px;border-radius:50%;line-height:18px;position:relative;vertical-align:middle;width:18px}.dhl-preferred-service-content .dhl-preferred-service-item .woocommerce-help-tip::after{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center;cursor:help;content:"?"}.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-delivery-types,.dhl-preferred-service-content .dhl-preferred-service-item .dhl-preferred-location-types{list-style:none;margin:0;margin-bottom:1em;display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center}.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} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/css/preferred-services.scss b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.scss new file mode 100644 index 000000000..72123a694 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/css/preferred-services.scss @@ -0,0 +1,230 @@ +#tiptip_holder { + display: none; + z-index: 8675309; + position: absolute; + top: 0; + + /*rtl:ignore*/ + left: 0; + + &.tip_top { + padding-bottom: 5px; + + #tiptip_arrow_inner { + margin-top: -7px; + margin-left: -6px; + border-top-color: #333; + } + } + + &.tip_bottom { + padding-top: 5px; + + #tiptip_arrow_inner { + margin-top: -5px; + margin-left: -6px; + border-bottom-color: #333; + } + } + + &.tip_right { + padding-left: 5px; + + #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -5px; + border-right-color: #333; + } + } + + &.tip_left { + padding-right: 5px; + + #tiptip_arrow_inner { + margin-top: -6px; + margin-left: -7px; + border-left-color: #333; + } + } +} + +#tiptip_content { + color: #fff; + font-size: 0.8em; + max-width: 150px; + background: #333; + text-align: center; + border-radius: 3px; + padding: 0.618em 1em; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + + code { + padding: 1px; + background: #888; + } +} + +#tiptip_arrow, +#tiptip_arrow_inner { + position: absolute; + border-color: transparent; + border-style: solid; + border-width: 6px; + height: 0; + width: 0; +} + +.dhl-preferred-service-content { + margin-top: 1em; + + .dhl-hidden { + display: none; + } + + .dhl-preferred-service-cost { + font-size: .9em; + } + + .dhl-preferred-service-item { + margin-bottom: 1em; + + .dhl-preferred-service-logo { + img { + margin: 0; + padding: 0; + max-height: 100px; + max-width: 100px; + background: #FFCC00; + } + margin-bottom: 1em; + } + + .dhl-preferred-service-title { + font-weight: bold; + font-size: 1em; + margin-bottom: .5em; + } + + .dhl-preferred-service-cost { + margin-bottom: .5em; + } + + .dhl-preferred-service-desc { + font-size: .9em; + margin-bottom: .5em; + } + + .dhl-preferred-service-times { + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0; + padding: 0; + + li { + flex-basis: 10%; + display: inline-block; + text-align: center; + padding: 10px 0 0; + margin: 0 8px 8px 0; + background-color: #e3e3e3; + + label { + position: relative; + display: flex; + flex-direction: column; + flex-wrap: wrap; + padding: 5px 10px; + font-size: .9em; + font-weight: bold; + background-color: #eef4f2; + cursor: pointer; + margin: 0; + color: #5f7285; + + .dhl-preferred-time-title { + font-size: 1.2em; + } + } + + input[type=radio] { + opacity: 0; + width: 1px; + height: 1px; + position: absolute; + + &:checked ~ label { + background-color: #FFCC00; + } + } + } + + &.dhl-preferred-service-time { + li { + flex-grow: inherit; + flex-basis: inherit; + + label { + .dhl-preferred-time-title { + font-size: 1em; + } + } + } + } + } + + .dhl-preferred-service-data { + input[type=text] { + width: 100%; + margin-bottom: .5em; + } + } + + .woocommerce-help-tip { + background: #ffcc00; + display: inline-block; + font-size: 1em; + font-style: normal; + height: 18px; + padding: 3px; + margin-top: -5px; + margin-left: 5px; + border-radius: 50%; + line-height: 18px; + position: relative; + vertical-align: middle; + width: 18px; + + &::after { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: center; + cursor: help; + content: "?"; + } + } + + .dhl-preferred-location-types, .dhl-preferred-delivery-types { + list-style: none; + margin: 0; + margin-bottom: 1em; + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + + li { + margin-right: 1em; + } + } + + &.dhl-preferred-service-header { + .dhl-preferred-service-title { + font-size: 1.1em; + } + } + } +} \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/img/dhl-official.png b/packages/woocommerce-germanized-dhl/assets/img/dhl-official.png new file mode 100755 index 0000000000000000000000000000000000000000..18dab3cf6209f060a111015fd08fedcc2fe59436 GIT binary patch literal 41839 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 literal 0 HcmV?d00001 diff --git a/packages/woocommerce-germanized-dhl/assets/img/packstation.png b/packages/woocommerce-germanized-dhl/assets/img/packstation.png new file mode 100755 index 0000000000000000000000000000000000000000..903d8388b38a91c4f425492bcb1c256e4f79068c GIT binary patch literal 6273 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 G6Z#+NBmHjx literal 0 HcmV?d00001 diff --git a/packages/woocommerce-germanized-dhl/assets/img/parcelshop.png b/packages/woocommerce-germanized-dhl/assets/img/parcelshop.png new file mode 100755 index 0000000000000000000000000000000000000000..15fafae42ac6ed518a4586160fb8f0d3d3fefa05 GIT binary patch literal 5538 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 literal 0 HcmV?d00001 diff --git a/packages/woocommerce-germanized-dhl/assets/img/wp-int-eu-preview.png b/packages/woocommerce-germanized-dhl/assets/img/wp-int-eu-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..a4438cd37871b9f9b09dcea86194710b24f235c8 GIT binary patch literal 23720 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 literal 0 HcmV?d00001 diff --git a/packages/woocommerce-germanized-dhl/assets/img/wp-int-preview.png b/packages/woocommerce-germanized-dhl/assets/img/wp-int-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..6ac0f7e34640b1734b84b0e087f8c0f81d10fd1b GIT binary patch literal 49144 zcmV)CK*GO?P)00093P)t-s|NsB} z{{8>}0HUIz%*@OoAtAW9xcvP5yu7^k_xJJg@h>kgl9H18`uZg%B^w(X_4V}9(b0~M zj!jKX78Vxn?(XX9>cPRmot>Spudo0A`h0wRfq{XQm6hP&;Iy=~rlzI}3JToZ+{MMk z`TzfNa&phl&zhQ=<>ci?Mo0ht^kZXVI5#({s;WLdJ`oWS>H7c4$;tBh|Ni;$)YR1C z;^O}G>DSlShlYpp|Nm!aXaD`{i;Iir@%sMo+)`3fcXxPJR#x@u+urj2tgNl@_WEpV zYx3dE`0(cHPiP-s|?++SyuKTW@Y}&$W{J>C(^g{nqLG*~qjV0svrLUeV<6 z+~n-V-|@=2sqX6Hq{ZCg+0oa$ptjlg+{dTO=llQ%N#6SZz18dh4O!~j$J5^CnXk&d z&))(ueMtran8oS<3@Gl_yXeoc=IrpY#@RFj0Kun?%BX;wyV%Rs-LA~*`{~8Lpm>6( z&-&cK0u*fl1U6d;0mHDKsk_j8p2JsjmTj27jiD zCksVjKWlF^FfkruRy9jyL=cLEr*lG1NfJv;H54@oDVef~4;7O_006wRNklPATi{ijh2iU4@6|1dQE7VD=wbkxr ztzFvMsvT;Ftyb*TPTODi9`CyxBq5fn4YszvAKZ7}yt~{#@80*mcL63Gzu~}6sO&!> z960;Hf8<&&NvB(@mP-4Ek8M|*fa5q2HvD!#+*AzC|ke( zc)uCA&@{Q^$i#6iSe;edc-7GTxL{OE)v(Ft`Xsnm z-L<+q2l6)WHMj%dIedJ?{LQ9V&QB zC0~N~;)PcjRH6zERdN<@m_<@Hkx2{j>u@C5yj<+Ms-^+Kp`lmSHV!7u%apF3soxO- zyv8r8nzH!%s=*hG2j;e0B^K+-g$w2+By306mvikOlW?^**-P+VwD6jn=?sV%;Tb?R zBEHC~a!Ng}iF_TKO_TA+ows0P({;GCys~!E*zLG*NG{pB!)qId4XP)2$!svb*V^

>hTl;x^cnSN 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 literal 0 HcmV?d00001 diff --git a/packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.js b/packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.js new file mode 100644 index 000000000..2bfd715a3 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.js @@ -0,0 +1,110 @@ +window.germanized = window.germanized || {}; +window.germanized.admin = window.germanized.admin || {}; + +( function( $, admin ) { + + /** + * Core + */ + admin.dhl_post_label = { + + params: {}, + + init: function () { + var self = admin.dhl_post_label; + self.params = wc_gzd_admin_deutsche_post_label_params; + + $( document ).on( 'change', '#wc-gzd-shipment-label-admin-fields-deutsche_post #product_id, #wc-gzd-shipment-label-admin-fields-deutsche_post #wc-gzd-shipment-label-wrapper-additional-services :input', self.onRefreshPreview ); + }, + + getSelectedAdditionalServices: function() { + var selectedIds = $( "#wc-gzd-shipment-label-wrapper-additional-services :input:checked" ).map( function() { + return $( this ).attr( 'name' ).replace( 'service_', '' ); + }).get(); + + return selectedIds; + }, + + onRefreshPreview: function() { + var self = admin.dhl_post_label, + backbone = germanized.admin.shipment_label_backbone.backbone, + params = {}, + $wrapper = $( '.wc-gzd-shipment-create-label' ); + + params['security'] = self.params.refresh_label_preview_nonce; + params['product_id'] = self.getProductId(); + params['selected_services'] = self.getSelectedAdditionalServices(); + params['action'] = 'woocommerce_gzd_dhl_refresh_deutsche_post_label_preview'; + + backbone.doAjax( params, $wrapper, self.onPreviewSuccess ); + }, + + onPreviewSuccess: function( data ) { + var self = admin.dhl_post_label, + $wrapper = $( '.wc-gzd-dhl-im-product-data .col-preview' ), + $img_wrapper = $( '.wc-gzd-dhl-im-product-data' ).find( '.image-preview' ); + + if ( data.is_wp_int ) { + $wrapper.parents( '.wc-gzd-shipment-create-label' ).find( '.page_format_field' ).hide(); + } else { + $wrapper.parents( '.wc-gzd-shipment-create-label' ).find( '.page_format_field' ).show(); + } + + if ( data.preview_url ) { + $wrapper.block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + + if ( $img_wrapper.find( '.stamp-preview' ).length <= 0 ) { + $img_wrapper.append( '' ); + } + + self.replaceProductData( data.preview_data ); + + $img_wrapper.find( '.stamp-preview' ).attr('src', data.preview_url ).load( function() { + $wrapper.unblock(); + $( this ).show(); + }); + } else { + $img_wrapper.html( '' ); + } + }, + + refreshProductData: function() { + var self = admin.dhl_post_label; + + self.onRefreshPreview(); + }, + + getProductId: function() { + return $( '#wc-gzd-shipment-label-admin-fields-deutsche_post #product_id' ).val(); + }, + + replaceProductData: function( productData ) { + var self = admin.dhl_post_label, + $wrapper = $( '.wc-gzd-shipment-create-label' ).find( '.wc-gzd-dhl-im-product-data' ); + + $wrapper.find( '.data-placeholder' ).html( '' ); + + $wrapper.find( '.data-placeholder' ).each( function() { + var replaceKey = $( this ).data( 'replace' ); + + if ( productData.hasOwnProperty( replaceKey ) ) { + $( this ).html( productData[ replaceKey ] ); + $( this ).show(); + } else { + $( this ).hide(); + } + } ); + } + }; + + $( document ).ready( function() { + germanized.admin.dhl_post_label.init(); + }); + +})( jQuery, window.germanized.admin ); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.min.js b/packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.min.js new file mode 100644 index 000000000..9a979c4b6 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/admin-deutsche-post-label.min.js @@ -0,0 +1 @@ +window.germanized=window.germanized||{},window.germanized.admin=window.germanized.admin||{},function(d,n){n.dhl_post_label={params:{},init:function(){var e=n.dhl_post_label;e.params=wc_gzd_admin_deutsche_post_label_params,d(document).on("change","#wc-gzd-shipment-label-admin-fields-deutsche_post #product_id, #wc-gzd-shipment-label-admin-fields-deutsche_post #wc-gzd-shipment-label-wrapper-additional-services :input",e.onRefreshPreview)},getSelectedAdditionalServices:function(){return d("#wc-gzd-shipment-label-wrapper-additional-services :input:checked").map(function(){return d(this).attr("name").replace("service_","")}).get()},onRefreshPreview:function(){var e=n.dhl_post_label,a=germanized.admin.shipment_label_backbone.backbone,i={},t=d(".wc-gzd-shipment-create-label");i.security=e.params.refresh_label_preview_nonce,i.product_id=e.getProductId(),i.selected_services=e.getSelectedAdditionalServices(),i.action="woocommerce_gzd_dhl_refresh_deutsche_post_label_preview",a.doAjax(i,t,e.onPreviewSuccess)},onPreviewSuccess:function(e){var a=n.dhl_post_label,i=d(".wc-gzd-dhl-im-product-data .col-preview"),t=d(".wc-gzd-dhl-im-product-data").find(".image-preview");e.is_wp_int?i.parents(".wc-gzd-shipment-create-label").find(".page_format_field").hide():i.parents(".wc-gzd-shipment-create-label").find(".page_format_field").show(),e.preview_url?(i.block({message:null,overlayCSS:{background:"#fff",opacity:.6}}),t.find(".stamp-preview").length<=0&&t.append(''),a.replaceProductData(e.preview_data),t.find(".stamp-preview").attr("src",e.preview_url).load(function(){i.unblock(),d(this).show()})):t.html("")},refreshProductData:function(){n.dhl_post_label.onRefreshPreview()},getProductId:function(){return d("#wc-gzd-shipment-label-admin-fields-deutsche_post #product_id").val()},replaceProductData:function(a){n.dhl_post_label;var e=d(".wc-gzd-shipment-create-label").find(".wc-gzd-dhl-im-product-data");e.find(".data-placeholder").html(""),e.find(".data-placeholder").each(function(){var e=d(this).data("replace");a.hasOwnProperty(e)?(d(this).html(a[e]),d(this).show()):d(this).hide()})}},d(document).ready(function(){germanized.admin.dhl_post_label.init()})}(jQuery,window.germanized.admin); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.js b/packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.js new file mode 100644 index 000000000..cf3528bfc --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.js @@ -0,0 +1,266 @@ +window.germanized = window.germanized || {}; +window.germanized.admin = window.germanized.admin || {}; + +/* + * http://www.myersdaily.org/joseph/javascript/md5-text.html + */ +( function (global) { + + var md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + + } + + var cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + } + + var ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + } + + var gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + } + + var hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + } + + var ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + } + + var md51 = function (s) { + var txt = '', + n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i; + for (i = 64; i <= s.length; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < s.length; i++) + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i++) tail[i] = 0; + } + tail[14] = n * 8; + md5cycle(state, tail); + return state; + } + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + var md5blk = function (s) { /* I figured global was faster. */ + var md5blks = [], + i; /* Andy King said do it this way. */ + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + } + + var hex_chr = '0123456789abcdef'.split(''); + + var rhex = function (n) { + var s = '', + j = 0; + for (; j < 4; j++) + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + return s; + } + + var hex = function (x) { + for (var i = 0; i < x.length; i++) + x[i] = rhex(x[i]); + return x.join(''); + } + + var md5 = global.md5 = function (s) { + return hex(md51(s)); + } + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + } + + if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') { + var add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + } + } +})(window); + +( function( $, admin, window ) { + + /** + * Core + */ + admin.dhl_internetmarke = { + + params: {}, + + init: function () { + var self = admin.dhl_internetmarke; + + $( document ).on( 'click', '#woocommerce_gzd_dhl_im_portokasse_charge', self.onCharge ); + }, + + onCharge: function() { + var $button = $( this ), + data = $button.data(), + amount = $( '#woocommerce_gzd_dhl_im_portokasse_charge_amount' ).val(); + + $form = $( '
' ).appendTo( 'body' ); + + $.each( data, function( index, value ) { + $form.append( '' ); + } ); + + var balance = parseInt( ( parseFloat( amount.replace( ',', '.') ).toFixed( 2 ) * 100 ).toFixed() ); + var wallet = parseInt( data['wallet'] ); + + /** + * Set min amount. + */ + if ( balance < 1000 || Number.isNaN( balance ) ) { + balance = 1000; + } + + var date = new Date(); + var timestamp = + ('0' + date.getDate()).slice(-2) + + ('0' + (date.getMonth() + 1)).slice(-2) + + date.getFullYear().toString() + + '-' + + ('0' + date.getHours()).slice(-2) + + ('0' + date.getMinutes()).slice(-2) + + ('0' + date.getSeconds()).slice(-2); + + var concat = [ + data['partner_id'], + timestamp, + data['success_url'], + data['cancel_url'], + data['user_token'], + wallet + balance, + data['schluessel_dpwn_partner'] + ].join( '::' ); + + $form.append( '' ); + $form.append( '' ); + $form.append( '' ); + + $form.submit(); + + return false; + } + }; + + $( document ).ready( function() { + germanized.admin.dhl_internetmarke.init(); + }); + +})( jQuery, window.germanized.admin, window ); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.min.js b/packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.min.js new file mode 100644 index 000000000..ff0186a70 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/admin-internetmarke.min.js @@ -0,0 +1 @@ +window.germanized=window.germanized||{},window.germanized.admin=window.germanized.admin||{},function(e){var a=function(e,n){var r=e[0],t=e[1],o=e[2],a=e[3],r=i(r,t,o,a,n[0],7,-680876936),a=i(a,r,t,o,n[1],12,-389564586),o=i(o,a,r,t,n[2],17,606105819),t=i(t,o,a,r,n[3],22,-1044525330);r=i(r,t,o,a,n[4],7,-176418897),a=i(a,r,t,o,n[5],12,1200080426),o=i(o,a,r,t,n[6],17,-1473231341),t=i(t,o,a,r,n[7],22,-45705983),r=i(r,t,o,a,n[8],7,1770035416),a=i(a,r,t,o,n[9],12,-1958414417),o=i(o,a,r,t,n[10],17,-42063),t=i(t,o,a,r,n[11],22,-1990404162),r=i(r,t,o,a,n[12],7,1804603682),a=i(a,r,t,o,n[13],12,-40341101),o=i(o,a,r,t,n[14],17,-1502002290),t=i(t,o,a,r,n[15],22,1236535329),r=u(r,t,o,a,n[1],5,-165796510),a=u(a,r,t,o,n[6],9,-1069501632),o=u(o,a,r,t,n[11],14,643717713),t=u(t,o,a,r,n[0],20,-373897302),r=u(r,t,o,a,n[5],5,-701558691),a=u(a,r,t,o,n[10],9,38016083),o=u(o,a,r,t,n[15],14,-660478335),t=u(t,o,a,r,n[4],20,-405537848),r=u(r,t,o,a,n[9],5,568446438),a=u(a,r,t,o,n[14],9,-1019803690),o=u(o,a,r,t,n[3],14,-187363961),t=u(t,o,a,r,n[8],20,1163531501),r=u(r,t,o,a,n[13],5,-1444681467),a=u(a,r,t,o,n[2],9,-51403784),o=u(o,a,r,t,n[7],14,1735328473),t=u(t,o,a,r,n[12],20,-1926607734),r=c(r,t,o,a,n[5],4,-378558),a=c(a,r,t,o,n[8],11,-2022574463),o=c(o,a,r,t,n[11],16,1839030562),t=c(t,o,a,r,n[14],23,-35309556),r=c(r,t,o,a,n[1],4,-1530992060),a=c(a,r,t,o,n[4],11,1272893353),o=c(o,a,r,t,n[7],16,-155497632),t=c(t,o,a,r,n[10],23,-1094730640),r=c(r,t,o,a,n[13],4,681279174),a=c(a,r,t,o,n[0],11,-358537222),o=c(o,a,r,t,n[3],16,-722521979),t=c(t,o,a,r,n[6],23,76029189),r=c(r,t,o,a,n[9],4,-640364487),a=c(a,r,t,o,n[12],11,-421815835),o=c(o,a,r,t,n[15],16,530742520),t=c(t,o,a,r,n[2],23,-995338651),r=m(r,t,o,a,n[0],6,-198630844),a=m(a,r,t,o,n[7],10,1126891415),o=m(o,a,r,t,n[14],15,-1416354905),t=m(t,o,a,r,n[5],21,-57434055),r=m(r,t,o,a,n[12],6,1700485571),a=m(a,r,t,o,n[3],10,-1894986606),o=m(o,a,r,t,n[10],15,-1051523),t=m(t,o,a,r,n[1],21,-2054922799),r=m(r,t,o,a,n[8],6,1873313359),a=m(a,r,t,o,n[15],10,-30611744),o=m(o,a,r,t,n[6],15,-1560198380),t=m(t,o,a,r,n[13],21,1309151649),r=m(r,t,o,a,n[4],6,-145523070),a=m(a,r,t,o,n[11],10,-1120210379),o=m(o,a,r,t,n[2],15,718787259),t=m(t,o,a,r,n[9],21,-343485551),e[0]=l(r,e[0]),e[1]=l(t,e[1]),e[2]=l(o,e[2]),e[3]=l(a,e[3])},d=function(e,n,r,t,o,a){return n=l(l(n,e),l(t,a)),l(n<>>32-o,r)},i=function(e,n,r,t,o,a,i){return d(n&r|~n&t,e,n,o,a,i)},u=function(e,n,r,t,o,a,i){return d(n&t|r&~t,e,n,o,a,i)},c=function(e,n,r,t,o,a,i){return d(n^r^t,e,n,o,a,i)},m=function(e,n,r,t,o,a,i){return d(r^(n|~t),e,n,o,a,i)},t="0123456789abcdef".split(""),n=function(e){for(var n=0;n>8*r+4&15]+t[e>>8*r&15];return n}(e[n]);return e.join("")},e=e.md5=function(e){return n(function(e){for(var n=e.length,r=[1732584193,-271733879,-1732584194,271733878],t=64;t<=e.length;t+=64)a(r,function(e){for(var n=[],r,r=0;r<64;r+=4)n[r>>2]=e.charCodeAt(r)+(e.charCodeAt(r+1)<<8)+(e.charCodeAt(r+2)<<16)+(e.charCodeAt(r+3)<<24);return n}(e.substring(t-64,t)));e=e.substring(t-64);var o=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(t=0;t>2]|=e.charCodeAt(t)<<(t%4<<3);if(o[t>>2]|=128<<(t%4<<3),55>16)+(n>>16)+(r>>16)<<16|65535&r})}(window),function(o,n,a){n.dhl_internetmarke={params:{},init:function(){var e=n.dhl_internetmarke;o(document).on("click","#woocommerce_gzd_dhl_im_portokasse_charge",e.onCharge)},onCharge:function(){var e=o(this),n=e.data(),r=o("#woocommerce_gzd_dhl_im_portokasse_charge_amount").val();$form=o('
').appendTo("body"),o.each(n,function(e,n){$form.append('')});var t=parseInt((100*parseFloat(r.replace(",",".")).toFixed(2)).toFixed()),e=parseInt(n.wallet);(t<1e3||Number.isNaN(t))&&(t=1e3);r=new Date,r=("0"+r.getDate()).slice(-2)+("0"+(r.getMonth()+1)).slice(-2)+r.getFullYear().toString()+"-"+("0"+r.getHours()).slice(-2)+("0"+r.getMinutes()).slice(-2)+("0"+r.getSeconds()).slice(-2),n=[n.partner_id,r,n.success_url,n.cancel_url,n.user_token,e+t,n.schluessel_dpwn_partner].join("::");return $form.append(''),$form.append(''),$form.append(''),$form.submit(),!1}},o(document).ready(function(){germanized.admin.dhl_internetmarke.init()})}(jQuery,window.germanized.admin,window); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/parcel-finder.js b/packages/woocommerce-germanized-dhl/assets/js/parcel-finder.js new file mode 100644 index 000000000..a4dfc1f0f --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/parcel-finder.js @@ -0,0 +1,309 @@ +window.germanized = window.germanized || {}; +window.germanized.dhl_parcel_finder = window.germanized.dhl_parcel_finder || {}; + +( function( $, germanized ) { + + /** + * Core + */ + germanized.dhl_parcel_finder = { + + params: {}, + parcelShops: [], + wrapper: '', + + init: function () { + var self = germanized.dhl_parcel_finder; + self.params = wc_gzd_dhl_parcel_finder_params; + self.wrapper = self.params.wrapper; + + $( document ) + .on( 'click', '.gzd-dhl-parcel-shop-modal', self.openModal ) + .on( 'click', '#dhl-parcel-finder-wrapper .dhl-parcel-finder-close', self.closeModal ) + .on( 'submit', '#dhl-parcel-finder-wrapper #dhl-parcel-finder-form', self.onSubmit ) + .on( 'click', '#dhl-parcel-finder-wrapper .dhl-retry-search', self.onSubmit ) + .on( 'click', '#dhl-parcel-finder-wrapper .dhl-parcelshop-select-btn', self.onSelectShop ); + + $( document.body ).on( 'woocommerce_gzd_dhl_location_available_pickup_types_changed', self.onChangeAvailablePickupTypes ); + }, + + onChangeAvailablePickupTypes: function() { + var self = germanized.dhl_parcel_finder, + loc = germanized.dhl_parcel_locator, + $modal = self.getModal(), + method = loc.getShippingMethod(), + methodData = loc.getShippingMethodData( method ); + + if ( methodData ) { + + $modal.find( '.finder-pickup-type' ).addClass( 'hidden' ); + + $.each( methodData.supports, function( i, pickupType ) { + var $type = $modal.find( '.finder-pickup-type[data-pickup_type="' + pickupType + '"]' ); + + $type.find( 'input[type=checkbox]' ).prop( 'checked', true ); + $type.removeClass( 'hidden' ); + }); + } + }, + + openModal: function() { + var self = germanized.dhl_parcel_finder, + $modal = self.getModal(); + + var country = $( self.wrapper + ' #shipping_country' ).val().length > 0 ? $( self.wrapper + ' #shipping_country' ).val() : $( self.wrapper + ' #billing_country' ).val(); + $modal.find( '#dhl-parcelfinder-country').val( country ); + + var postcode = $( self.wrapper + ' #shipping_postcode' ).val().length > 0 ? $( self.wrapper + ' #shipping_postcode' ).val() : $( self.wrapper + ' #billing_postcode' ).val(); + $modal.find( '#dhl-parcelfinder-postcode' ).val( postcode ); + + var city = $( self.wrapper + ' #shipping_city' ).val().length > 0 ? $( self.wrapper + ' #shipping_city' ).val() : $( self.wrapper + ' #billing_city' ).val(); + $modal.find( '#dhl-parcelfinder-city' ).val( city ); + + $modal.addClass( 'open' ); + $modal.find( '#dhl-parcel-finder-form' ).submit(); + + return false; + }, + + closeModal: function() { + var self = germanized.dhl_parcel_finder; + self.getModal().removeClass( 'open' ); + + return false; + }, + + getModal: function() { + return $( '#dhl-parcel-finder-wrapper' ); + }, + + doAjax: function( params, $wrapper, cSuccess, cError ) { + var self = germanized.dhl_parcel_finder; + + cSuccess = cSuccess || self.onAjaxSuccess; + cError = cError || self.onAjaxError; + + if ( ! params.hasOwnProperty( 'security' ) ) { + params['security'] = self.params.parcel_finder_nonce; + } + + $wrapper.find( '#dhl-parcel-finder-map' ).block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + + $wrapper.find( '.notice-wrapper' ).empty(); + + $.ajax({ + type: "POST", + url: self.params.ajax_url, + data: params, + success: function( data ) { + if ( data.success ) { + $wrapper.find( '#dhl-parcel-finder-map' ).unblock(); + cSuccess.apply( $wrapper, [ data ] ); + } else { + cError.apply( $wrapper, [ data ] ); + $wrapper.find( '#dhl-parcel-finder-map' ).unblock(); + + if ( data.hasOwnProperty( 'message' ) ) { + self.addNotice( data.message, 'error', $wrapper ); + } else if( data.hasOwnProperty( 'messages' ) ) { + $.each( data.messages, function( i, message ) { + self.addNotice( message, 'error', $wrapper ); + }); + } + } + }, + error: function( data ) {}, + dataType: 'json' + }); + }, + + onAjaxSuccess: function( data ) {}, + + onAjaxError: function( data ) {}, + + getFormData: function( $form ) { + var data = {}; + + $.each( $form.serializeArray(), function( index, item ) { + if ( item.name.indexOf( '[]' ) !== -1 ) { + item.name = item.name.replace( '[]', '' ); + data[ item.name ] = $.makeArray( data[ item.name ] ); + data[ item.name ].push( item.value ); + } else { + data[ item.name ] = item.value; + } + }); + + return data; + }, + + onSubmit: function( e ) { + var self = germanized.dhl_parcel_finder, + loc = germanized.dhl_parcel_locator, + $modal = self.getModal(), + $content = $modal.find( '#dhl-parcel-finder' ), + $form = $content.find( 'form' ), + params = self.getFormData( $form ); + + params['action'] = 'woocommerce_gzd_dhl_parcelfinder_search'; + params['is_checkout'] = loc.isCheckout() ? 'yes' : 'no'; + + self.doAjax( params, $content, self.onSubmitSuccess ); + + return false; + }, + + onSubmitSuccess: function( data ) { + var self = germanized.dhl_parcel_finder; + + if ( data.parcel_shops ) { + self.parcelShops = data.parcel_shops; + + if ( typeof google === 'object' && typeof google.maps === 'object' ) { + self.updateMap(); + } else { + self.loadMapsAPI(); + } + } + }, + + loadMapsAPI: function() { + var self = germanized.dhl_parcel_finder; + + self.addScript( 'https://maps.googleapis.com/maps/api/js?key=' + self.params.api_key, self.updateMap ); + }, + + addScript: function( url, callback ) { + var script = document.createElement( 'script' ); + + if ( callback ) { + script.onload = callback; + } + + script.type = 'text/javascript'; + script.src = url; + document.body.appendChild( script ); + }, + + updateMap: function() { + var self = germanized.dhl_parcel_finder, + parcelShops = self.parcelShops; + + var uluru = { + lat: parcelShops[0].place.geo.latitude, + lng: parcelShops[0].place.geo.longitude + }; + + var map = new google.maps.Map( document.getElementById( 'dhl-parcel-finder-map' ), { + zoom: 13, + center: uluru + }); + + var infoWinArray = []; + + $.each( parcelShops, function( key, value ) { + + var uluru = { + lat: value.place.geo.latitude, + lng: value.place.geo.longitude + }; + + var markerIcon = self.params.packstation_icon, + shopLabel = self.params.i18n.packstation; + + switch ( value.gzd_type ) { + case 'parcelshop': + markerIcon = self.params.parcelshop_icon; + shopLabel = self.params.i18n.branch; + break; + case 'postoffice': + markerIcon = self.params.postoffice_icon; + shopLabel = self.params.i18n.branch; + break; + } + + var infowindow = new google.maps.InfoWindow({ + content: value.html_content, + maxWidth: 300 + }); + + infoWinArray.push( infowindow ); + + var marker = new google.maps.Marker({ + position : uluru, + map : map, + title : shopLabel, + animation: google.maps.Animation.DROP, + icon : markerIcon + }); + + marker.addListener('click', function() { + clearOverlays(); + infowindow.open( map, marker ); + }); + }); + + // Clear all info windows + function clearOverlays() { + for ( var i = 0; i < infoWinArray.length; i++ ) { + infoWinArray[i].close(); + } + } + }, + + onSelectShop: function() { + var self = germanized.dhl_parcel_finder, + parcelShopId = $( this ).attr( 'id' ), + $addressType = $( self.wrapper + ' #shipping_address_type' ), + country = $( self.wrapper + ' #shipping_country' ).val(), + fieldKey = germanized.dhl_parcel_locator.getPickupFieldKey( country ); + + $.each( self.parcelShops, function( key, value ) { + if ( value.gzd_result_id === parcelShopId ) { + var isPackstation = 'packstation' === value.gzd_type; + + $( self.wrapper + ' #shipping_first_name' ).val( $( self.wrapper + ' #shipping_first_name' ).val().length > 0 ? $( self.wrapper + ' #shipping_first_name' ).val() : $( self.wrapper + ' #billing_first_name' ).val() ); + $( self.wrapper + ' #shipping_last_name' ).val( $( self.wrapper + ' #shipping_last_name' ).val().length > 0 ? $( self.wrapper + ' #shipping_last_name' ).val() : $( self.wrapper + ' #billing_last_name' ).val() ); + + $( self.wrapper + ' #shipping_' + fieldKey ).val( value.gzd_name ); + + if ( 'DE' === country ) { + $( self.wrapper + ' #shipping_address_2' ).val( '' ); + } else { + $( self.wrapper + ' #shipping_address_1' ).val( value.place.address.streetAddress ); + } + + $( self.wrapper + ' #shipping_postcode' ).val( value.place.address.postalCode ); + $( self.wrapper + ' #shipping_city' ).val( value.place.address.addressLocality ); + + $addressType.val( 'dhl' ).trigger( 'change' ); + + self.closeModal(); + + if ( 'DE' === country ) { + if ( isPackstation && $( self.wrapper + ' #shipping_dhl_postnumber' ).val() === '' ) { + $( self.wrapper + ' #shipping_dhl_postnumber' ).focus(); + } + } + + return true; + } + }); + }, + + addNotice: function( message, noticeType, $wrapper ) { + $wrapper.find( '.notice-wrapper' ).append( '

' + message + '

' ); + } + }; + + $( document ).ready( function() { + germanized.dhl_parcel_finder.init(); + }); + +})( jQuery, window.germanized ); diff --git a/packages/woocommerce-germanized-dhl/assets/js/parcel-finder.min.js b/packages/woocommerce-germanized-dhl/assets/js/parcel-finder.min.js new file mode 100644 index 000000000..d6ee67425 --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/parcel-finder.min.js @@ -0,0 +1 @@ +window.germanized=window.germanized||{},window.germanized.dhl_parcel_finder=window.germanized.dhl_parcel_finder||{},function(t,s){s.dhl_parcel_finder={params:{},parcelShops:[],wrapper:"",init:function(){var e=s.dhl_parcel_finder;e.params=wc_gzd_dhl_parcel_finder_params,e.wrapper=e.params.wrapper,t(document).on("click",".gzd-dhl-parcel-shop-modal",e.openModal).on("click","#dhl-parcel-finder-wrapper .dhl-parcel-finder-close",e.closeModal).on("submit","#dhl-parcel-finder-wrapper #dhl-parcel-finder-form",e.onSubmit).on("click","#dhl-parcel-finder-wrapper .dhl-retry-search",e.onSubmit).on("click","#dhl-parcel-finder-wrapper .dhl-parcelshop-select-btn",e.onSelectShop),t(document.body).on("woocommerce_gzd_dhl_location_available_pickup_types_changed",e.onChangeAvailablePickupTypes)},onChangeAvailablePickupTypes:function(){var e=s.dhl_parcel_finder,a=s.dhl_parcel_locator,p=e.getModal(),e=a.getShippingMethod(),a=a.getShippingMethodData(e);a&&(p.find(".finder-pickup-type").addClass("hidden"),t.each(a.supports,function(e,a){a=p.find('.finder-pickup-type[data-pickup_type="'+a+'"]');a.find("input[type=checkbox]").prop("checked",!0),a.removeClass("hidden")}))},openModal:function(){var e=s.dhl_parcel_finder,a=e.getModal(),p=(0

'+e+"

")}},t(document).ready(function(){s.dhl_parcel_finder.init()})}(jQuery,window.germanized); \ No newline at end of file diff --git a/packages/woocommerce-germanized-dhl/assets/js/parcel-locator.js b/packages/woocommerce-germanized-dhl/assets/js/parcel-locator.js new file mode 100644 index 000000000..fa5a2fcdd --- /dev/null +++ b/packages/woocommerce-germanized-dhl/assets/js/parcel-locator.js @@ -0,0 +1,505 @@ + +window.germanized = window.germanized || {}; +window.germanized.dhl_parcel_locator = window.germanized.dhl_parcel_locator || {}; + +( function( $, germanized ) { + + /** + * Core + */ + germanized.dhl_parcel_locator = { + + params: {}, + parcelShops: [], + wrapper: '', + + init: function () { + var self = germanized.dhl_parcel_locator; + self.params = wc_gzd_dhl_parcel_locator_params; + self.wrapper = self.params.wrapper; + + $( document ) + .on( 'change.dhl', self.wrapper + ' #shipping_address_type', self.refreshAddressType ) + .on( 'change.dhl', self.wrapper + ' #shipping_address_1', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #shipping_address_2', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #shipping_postcode', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #shipping_country', self.onChangeAddress ) + .on( 'change.dhl', self.wrapper + ' #ship-to-different-address-checkbox', self.onChangeShipping ) + .on( 'change.dhl', self.wrapper + ' #shipping_country', self.refreshAvailability ) + .on( 'input.dhl validate.dhl change.dhl', self.wrapper + ' #shipping_dhl_postnumber', self.validatePostnumber ); + + $( document.body ).on( 'payment_method_selected', self.triggerCheckoutRefresh ); + $( document.body ).on( 'updated_checkout', self.afterRefreshCheckout ); + + if ( ! self.isCheckout() ) { + $( document.body ).on( 'country_to_state_changing', function() { + var self = germanized.dhl_parcel_locator; + + setTimeout( function() { + self.refreshAddressType(); + }, 500 ); + } ); + } + + self.refreshAvailability(); + self.refreshAddressType(); + }, + + validatePostnumber: function( e ) { + var $this = $( this ), + $parent = $this.closest( '.form-row' ), + eventType = e.type; + + if ( 'input' === eventType ) { + if ( $this.val() ) { + $this.val( $this.val().replace( /\D/g,'' ) ); + } + } + + if ( 'validate' === eventType || 'change' === eventType ) { + if ( $this.val() ) { + $this.val( $this.val().replace( /\D/g,'' ) ); + + if ( $this.val().toString().length < 6 || $this.val().toString().length > 12 ) { + $parent.removeClass( 'woocommerce-validated' ).addClass( 'woocommerce-invalid woocommerce-invalid-postnumber' ); + } + } + } + }, + + triggerCheckoutRefresh: function() { + $( document.body ).trigger( 'update_checkout' ); + }, + + isCheckout: function() { + var self = germanized.dhl_parcel_locator; + + return self.params.is_checkout; + }, + + afterRefreshCheckout: function() { + var self = germanized.dhl_parcel_locator; + + var params = { + 'security': self.params.parcel_locator_data_nonce, + 'action' : 'woocommerce_gzd_dhl_parcel_locator_refresh_shipping_data' + }; + + $.ajax({ + type: "POST", + url: self.params.ajax_url, + data: params, + success: function( data ) { + // Update shipping method data from session + self.params['methods'] = data.methods; + self.refreshAvailability(); + }, + error: function( data ) { + self.refreshAvailability(); + }, + dataType: 'json' + }); + }, + + refreshAvailability: function() { + var self = germanized.dhl_parcel_locator, + shippingMethod = self.getShippingMethod(), + methodData = self.getShippingMethodData( shippingMethod ); + + if ( ! self.isAvailable() ) { + $( self.wrapper + ' #shipping_address_type' ).val( 'regular' ).trigger( 'change' ); + $( self.wrapper + ' #shipping_address_type_field' ).hide(); + } else { + var $typeField = $( self.wrapper + ' #shipping_address_type' ); + var selected = $typeField.val(); + + if ( self.isCheckout() ) { + $typeField.html( '' ); + + if ( methodData ) { + $.each( methodData.address_type_options, function( name, title ) { + $typeField.append( $( '"),$quantity.data("max-quantity-"+t,i.max_quantity)}),n(".wc-backbone-modal-content article").unblock(),n(document.body).on("change","input#wc-gzd-shipment-add-items-quantity",function(){var t=$select.val(),i=n(this).val();$quantity.data("max-quantity-"+t)&&(t=$quantity.data("max-quantity-"+t)) 0 ) { + + var actionData = self.params.bulk_actions[ action ]; + + $( '.bulk-action-wrapper' ).find( '.bulk-title' ).text( actionData['title'] ); + $( '#posts-filter' ).addClass( 'bulk-action-processing' ); + $( '#posts-filter' ).find( '.bulkactions button' ).prop( 'disabled', true ); + + // Handle bulk action processing + self.handleBulkAction( action, 1, ids, type ); + + return false; + } + }, + + handleBulkAction: function( action, step, ids, type ) { + var self = germanized.admin.shipments_table, + actionData = self.params.bulk_actions[ action ]; + + $.ajax( { + type: 'POST', + url: self.params.ajax_url, + data: { + action : 'woocommerce_gzd_shipments_bulk_action_handle', + bulk_action : action, + step : step, + type : type, + ids : ids, + security : actionData['nonce'] + }, + dataType: 'json', + success: function( response ) { + if ( response.success ) { + + if ( 'done' === response.data.step ) { + $( '.bulk-action-wrapper' ).find( '.woocommerce-shimpents-bulk-progress' ).val( response.data.percentage ); + + window.location = response.data.url; + + setTimeout( function() { + $( '#posts-filter' ).removeClass( 'bulk-action-processing' ); + $( '#posts-filter' ).find( '.bulkactions button' ).prop( 'disabled', false ); + }, 2000 ); + } else { + $( '.bulk-action-wrapper' ).find( '.woocommerce-shimpents-bulk-progress' ).val( response.data.percentage ); + self.handleBulkAction( action, parseInt( response.data.step, 10 ), response.data.ids, response.data.type ); + } + } + } + }).fail( function( response ) { + window.console.log( response ); + } ); + }, + + initTipTip: function() { + $( '.column-actions .wc-gzd-shipment-action-button' ).tipTip( { + 'fadeIn': 50, + 'fadeOut': 50, + 'delay': 200 + }); + }, + + initEnhanced: function() { + try { + $( document.body ) + .on( 'wc-enhanced-select-init', function() { + // Ajax order search boxes + $( ':input.wc-gzd-order-search' ).filter( ':not(.enhanced)' ).each( function() { + var select2_args = { + allowClear: $( this ).data( 'allow_clear' ) ? true : false, + placeholder: $( this ).data( 'placeholder' ), + minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '1', + escapeMarkup: function( m ) { + return m; + }, + ajax: { + url: wc_gzd_admin_shipments_table_params.ajax_url, + dataType: 'json', + delay: 1000, + data: function( params ) { + return { + term: params.term, + action: 'woocommerce_gzd_json_search_orders', + security: wc_gzd_admin_shipments_table_params.search_orders_nonce, + exclude: $( this ).data( 'exclude' ) + }; + }, + processResults: function( data ) { + var terms = []; + if ( data ) { + $.each( data, function( id, text ) { + terms.push({ + id: id, + text: text + }); + }); + } + return { + results: terms + }; + }, + cache: true + } + }; + + $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); + }); + + $( ':input.wc-gzd-shipping-provider-search' ).filter( ':not(.enhanced)' ).each( function() { + var select2_args = { + allowClear: $( this ).data( 'allow_clear' ) ? true : false, + placeholder: $( this ).data( 'placeholder' ), + minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '1', + escapeMarkup: function( m ) { + return m; + }, + ajax: { + url: wc_gzd_admin_shipments_table_params.ajax_url, + dataType: 'json', + delay: 1000, + data: function( params ) { + return { + term: params.term, + action: 'woocommerce_gzd_json_search_shipping_provider', + security: wc_gzd_admin_shipments_table_params.search_shipping_provider_nonce, + exclude: $( this ).data( 'exclude' ) + }; + }, + processResults: function( data ) { + var terms = []; + if ( data ) { + $.each( data, function( id, text ) { + terms.push({ + id: id, + text: text + }); + }); + } + return { + results: terms + }; + }, + cache: true + } + }; + + $( this ).selectWoo( select2_args ).addClass( 'enhanced' ); + }); + }); + + $( 'html' ).on( 'click', function( event ) { + if ( this === event.target ) { + $( ':input.wc-gzd-order-search' ).filter( '.select2-hidden-accessible' ).selectWoo( 'close' ); + $( ':input.wc-gzd-shipping-provider-search' ).filter( '.select2-hidden-accessible' ).selectWoo( 'close' ); + } + } ); + } catch( err ) { + // If select2 failed (conflict?) log the error but don't stop other scripts breaking. + window.console.log( err ); + } + } + }; + + $( document ).ready( function() { + germanized.admin.shipments_table.init(); + }); + +})( jQuery, window.germanized.admin ); diff --git a/packages/woocommerce-germanized-shipments/assets/js/admin-shipments-table.min.js b/packages/woocommerce-germanized-shipments/assets/js/admin-shipments-table.min.js new file mode 100644 index 000000000..e66290fea --- /dev/null +++ b/packages/woocommerce-germanized-shipments/assets/js/admin-shipments-table.min.js @@ -0,0 +1 @@ +window.germanized=window.germanized||{},window.germanized.admin=window.germanized.admin||{},function(o){window.germanized.admin.shipments_table={params:{},init:function(){var e=germanized.admin.shipments_table;e.params=wc_gzd_admin_shipments_table_params,e.initEnhanced(),o(document).on("click","#doaction, #doaction2",e.onBulkSubmit).on("click",".wc-gzd-shipment-action-button-generate-label",e.onCreateLabel),o(document.body).on("init_tooltips",function(){e.initTipTip()}),e.initTipTip()},onCreateLabel:function(){germanized.admin.shipments_table;var e=o(this).parents("tr").find("th.check-column input").val();return o(this).parents("td").WCBackboneModal({template:"wc-gzd-modal-create-shipment-label-"+e}),!1},onBulkSubmit:function(){var e,t=germanized.admin.shipments_table,n=o(this).parents(".bulkactions").find("select[name^=action]").val(),a=o(this).parents("#posts-filter").find("input.shipment_type").val(),i=[];if(o("#posts-filter").find('input[name="shipment[]"]:checked').each(function(){i.push(o(this).val())}),t.params.bulk_actions.hasOwnProperty(n)&&0 0 ) { + return $shipment.data( 'shipment' ); + } + + return false; + }, + + block: function() { + var self = germanized.admin.shipments; + + self.$wrapper.block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + }, + + unblock: function() { + var self = germanized.admin.shipments; + + self.$wrapper.unblock(); + }, + + getData: function( additionalData ) { + var self = germanized.admin.shipments, + data = {}; + + additionalData = additionalData || {}; + + $.each( self.$wrapper.find( ':input[name]' ).serializeArray(), function( index, item ) { + if ( item.name.indexOf( '[]' ) !== -1 ) { + item.name = item.name.replace( '[]', '' ); + data[ item.name ] = $.makeArray( data[ item.name ] ); + data[ item.name ].push( item.value ); + } else { + data[ item.name ] = item.value; + } + }); + + $.extend( data, additionalData ); + + return data; + }, + + doAjax: function( params, cSuccess, cError ) { + var self = germanized.admin.shipments, + url = self.params.ajax_url, + $wrapper = self.$wrapper, + refreshFragments = true; + + $wrapper.find( '.notice-wrapper' ).empty(); + + cSuccess = cSuccess || self.onAjaxSuccess; + cError = cError || self.onAjaxError; + + if ( params.hasOwnProperty( 'refresh_fragments' ) ) { + refreshFragments = params['refresh_fragments']; + } + + if ( ! params.hasOwnProperty( 'security' ) ) { + params['security'] = self.params.edit_shipments_nonce; + } + + if ( ! params.hasOwnProperty( 'order_id' ) ) { + params['order_id'] = self.params.order_id; + } + + params = self.getData( params ); + + $.ajax({ + type: "POST", + url: url, + data: params, + success: function( data ) { + if ( data.success ) { + + active = self.getShipment( self.getActiveShipmentId() ); + current_packaging_id = false; + + if ( active ) { + current_packaging_id = active.getShipment().find( '.shipment-packaging-select' ).val(); + } + + if ( refreshFragments ) { + if ( data.fragments ) { + $.each( data.fragments, function ( key, value ) { + $( key ).replaceWith( value ); + $( key ).unblock(); + } ); + } + } + + cSuccess.apply( $wrapper, [ data ] ); + + if ( data.hasOwnProperty( 'order_needs_new_shipments' ) ) { + self.setNeedsShipments( data.order_needs_new_shipments ); + } + + if ( data.hasOwnProperty( 'order_needs_new_returns' ) ) { + self.setNeedsReturns( data.order_needs_new_returns ); + } + + var shipmentData = data.hasOwnProperty( 'shipments' ) ? data.shipments : {}; + + $.each( self.getShipments(), function( shipmentId, shipment ) { + + if ( shipmentData.hasOwnProperty( shipmentId ) ) { + shipment.setIsEditable( shipmentData[ shipmentId ].is_editable ); + shipment.setNeedsItems( shipmentData[ shipmentId ].needs_items ); + shipment.setWeight( shipmentData[ shipmentId ].weight ); + shipment.setLength( shipmentData[ shipmentId ].length ); + shipment.setWidth( shipmentData[ shipmentId ].width ); + shipment.setHeight( shipmentData[ shipmentId ].height ); + shipment.setTotalWeight( shipmentData[ shipmentId ].total_weight ); + + self.initShipment( shipmentId ); + } + }); + + if ( ( data.hasOwnProperty( 'needs_refresh' ) || data.hasOwnProperty( 'needs_packaging_refresh' ) ) && data.hasOwnProperty( 'shipment_id' ) ) { + self.initShipment( data.shipment_id ); + + if ( data.hasOwnProperty( 'needs_packaging_refresh' ) ) { + active = self.getShipment( self.getActiveShipmentId() ); + + if ( active ) { + // Refresh dimensions in case the packaging has changed + new_packaging_id = active.getShipment().find( '.shipment-packaging-select' ).val(); + + if ( new_packaging_id !== current_packaging_id ) { + self.getShipment( data.shipment_id ).refreshDimensions(); + } + } + } + } + } else { + cError.apply( $wrapper, [ data ] ); + self.unblock(); + + if ( data.hasOwnProperty( 'message' ) ) { + self.addNotice( data.message, 'error' ); + } else if( data.hasOwnProperty( 'messages' ) ) { + $.each( data.messages, function( i, message ) { + self.addNotice( message, 'error' ); + }); + } + } + }, + error: function( data ) { + cError.apply( $wrapper, [ data ] ); + self.unblock(); + }, + dataType: 'json' + }); + }, + + onAjaxError: function( data ) { + + }, + + onAjaxSuccess: function( data ) { + + }, + + onRemoveNotice: function() { + $( this ).parents( '.notice' ).slideUp( 150, function() { + $( this ).remove(); + }); + }, + + addNotice: function( message, noticeType ) { + var self = germanized.admin.shipments; + + self.$wrapper.find( '.notice-wrapper' ).append( '

' + message + '

' ); + }, + + getParams: function() { + var self = germanized.admin.shipments; + + return self.params; + }, + + onRemoveShipment: function() { + var self = germanized.admin.shipments, + $shipment = $( this ).parents( '.order-shipment' ), + id = $shipment.data( 'shipment' ); + + var answer = window.confirm( self.getParams().i18n_remove_shipment_notice ); + + if ( answer ) { + self.removeShipment( id ); + } + + return false; + }, + + removeShipment: function( shipment_id ) { + var self = germanized.admin.shipments; + + var params = { + 'action' : 'woocommerce_gzd_remove_shipment', + 'shipment_id': shipment_id + }; + + self.block(); + self.doAjax( params, self.onRemoveShipmentSuccess, self.onRemoveShipmentError ); + }, + + onRemoveShipmentSuccess: function( data ) { + var self = germanized.admin.shipments, + shipmentIds = Array.isArray( data['shipment_id'] ) ? data['shipment_id'] : [data['shipment_id']]; + + $.each( shipmentIds, function( i, shipmentId ) { + var $shipment = self.$wrapper.find( '#shipment-' + shipmentId ); + + if ( $shipment.length > 0 ) { + if ( $shipment.hasClass( 'active' ) ) { + $shipment.find( '.shipment-content-wrapper' ).slideUp( 300, function() { + $shipment.removeClass( 'active' ); + $shipment.remove(); + + self.initShipments(); + }); + } else { + $shipment.remove(); + } + } + }); + + self.initShipments(); + self.unblock(); + }, + + onRemoveShipmentError: function( data ) { + var self = germanized.admin.shipments; + + self.unblock(); + }, + + onAddShipment: function() { + var self = germanized.admin.shipments; + + self.addShipment(); + + return false; + }, + + addShipment: function() { + var self = germanized.admin.shipments; + + var params = { + 'action': 'woocommerce_gzd_add_shipment' + }; + + self.block(); + self.doAjax( params, self.onAddShipmentSuccess, self.onAddShipmentError ); + }, + + onAddShipmentSuccess: function( data ) { + var self = germanized.admin.shipments; + + if ( self.$wrapper.find( '.order-shipment.active' ).length > 0 ) { + self.$wrapper.find( '.order-shipment.active' ).find( '.shipment-content-wrapper' ).slideUp( 300, function() { + + self.$wrapper.find( '.order-shipment.active' ).removeClass( 'active' ); + self.appendNewShipment( data ); + + self.initShipments(); + + // Init tiptip + self.initTiptip(); + self.unblock(); + }); + } else { + self.appendNewShipment( data ); + self.initShipments(); + + // Init tiptip + self.initTiptip(); + self.unblock(); + } + }, + + appendNewShipment: function( data ) { + var self = germanized.admin.shipments; + + if ( 'simple' === data['new_shipment_type'] && self.$wrapper.find( '.panel-order-return-title' ).length > 0 ) { + self.$wrapper.find( '.panel-order-return-title' ).before( data.new_shipment ); + } else { + self.$wrapper.find( '#order-shipments-list' ).append( data.new_shipment ); + } + }, + + onAddShipmentError: function( data ) { + + }, + + onAddReturn: function() { + + $( this ).WCBackboneModal({ + template: 'wc-gzd-modal-add-shipment-return' + }); + + return false; + }, + + addReturn: function( items ) { + var self = germanized.admin.shipments; + + self.block(); + + var params = { + 'action' : 'woocommerce_gzd_add_return_shipment' + }; + + $.extend( params, items ); + + self.doAjax( params, self.onAddReturnSuccess, self.onAddReturnError ); + }, + + onAddReturnSuccess: function( data ) { + var self = germanized.admin.shipments; + + self.onAddShipmentSuccess( data ); + }, + + onAddReturnError: function( data ) { + var self = germanized.admin.shipments; + + self.onAddShipmentError( data ); + }, + + setNeedsSaving: function( needsSaving ) { + var self = germanized.admin.shipments, + shipmentId = self.getActiveShipmentId(), + $shipment = shipmentId ? self.getShipment( shipmentId ).getShipment() : false; + + if ( typeof needsSaving !== "boolean" ) { + needsSaving = true; + } + + self.needsSaving = needsSaving === true; + + if ( self.needsSaving ) { + self.$wrapper.find( '#order-shipments-save' ).show(); + } else { + self.$wrapper.find( '#order-shipments-save' ).hide(); + } + + if ( $shipment ) { + if ( self.needsSaving ) { + self.disableCreateLabel( $shipment ); + } else { + self.enableCreateLabel( $shipment ); + } + } + + if ( self.needsSaving ) { + self.disableCreateLabel( $shipment ); + } else { + self.enableCreateLabel( $shipment ); + } + + self.hideOrShowFooter(); + + $( document.body ).trigger( 'woocommerce_gzd_shipments_needs_saving', [ self.needsSaving, self.getActiveShipmentId() ] ); + + self.initTiptip(); + }, + + disableCreateLabel: function( $shipment ) { + var self = germanized.admin.shipments, + $button = $shipment.find( '.create-shipment-label' ); + + if ( $button.length > 0 ) { + $button.addClass( 'disabled button-disabled' ); + $button.prop( 'title', self.params.i18n_create_label_disabled ); + } + }, + + enableCreateLabel: function( $shipment ) { + var self = germanized.admin.shipments, + $button = $shipment.find( '.create-shipment-label' ); + + if ( $button.length > 0 ) { + $button.removeClass( 'disabled button-disabled' ); + $button.prop( 'title', self.params.i18n_create_label_enabled ); + } + }, + + setNeedsShipments: function( needsShipments ) { + var self = germanized.admin.shipments; + + if ( typeof needsShipments !== "boolean" ) { + needsShipments = true; + } + + self.needsShipments = needsShipments === true; + + if ( self.needsShipments ) { + self.$wrapper.addClass( 'needs-shipments' ); + self.$wrapper.find( '#order-shipment-add' ).show(); + } else { + self.$wrapper.removeClass( 'needs-shipments' ); + self.$wrapper.find( '#order-shipment-add' ).hide(); + } + + self.hideOrShowFooter(); + }, + + hideOrShowReturnTitle: function() { + var self = germanized.admin.shipments; + + if ( self.$wrapper.find( '.order-shipment.shipment-return' ).length === 0 ) { + self.$wrapper.find( '.panel-order-return-title' ).addClass( 'hide-default' ); + } else { + self.$wrapper.find( '.panel-order-return-title' ).removeClass( 'hide-default' ); + } + }, + + setNeedsReturns: function( needsReturns ) { + var self = germanized.admin.shipments; + + if ( typeof needsReturns !== "boolean" ) { + needsReturns = true; + } + + self.needsReturns = needsReturns === true; + + if ( self.needsReturns ) { + self.$wrapper.addClass( 'needs-returns' ); + self.$wrapper.find( '#order-return-shipment-add' ).show(); + } else { + self.$wrapper.removeClass( 'needs-returns' ); + self.$wrapper.find( '#order-return-shipment-add' ).hide(); + } + + self.hideOrShowFooter(); + }, + + hideOrShowFooter: function() { + var self = germanized.admin.shipments; + + if ( self.needsSaving || self.needsShipments || self.needsReturns ) { + self.$wrapper.find( '.panel-footer' ).slideDown( 300 ); + } else { + self.$wrapper.find( '.panel-footer' ).slideUp( 300 ); + } + }, + + onToggleShipment: function() { + var self = germanized.admin.shipments, + $shipment = $( this ).parents( '.order-shipment:first' ), + isActive = $shipment.hasClass( 'active' ); + + self.closeShipments(); + + if ( ! isActive ) { + $shipment.find( '> .shipment-content-wrapper' ).slideDown( 300, function() { + $shipment.addClass( 'active' ); + }); + } + }, + + closeShipments: function() { + var self = germanized.admin.shipments; + + self.$wrapper.find( '.order-shipment.active .shipment-content-wrapper' ).slideUp( 300, function() { + self.$wrapper.find( '.order-shipment.active' ).removeClass( 'active' ); + }); + }, + + initShipments: function() { + var self = germanized.admin.shipments; + + // Refresh wrapper + self.$wrapper = $( '#panel-order-shipments' ); + + self.$wrapper.find( '.order-shipment' ).each( function() { + var id = $( this ).data( 'shipment' ); + + self.initShipment( id ); + }); + + self.hideOrShowReturnTitle(); + }, + + getShipments: function() { + var self = germanized.admin.shipments; + + return self.shipments; + }, + + getShipment: function( shipment_id ) { + var self = germanized.admin.shipments, + shipments = self.getShipments(); + + if ( shipments.hasOwnProperty( shipment_id ) ) { + return shipments[ shipment_id ]; + } + + return false; + }, + + refresh: function( shipment_id ) { + + }, + + refreshItems: function( shipment_id ) { + + }, + + addItem: function() { + + }, + + initTiptip: function() { + var self = germanized.admin.shipments; + + // Tooltips + $( document.body ).trigger( 'init_tooltips' ); + + self.$wrapper.find( '.woocommerce-help-tip' ).tipTip({ + 'attribute': 'data-tip', + 'fadeIn': 50, + 'fadeOut': 50, + 'delay': 200 + }); + + self.$wrapper.find( '.create-shipment-label' ).tipTip( { + 'fadeIn': 50, + 'fadeOut': 50, + 'delay': 200 + } ); + }, + + backbone: { + + onAddReturnSuccess: function( data ) { + $( '#wc-gzd-return-shipment-items' ).html( data.html ); + $( '.wc-backbone-modal-content article' ).unblock(); + + $( document.body ).on( 'change', 'input.wc-gzd-shipment-add-return-item-quantity', function() { + var $select = $( this ), + quantity = $select.val(); + + if ( $select.attr( 'max' ) ) { + var maxQuantity = $select.attr( 'max' ); + + if ( quantity > maxQuantity ) { + $select.val( maxQuantity ); + } + } + }); + }, + + init: function ( e, target ) { + var self = germanized.admin.shipments; + + if( ( 'wc-gzd-modal-add-shipment-return' ) === target ) { + $( '.wc-backbone-modal-content article' ).block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + + self.doAjax( { + 'action' : 'woocommerce_gzd_get_available_return_shipment_items' + }, self.backbone.onAddReturnSuccess ); + + return false; + } + }, + + response: function ( e, target, data ) { + var self = germanized.admin.shipments; + + if( ( 'wc-gzd-modal-add-shipment-return' ) === target ) { + self.addReturn( data ); + } + } + } + }; + + $( document ).ready( function() { + germanized.admin.shipments.init(); + }); + +})( jQuery, window.germanized.admin ); diff --git a/packages/woocommerce-germanized-shipments/assets/js/admin-shipments.min.js b/packages/woocommerce-germanized-shipments/assets/js/admin-shipments.min.js new file mode 100644 index 000000000..625e0f9ee --- /dev/null +++ b/packages/woocommerce-germanized-shipments/assets/js/admin-shipments.min.js @@ -0,0 +1 @@ +window.germanized=window.germanized||{},window.germanized.admin=window.germanized.admin||{},function(d){window.germanized.admin.shipments={params:{},shipments:{},$wrapper:!1,needsSaving:!1,needsShipments:!0,needsReturns:!1,init:function(){var e=germanized.admin.shipments;e.params=wc_gzd_admin_shipments_params,e.$wrapper=d("#panel-order-shipments"),e.needsShipments=e.$wrapper.find("#order-shipment-add").is(":visible"),e.needsReturns=e.$wrapper.find("#order-return-shipment-add").is(":visible"),e.initShipments(),d(document).ajaxComplete(e.onAjaxComplete),d(document).on("click","#order-shipments-list .shipment-header",e.onToggleShipment).on("change","#order-shipments-list :input:visible",e.setNeedsSaving).on("click","#panel-order-shipments #order-shipment-add",e.onAddShipment).on("click","#panel-order-shipments #order-return-shipment-add",e.onAddReturn).on("click","#panel-order-shipments .remove-shipment",e.onRemoveShipment).on("click","#panel-order-shipments button#order-shipments-save",e.onSave).on("click","#panel-order-shipments .notice-dismiss",e.onRemoveNotice),d(document.body).on("wc_backbone_modal_loaded",e.backbone.init).on("wc_backbone_modal_response",e.backbone.response)},onAjaxComplete:function(e,n,i){var t=germanized.admin.shipments;if(null!=n&&i.hasOwnProperty("data")){var n=i.data,i=!1;try{i=JSON.parse('{"'+n.replace(/&/g,'","').replace(/=/g,'":"')+'"}',function(e,n){return""===e?n:decodeURIComponent(n)})}catch(e){i=!1}i&&i.hasOwnProperty("action")&&("woocommerce_save_order_items"!==(n=i.action)&&"woocommerce_remove_order_item"!==n&&"woocommerce_add_order_item"!==n&&"woocommerce_delete_refund"!==n||t.syncItemQuantities())}},syncItemQuantities:function(){var e=germanized.admin.shipments,n=(e.block(),{action:"woocommerce_gzd_validate_shipment_item_quantities",active:e.getActiveShipmentId()});e.doAjax(n,e.onSyncSuccess)},onSyncSuccess:function(e){var n=germanized.admin.shipments;n.unblock(),n.initShipments(),n.initTiptip()},onSave:function(e){var n=germanized.admin.shipments;return e.preventDefault(),n.save(),!1},save:function(){var e=germanized.admin.shipments,n=(e.block(),{action:"woocommerce_gzd_save_shipments",active:e.getActiveShipmentId()});e.doAjax(n,e.onSaveSuccess)},initShipment:function(e){var n=germanized.admin.shipments;n.shipments.hasOwnProperty(e)?n.shipments[e].refreshDom():n.shipments[e]=new d.GermanizedShipment(e)},onSaveSuccess:function(e){var n=germanized.admin.shipments;n.initShipments(),n.setNeedsSaving(!1),n.unblock(),n.initTiptip()},getActiveShipmentId:function(){var e=germanized.admin.shipments.$wrapper.find(".order-shipment.active");return 0

'+e+'

')},getParams:function(){return germanized.admin.shipments.params},onRemoveShipment:function(){var e=germanized.admin.shipments,n=d(this).parents(".order-shipment").data("shipment");return window.confirm(e.getParams().i18n_remove_shipment_notice)&&e.removeShipment(n),!1},removeShipment:function(e){var n=germanized.admin.shipments,e={action:"woocommerce_gzd_remove_shipment",shipment_id:e};n.block(),n.doAjax(e,n.onRemoveShipmentSuccess,n.onRemoveShipmentError)},onRemoveShipmentSuccess:function(e){var t=germanized.admin.shipments,e=Array.isArray(e.shipment_id)?e.shipment_id:[e.shipment_id];d.each(e,function(e,n){var i=t.$wrapper.find("#shipment-"+n);0 .shipment-content-wrapper").slideDown(300,function(){n.addClass("active")})},closeShipments:function(){var e=germanized.admin.shipments;e.$wrapper.find(".order-shipment.active .shipment-content-wrapper").slideUp(300,function(){e.$wrapper.find(".order-shipment.active").removeClass("active")})},initShipments:function(){var n=germanized.admin.shipments;n.$wrapper=d("#panel-order-shipments"),n.$wrapper.find(".order-shipment").each(function(){var e=d(this).data("shipment");n.initShipment(e)}),n.hideOrShowReturnTitle()},getShipments:function(){return germanized.admin.shipments.shipments},getShipment:function(e){var n=germanized.admin.shipments.getShipments();return!!n.hasOwnProperty(e)&&n[e]},refresh:function(e){},refreshItems:function(e){},addItem:function(){},initTiptip:function(){var e=germanized.admin.shipments;d(document.body).trigger("init_tooltips"),e.$wrapper.find(".woocommerce-help-tip").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200}),e.$wrapper.find(".create-shipment-label").tipTip({fadeIn:50,fadeOut:50,delay:200})},backbone:{onAddReturnSuccess:function(e){d("#wc-gzd-return-shipment-items").html(e.html),d(".wc-backbone-modal-content article").unblock(),d(document.body).on("change","input.wc-gzd-shipment-add-return-item-quantity",function(){var e,n=d(this),i=n.val();n.attr("max")&&(e=n.attr("max")) 0 ) { + $( 'select[id$=shipping_provider]' ).trigger( 'change' ); + } + }, + + parseFieldId: function( fieldId ) { + return fieldId.replace( '[', '_' ).replace( ']', '' ); + }, + + onChangeField: function() { + var self = germanized.admin.shipping_provider_method, + $wrapper = $( this ).parents( 'form' ), + fieldId = self.parseFieldId( $( this ).attr( 'id' ) ), + val = $( this ).val(), + currentProvider = self.currentProvider; + + if ( currentProvider && fieldId.toLowerCase().indexOf( '_' + currentProvider + '_' ) >= 0 ) { + // Remove the shipping method name prefix + var fieldIdClean = fieldId.substring( fieldId.lastIndexOf( currentProvider + '_' ), fieldId.length ); + + $wrapper.find( ':input[data-show_if_' + fieldIdClean + ']' ).parents( 'tr' ).hide(); + + if ( $( this ).is( ':checkbox' ) ) { + if ( $( this ).is( ':checked' ) ) { + $wrapper.find( ':input[data-show_if_' + fieldIdClean + ']' ).parents( 'tr' ).show(); + } + } else { + $wrapper.find( ':input[data-show_if_' + fieldIdClean + '*="' + val + '"]' ).parents( 'tr' ).show(); + } + } + }, + + onShippingMethodOpen: function( e, t ) { + if ( 'wc-modal-shipping-method-settings' === t ) { + $wrapper = $( '.wc-modal-shipping-method-settings' ); + + if ( $( 'select[id$=shipping_provider]' ).length > 0 ) { + $wrapper.find( 'select[id$=shipping_provider]' ).trigger( 'change' ); + } + } + }, + + showOrHideAll: function() { + var self = germanized.admin.shipping_provider_method, + $select = $( this ), + $providers = $select.find( 'option' ), + $form = $select.parents( 'form' ); + + self.currentProvider = $select.val(); + + $providers.each( function() { + var $provider = $( this ), + provider_setting_prefix = $provider.val(); + + if ( provider_setting_prefix.length > 0 ) { + $form.find( 'table.form-table' ).each( function() { + if ( $( this ).find( ':input[id*=_' + provider_setting_prefix + '_]' ).length > 0 ) { + self.hideTable( $( this ) ); + } + }); + } + }); + + if ( self.currentProvider.length > 0 ) { + $form.find( 'table.form-table' ).each( function() { + if ( $( this ).find( ':input[id*=_' + self.currentProvider + '_]' ).length > 0 ) { + self.showTable( $( this ) ); + } + }); + + // Trigger show/hide + $form.find( ':input[id*=_' + self.currentProvider + '_]:visible' ).trigger( 'change' ); + } + }, + + hideTable: function( $table ) { + if ( $table.find( 'select[id$=shipping_provider]' ).length > 0 ) { + return false; + } + + $table.prevUntil( 'table.form-table' ).hide(); + $table.hide(); + }, + + showTable: function( $table ) { + $table.prevUntil( 'table.form-table' ).show(); + $table.show(); + } + }; + + $( document ).ready( function() { + germanized.admin.shipping_provider_method.init(); + }); + +})( jQuery, window.germanized.admin ); diff --git a/packages/woocommerce-germanized-shipments/assets/js/admin-shipping-provider-method.min.js b/packages/woocommerce-germanized-shipments/assets/js/admin-shipping-provider-method.min.js new file mode 100644 index 000000000..2a4c359bb --- /dev/null +++ b/packages/woocommerce-germanized-shipments/assets/js/admin-shipping-provider-method.min.js @@ -0,0 +1 @@ +window.germanized=window.germanized||{},window.germanized.admin=window.germanized.admin||{},function(d){window.germanized.admin.shipping_provider_method={params:{},currentProvider:"",init:function(){var i=germanized.admin.shipping_provider_method;i.params=wc_gzd_admin_shipping_provider_method_params,d(document).on("change","select[id$=shipping_provider]",i.showOrHideAll).on("change",":input:visible[id]",i.onChangeField),d(document.body).on("wc_backbone_modal_loaded",i.onShippingMethodOpen),0 0 ) { + return $element.parents( 'tr' ).data( 'shipping-provider' ); + } + + return false; + }, + + onRemoveProvider: function() { + var self = germanized.admin.shipping_providers, + provider = self.getProviderName( $( this ) ); + + if ( provider ) { + var answer = window.confirm( self.getParams().i18n_remove_shipping_provider_notice ); + + if ( answer ) { + self.removeProvider( provider ); + } + } + + return false; + }, + + removeProvider: function( id ) { + var self = germanized.admin.shipping_providers, + params = { + 'action' : 'woocommerce_gzd_remove_shipping_provider', + 'provider': id, + 'security': self.getParams().remove_shipping_provider_nonce + }; + + self.block(); + self.doAjax( params, self.onRemoveProviderSuccess ); + }, + + onRemoveProviderSuccess: function( data ) { + var self = germanized.admin.shipping_providers, + $provider = self.$wrapper.find( 'tr[data-shipping-provider="' + data['provider'] + '"]' ); + + if ( $provider.length > 0 ) { + $provider.remove(); + } + }, + + block: function() { + var self = germanized.admin.shipping_providers; + + self.$wrapper.block({ + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + }); + }, + + unblock: function() { + var self = germanized.admin.shipping_providers; + + self.$wrapper.unblock(); + }, + + doAjax: function( params, cSuccess, cError ) { + var self = germanized.admin.shipping_providers, + url = self.params.ajax_url, + $wrapper = self.$wrapper; + + cSuccess = cSuccess || self.onAjaxSuccess; + cError = cError || self.onAjaxError; + + if ( ! params.hasOwnProperty( 'security' ) ) { + params['security'] = self.params.edit_shipping_providers_nonce; + } + + $.ajax({ + type: "POST", + url: url, + data: params, + success: function( data ) { + if ( data.success ) { + cSuccess.apply( $wrapper, [ data ] ); + self.unblock(); + } else { + cError.apply( $wrapper, [ data ] ); + self.unblock(); + } + }, + error: function( data ) { + cError.apply( $wrapper, [ data ] ); + }, + dataType: 'json' + }); + }, + + onAjaxError: function( data ) { + + }, + + onAjaxSuccess: function( data ) { + + }, + + getParams: function() { + var self = germanized.admin.shipping_providers; + + return self.params; + } + }; + + $( document ).ready( function() { + germanized.admin.shipping_providers.init(); + }); + +})( jQuery, window.germanized.admin ); diff --git a/packages/woocommerce-germanized-shipments/assets/js/admin-shipping-providers.min.js b/packages/woocommerce-germanized-shipments/assets/js/admin-shipping-providers.min.js new file mode 100644 index 000000000..241d6e6a8 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/assets/js/admin-shipping-providers.min.js @@ -0,0 +1 @@ +window.germanized=window.germanized||{},window.germanized.admin=window.germanized.admin||{},function(a){window.germanized.admin.shipping_providers={params:{},$wrapper:"",init:function(){var e=germanized.admin.shipping_providers;e.params=wc_gzd_admin_shipping_providers_params,e.$wrapper=a(".wc-gzd-shipping-providers"),a(document).on("click",".wc-gzd-shipping-provider-delete",e.onRemoveProvider).on("change",".wc-gzd-shipping-providers input.wc-gzd-shipping-provider-activated-checkbox",this.onChangeProviderStatus),a(window).on("load",function(){a(document).on("updateMoveButtons","table.wc-gzd-shipping-providers",e.onChangeSort)}),a("table.wc-gzd-shipping-providers tbody").sortable({items:"tr",cursor:"move",axis:"y",handle:"td.sort",scrollSensitivity:40,helper:function(e,i){return i.children().each(function(){a(this).width(a(this).width())}),i.css("left","0"),i},start:function(e,i){i.item.css("background-color","#f6f6f6")},stop:function(e,i){i.item.removeAttr("style"),i.item.trigger("updateMoveButtons")}})},onChangeSort:function(){var e=a(this).find('input[name="provider_order[]"]').map(function(){return a(this).val()}).get(),i=germanized.admin.shipping_providers,e={action:"woocommerce_gzd_sort_shipping_provider",order:e,security:i.getParams().sort_shipping_provider_nonce};i.doAjax(e)},onChangeProviderStatus:function(){var e=germanized.admin.shipping_providers,i=a(this),r=e.getProviderName(i),n=i.parents("td").find(".woocommerce-gzd-input-toggle"),i={action:"woocommerce_gzd_edit_shipping_provider_status",enable:i.is(":checked")?"yes":"no",provider:r};window.onbeforeunload="",n.addClass("woocommerce-input-toggle--loading"),e.doAjax(i,e.onChangeProviderStatusSucess)},onChangeProviderStatusSucess:function(e){var i=germanized.admin.shipping_providers.$wrapper.find('tr[data-shipping-provider="'+e.provider+'"]').find(".woocommerce-gzd-input-toggle");i.removeClass("woocommerce-input-toggle--loading"),i.removeClass("woocommerce-input-toggle--enabled, woocommerce-input-toggle--disabled"),"yes"===e.activated?i.addClass("woocommerce-input-toggle--enabled"):i.addClass("woocommerce-input-toggle--disabled")},getProviderName:function(e){germanized.admin.shipping_providers;return e.data("shipping-provider")?e.data("shipping-provider"):0 + +get_available_items_for_return() as $item_id => $item_data ) : + ?> + + + + + diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-content.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-content.php new file mode 100644 index 000000000..5c7a9b3e5 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-content.php @@ -0,0 +1,261 @@ + + +
+
+ + +
+
+
+

+ + +

+
+
+

+ + + + 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 ) : ?> +

+ + +

+ + +

+ + +

+ +

+ + +

+ + +
+ +
+
+
+
+ supports_label() && ( ( $label = $shipment->get_label() ) || $shipment->needs_label() ) ) : + include 'label/html-shipment-label.php'; + endif; + ?> +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ $column ) : ?> +
+ +
+ +
+
+ +
+ get_items() as $item ) : ?> + + +
+
+ +
+
+ +
+ +
+ +
+ + +
+
+ +
+ + + + +
+
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item-count.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item-count.php new file mode 100644 index 000000000..4028a912b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item-count.php @@ -0,0 +1,19 @@ + + + + get_shippable_item_count() ) > 0 ) : + $item_count = $shipment->get_item_count(); + ?> + + + diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item.php new file mode 100644 index 000000000..8fe650fd2 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-item.php @@ -0,0 +1,69 @@ + + +
+
+ $column ) : ?> + +
+ + + + get_name() ); ?> get_sku() ? '(' . esc_html( $item->get_sku() ) . ')' : '' ); ?> + + + + + + + + + + + + + + + + get_id(), $item, $shipment, $column_name ); + ?> +
+ + +
+ + +
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-list.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-list.php new file mode 100644 index 000000000..44a6178c0 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-list.php @@ -0,0 +1,36 @@ +get_return_shipments(); +?> + +
+ get_simple_shipments() as $shipment ) : + $is_active = ( $active_shipment && $shipment->get_id() === $active_shipment ) ? true : false; + + include 'html-order-shipment.php'; + ?> + + +
+

+ get_return_status() ) ); ?> +
+ + + get_id() === $active_shipment ) ? true : false; + include 'html-order-shipment.php'; + ?> + + +
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-packaging-select.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-packaging-select.php new file mode 100644 index 000000000..25fd7d779 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment-packaging-select.php @@ -0,0 +1,41 @@ +get_available_packaging(); +$available_packaging_ids = array(); + +foreach ( $available_packaging as $packaging ) { + $available_packaging_ids[] = (int) $packaging->get_id(); +} + +$current_packaging_id = $shipment->get_packaging_id(); +$default_packaging_id = \Vendidero\Germanized\Shipments\Package::get_setting( 'default_packaging' ); +$default_packaging = ! empty( $default_packaging_id ) ? wc_gzd_get_packaging( $default_packaging_id ) : false; +$default_exists_in_list = false; +?> + diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment.php new file mode 100644 index 000000000..c5191c14f --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipment.php @@ -0,0 +1,31 @@ + + +
+
+
+

get_type() ) ), esc_html( $shipment->get_shipment_number() ) ); ?>

+ get_status() ) ); ?> +
+ +
+ + +
+
+ +
+ +
+
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipments.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipments.php new file mode 100644 index 000000000..9f04ed95b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-order-shipments.php @@ -0,0 +1,94 @@ + + +
+
+ +
+

+ get_shipping_status() ) ); ?> +
+ +
+ + + + +
+
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/html-settings-provider-list.php b/packages/woocommerce-germanized-shipments/includes/admin/views/html-settings-provider-list.php new file mode 100644 index 000000000..f5d307883 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/html-settings-provider-list.php @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + $provider ) : ?> + + + + + + + + + +
+
+ + + +
+
+ get_title() ); ?> +
+ + is_manual_integration() ) : ?> + | + + +
+
+

get_description() ); ?>

+
+
+ + is_activated() ? 'yes' : 'no', 'yes' ); ?> + /> +
+
+ get_help_link() ) : ?> + + + + +
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-error.php b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-error.php new file mode 100644 index 000000000..600c9684e --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-error.php @@ -0,0 +1,22 @@ + +
+ +
+ get_error_messages() as $message ) : ?> +
+ +
+ +
+
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-form.php b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-form.php new file mode 100644 index 000000000..42a9b6f0c --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone-form.php @@ -0,0 +1,17 @@ + +
+ + + +
diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone.php b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone.php new file mode 100644 index 000000000..b1f35edf3 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label-backbone.php @@ -0,0 +1,34 @@ + + + diff --git a/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label.php b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label.php new file mode 100644 index 000000000..8133d77b9 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/admin/views/label/html-shipment-label.php @@ -0,0 +1,49 @@ + + +
+

get_shipping_provider() ) ) ); ?> has_label() && $shipment->get_tracking_id() ) ? wp_kses_post( Admin::get_shipment_tracking_html( $shipment ) ) : '' ); ?>

+ +
+
+ +
+ get_file() ) : ?> + + + + + +
+ +
+ + + +
+ +
+
+
diff --git a/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-guest-return-shipment-request.php b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-guest-return-shipment-request.php new file mode 100644 index 000000000..55e2ea4a4 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-guest-return-shipment-request.php @@ -0,0 +1,214 @@ +customer_email = true; + $this->id = 'customer_guest_return_shipment_request'; + $this->title = _x( 'Order guest return request', 'shipments', 'woocommerce-germanized' ); + $this->description = _x( 'Order guest return request are sent to the customer after submitting a new return request as a guest.', 'shipments', 'woocommerce-germanized' ); + + $this->template_html = 'emails/customer-guest-return-shipment-request.php'; + $this->template_plain = 'emails/plain/customer-guest-return-shipment-request.php'; + $this->template_base = Package::get_path() . '/templates/'; + $this->helper = function_exists( 'wc_gzd_get_email_helper' ) ? wc_gzd_get_email_helper( $this ) : false; + + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{order_number}' => '', + '{order_date}' => '', + ); + + // Call parent constructor. + parent::__construct(); + } + + /** + * Get email subject. + * + * @since 3.1.0 + * @return string + */ + public function get_default_subject() { + return _x( 'Your return request to your order {order_number}', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Get email heading. + * + * @since 3.1.0 + * @return string + */ + public function get_default_heading() { + return _x( 'Return request to your order: {order_number}', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Switch Woo and Germanized locale + */ + public function setup_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_switch_to_site_locale' ) && apply_filters( 'woocommerce_email_setup_locale', true ) ) { + wc_gzd_switch_to_site_locale(); + } + + parent::setup_locale(); + } + + /** + * Restore Woo and Germanized locale + */ + public function restore_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_restore_locale' ) && apply_filters( 'woocommerce_email_restore_locale', true ) ) { + wc_gzd_restore_locale(); + } + + parent::restore_locale(); + } + + /** + * Trigger. + * + * @param int $order_id order ID. + */ + public function trigger( $order_id ) { + if ( $this->helper ) { + $this->helper->setup_locale(); + } else { + $this->setup_locale(); + } + + if ( $this->object = wc_get_order( $order_id ) ) { + + $this->placeholders['{order_number}'] = $this->object->get_order_number(); + + if ( ( $order_shipment = wc_gzd_get_shipment_order( $this->object ) ) && ( $this->request_url = wc_gzd_get_order_customer_add_return_url( $this->object ) ) ) { + $this->recipient = $this->object->get_billing_email(); + $this->placeholders['{order_date}'] = wc_format_datetime( $this->object->get_date_created() ); + $this->placeholders['{order_number}'] = $this->object->get_order_number(); + } + } + + if ( $this->helper ) { + $this->helper->setup_email_locale(); + } + + if ( $this->is_enabled() && $this->get_recipient() ) { + $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); + } + + if ( $this->helper ) { + $this->helper->restore_email_locale(); + } + + if ( $this->helper ) { + $this->helper->restore_locale(); + } else { + $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( + 'order' => $this->object, + 'add_return_request_url' => $this->request_url, + 'email_heading' => $this->get_heading(), + 'additional_content' => $this->get_additional_content(), + 'sent_to_admin' => false, + 'plain_text' => false, + 'email' => $this, + ) + ); + } + + /** + * Get content plain. + * + * @return string + */ + public function get_content_plain() { + return wc_get_template_html( + $this->template_plain, + array( + 'order' => $this->object, + 'add_return_request_url' => $this->request_url, + 'email_heading' => $this->get_heading(), + 'additional_content' => $this->get_additional_content(), + 'sent_to_admin' => false, + 'plain_text' => true, + 'email' => $this, + ) + ); + } + + /** + * Default content to show below main email content. + * + * @since 1.0.1 + * @return string + */ + public function get_default_additional_content() { + return ''; + } + } + +endif; + +return new WC_GZD_Email_Customer_Guest_Return_Shipment_Request(); diff --git a/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment-delivered.php b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment-delivered.php new file mode 100644 index 000000000..9b319386d --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment-delivered.php @@ -0,0 +1,234 @@ +customer_email = true; + $this->id = 'customer_return_shipment_delivered'; + $this->title = _x( 'Order return delivered', 'shipments', 'woocommerce-germanized' ); + $this->description = _x( 'Order return notifications are sent to the customer after a return shipment has been returned (delivered) successfully.', 'shipments', 'woocommerce-germanized' ); + + $this->template_html = 'emails/customer-return-shipment-delivered.php'; + $this->template_plain = 'emails/plain/customer-return-shipment-delivered.php'; + $this->template_base = Package::get_path() . '/templates/'; + $this->helper = function_exists( 'wc_gzd_get_email_helper' ) ? wc_gzd_get_email_helper( $this ) : false; + + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{shipment_number}' => '', + '{order_number}' => '', + '{order_date}' => '', + '{date_sent}' => '', + ); + + // Triggers for this email. + add_action( 'woocommerce_gzd_return_shipment_status_processing_to_delivered_notification', array( $this, 'trigger' ), 10 ); + add_action( 'woocommerce_gzd_return_shipment_status_shipped_to_delivered_notification', array( $this, 'trigger' ), 10 ); + + // Call parent constructor. + parent::__construct(); + } + + /** + * Get email subject. + * + * @since 3.1.0 + * @return string + */ + public function get_default_subject() { + return _x( 'Return to your order {order_number} has been received', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Get email heading. + * + * @since 3.1.0 + * @return string + */ + public function get_default_heading() { + return _x( 'Received return to your order: {order_number}', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Switch Woo and Germanized locale + */ + public function setup_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_switch_to_site_locale' ) && apply_filters( 'woocommerce_email_setup_locale', true ) ) { + wc_gzd_switch_to_site_locale(); + } + + parent::setup_locale(); + } + + /** + * Restore Woo and Germanized locale + */ + public function restore_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_restore_locale' ) && apply_filters( 'woocommerce_email_restore_locale', true ) ) { + wc_gzd_restore_locale(); + } + + parent::restore_locale(); + } + + /** + * Trigger. + * + * @param int $shipment_id Shipment ID. + * @param bool $is_confirmation + */ + public function trigger( $shipment_id ) { + if ( $this->helper ) { + $this->helper->setup_locale(); + } else { + $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->recipient = $order_shipment->get_order()->get_billing_email(); + $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->shipment->get_date_sent() ) { + $this->placeholders['{date_sent}'] = wc_format_datetime( $this->shipment->get_date_sent() ); + } + } + } + + if ( $this->helper ) { + $this->helper->setup_email_locale(); + } + + if ( $this->is_enabled() && $this->get_recipient() ) { + $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); + } + + if ( $this->helper ) { + $this->helper->restore_email_locale(); + } + + if ( $this->helper ) { + $this->helper->restore_locale(); + } else { + $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' => false, + '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' => false, + 'plain_text' => true, + 'email' => $this, + ) + ); + } + + /** + * Default content to show below main email content. + * + * @since 1.0.1 + * @return string + */ + public function get_default_additional_content() { + return ''; + } + } + +endif; + +return new WC_GZD_Email_Customer_Return_Shipment_Delivered(); diff --git a/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment.php b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment.php new file mode 100644 index 000000000..3687a3ec4 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-return-shipment.php @@ -0,0 +1,267 @@ +customer_email = true; + $this->id = 'customer_return_shipment'; + $this->title = _x( 'Order return', 'shipments', 'woocommerce-germanized' ); + $this->description = _x( 'Order return notifications are sent to the customer after a return shipment was marked as processing.', 'shipments', 'woocommerce-germanized' ); + + $this->template_html = 'emails/customer-return-shipment.php'; + $this->template_plain = 'emails/plain/customer-return-shipment.php'; + $this->template_base = Package::get_path() . '/templates/'; + $this->helper = function_exists( 'wc_gzd_get_email_helper' ) ? wc_gzd_get_email_helper( $this ) : false; + + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{shipment_number}' => '', + '{order_number}' => '', + '{order_date}' => '', + '{date_sent}' => '', + ); + + // Triggers for this email. + add_action( 'woocommerce_gzd_return_shipment_status_draft_to_processing_notification', array( $this, 'trigger' ), 10 ); + add_action( 'woocommerce_gzd_return_shipment_status_requested_to_processing_notification', array( $this, 'trigger' ), 10 ); + + // Call parent constructor. + parent::__construct(); + } + + /** + * Get email subject. + * + * @since 3.1.0 + * @return string + */ + public function get_default_subject() { + return _x( 'Return to your order {order_number}', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Get email heading. + * + * @since 3.1.0 + * @return string + */ + public function get_default_heading() { + return _x( 'Return to your order: {order_number}', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Switch Woo and Germanized locale + */ + public function setup_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_switch_to_site_locale' ) && apply_filters( 'woocommerce_email_setup_locale', true ) ) { + wc_gzd_switch_to_site_locale(); + } + + parent::setup_locale(); + } + + /** + * Restore Woo and Germanized locale + */ + public function restore_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_restore_locale' ) && apply_filters( 'woocommerce_email_restore_locale', true ) ) { + wc_gzd_restore_locale(); + } + + parent::restore_locale(); + } + + /** + * Trigger. + * + * @param int $shipment_id Shipment ID. + * @param bool $is_confirmation + */ + public function trigger( $shipment_id, $is_confirmation = false ) { + if ( $this->helper ) { + $this->helper->setup_locale(); + } else { + $this->setup_locale(); + } + + $this->is_confirmation = $is_confirmation; + + if ( $this->shipment = wc_gzd_get_shipment( $shipment_id ) ) { + + if ( 'return' !== $this->shipment->get_type() ) { + return; + } + + // Check if this is a customer request. + if ( $this->shipment->is_customer_requested() ) { + $this->is_confirmation = true; + } + + $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->recipient = $order_shipment->get_order()->get_billing_email(); + $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->shipment->get_date_sent() ) { + $this->placeholders['{date_sent}'] = wc_format_datetime( $this->shipment->get_date_sent() ); + } + } + } + + $this->id = 'customer_return_shipment'; + + if ( $this->helper ) { + $this->helper->setup_email_locale(); + } + + if ( $this->is_enabled() && $this->get_recipient() ) { + $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); + } + + if ( $this->helper ) { + $this->helper->restore_email_locale(); + } + + if ( $this->helper ) { + $this->helper->restore_locale(); + } else { + $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, + 'is_confirmation' => $this->is_confirmation, + 'email_heading' => $this->get_heading(), + 'additional_content' => $this->get_additional_content(), + 'sent_to_admin' => false, + '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, + 'is_confirmation' => $this->is_confirmation, + 'email_heading' => $this->get_heading(), + 'additional_content' => $this->get_additional_content(), + 'sent_to_admin' => false, + '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 ''; + } + } + +endif; + +return new WC_GZD_Email_Customer_Return_Shipment(); diff --git a/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-shipment.php b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-shipment.php new file mode 100644 index 000000000..2fffd6cd0 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/emails/class-wc-gzd-email-customer-shipment.php @@ -0,0 +1,415 @@ +customer_email = true; + $this->id = 'customer_shipment'; + $this->title = _x( 'Order shipped', 'shipments', 'woocommerce-germanized' ); + $this->description = _x( 'Shipment notifications are sent to the customer when a shipment gets shipped.', 'shipments', 'woocommerce-germanized' ); + + $this->template_html = 'emails/customer-shipment.php'; + $this->template_plain = 'emails/plain/customer-shipment.php'; + $this->template_base = Package::get_path() . '/templates/'; + $this->helper = function_exists( 'wc_gzd_get_email_helper' ) ? wc_gzd_get_email_helper( $this ) : false; + + $this->placeholders = array( + '{site_title}' => $this->get_blogname(), + '{shipment_number}' => '', + '{order_number}' => '', + '{order_date}' => '', + '{date_sent}' => '', + '{current_shipment_num}' => '', + '{total_shipments}' => '', + ); + + // Triggers for this email. + if ( 'yes' === Package::get_setting( 'notify_enable' ) ) { + add_action( 'woocommerce_gzd_shipment_status_draft_to_shipped_notification', array( $this, 'trigger' ), 10 ); + add_action( 'woocommerce_gzd_shipment_status_processing_to_shipped_notification', array( $this, 'trigger' ), 10 ); + } + + // Call parent constructor. + parent::__construct(); + } + + /** + * Get email subject. + * + * @param bool $partial Whether it is a partial refund or a full refund. + * @since 3.1.0 + * @return string + */ + public function get_default_subject( $partial = false ) { + if ( $partial ) { + return _x( 'Your {site_title} order #{order_number} has been partially shipped ({current_shipment_num}/{total_shipments})', 'shipments', 'woocommerce-germanized' ); + } else { + return _x( 'Your {site_title} order #{order_number} has been shipped ({current_shipment_num}/{total_shipments})', 'shipments', 'woocommerce-germanized' ); + } + } + + /** + * Get email heading. + * + * @param bool $partial Whether it is a partial refund or a full refund. + * @since 3.1.0 + * @return string + */ + public function get_default_heading( $partial = false ) { + if ( $partial ) { + return _x( 'Partial shipment to your order: {order_number}', 'shipments', 'woocommerce-germanized' ); + } else { + return _x( 'Shipment to your order: {order_number}', 'shipments', 'woocommerce-germanized' ); + } + } + + /** + * Get email subject. + * + * @return string + */ + public function get_subject() { + if ( $this->partial_shipment ) { + $subject = $this->get_option( 'subject_partial', $this->get_default_subject( true ) ); + } else { + $subject = $this->get_option( 'subject_full', $this->get_default_subject() ); + } + + /** + * Filter to adjust the email subject for a shipped Shipment. + * + * @param string $subject The subject. + * @param WC_GZD_Email_Customer_Shipment $email The email instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_email_subject_customer_shipment', $this->format_string( $subject ), $this->object, $this ); + } + + /** + * Get email heading. + * + * @return string + */ + public function get_heading() { + if ( $this->partial_shipment ) { + $heading = $this->get_option( 'heading_partial', $this->get_default_heading( true ) ); + } else { + $heading = $this->get_option( 'heading_full', $this->get_default_heading() ); + } + + /** + * Filter to adjust the email heading for a shipped Shipment. + * + * @param string $heading The heading. + * @param WC_GZD_Email_Customer_Shipment $email The email instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_email_heading_customer_shipment', $this->format_string( $heading ), $this->object, $this ); + } + + /** + * Switch Woo and Germanized locale + */ + public function setup_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_switch_to_site_locale' ) && apply_filters( 'woocommerce_email_setup_locale', true ) ) { + wc_gzd_switch_to_site_locale(); + } + + parent::setup_locale(); + } + + /** + * Restore Woo and Germanized locale + */ + public function restore_locale() { + + if ( $this->is_customer_email() && function_exists( 'wc_gzd_restore_locale' ) && apply_filters( 'woocommerce_email_restore_locale', true ) ) { + wc_gzd_restore_locale(); + } + + parent::restore_locale(); + } + + public function get_shipped_position_number( $shipment_id ) { + $shipped_count = 1; + + if ( ! $this->object || ( ! $order = wc_gzd_get_shipment_order( $this->object ) ) ) { + return $shipped_count; + } + + if ( is_a( $shipment_id, '\Vendidero\Germanized\Shipments\Shipment' ) ) { + $shipment_id = $shipment_id->get_id(); + } + + if ( ! is_numeric( $shipment_id ) ) { + return $shipped_count; + } + + foreach ( $order->get_simple_shipments() as $key => $shipment ) { + if ( $shipment->is_shipped() ) { + if ( (int) $shipment->get_id() !== (int) $shipment_id ) { + $shipped_count++; + } + } + } + + return $shipped_count; + } + + /** + * Trigger. + * + * @param int $shipment_id Shipment ID. + */ + public function trigger( $shipment_id ) { + if ( $this->helper ) { + $this->helper->setup_locale(); + } else { + $this->setup_locale(); + } + + $this->partial_shipment = false; + + if ( $this->shipment = wc_gzd_get_shipment( $shipment_id ) ) { + + $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->total_shipments = count( $order_shipment->get_simple_shipments() ); + $this->cur_position = $this->get_shipped_position_number( $shipment_id ); + + if ( $order_shipment->needs_shipping() || $this->total_shipments > 1 ) { + if ( $order_shipment->needs_shipping() || ( $this->cur_position < $this->total_shipments ) ) { + $this->partial_shipment = true; + } + } + + $this->recipient = $order_shipment->get_order()->get_billing_email(); + $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(); + $this->placeholders['{total_shipments}'] = $this->total_shipments; + $this->placeholders['{current_shipment_num}'] = $this->cur_position; + + if ( $this->shipment->get_date_sent() ) { + $this->placeholders['{date_sent}'] = wc_format_datetime( $this->shipment->get_date_sent() ); + } + } + } + + $this->id = $this->partial_shipment ? 'customer_partial_shipment' : 'customer_shipment'; + + if ( $this->helper ) { + $this->helper->setup_email_locale(); + } + + if ( $this->is_enabled() && $this->get_recipient() ) { + $this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() ); + } + + if ( $this->helper ) { + $this->helper->restore_email_locale(); + } + + if ( $this->helper ) { + $this->helper->restore_locale(); + } else { + $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, + 'partial_shipment' => $this->partial_shipment, + 'cur_position' => $this->cur_position, + 'total_shipments' => $this->total_shipments, + 'email_heading' => $this->get_heading(), + 'additional_content' => $this->get_additional_content(), + 'sent_to_admin' => false, + '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, + 'partial_shipment' => $this->partial_shipment, + 'cur_position' => $this->cur_position, + 'total_shipments' => $this->total_shipments, + 'email_heading' => $this->get_heading(), + 'additional_content' => $this->get_additional_content(), + 'sent_to_admin' => false, + 'plain_text' => true, + 'email' => $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() { + /* translators: %s: list of placeholders */ + $placeholder_text = sprintf( _x( 'Available placeholders: %s', 'shipments', 'woocommerce-germanized' ), '' . 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..1d9785021 --- /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_shipment_setting( $shipment, 'label_minimum_shipment_weight' ), $unit, 'kg' ); + $default_weight = wc_get_weight( $provider->get_shipment_setting( $shipment, '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, 2 ); + + 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..6d949f7d2 --- /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() { + $data_store = \WC_Data_Store::load( 'packaging' ); + $list = $data_store->get_packaging_list(); + + 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() { + $list = wc_gzd_get_packaging_list(); + $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..9dde51ea1 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-functions.php @@ -0,0 +1,1527 @@ +countries ? WC()->countries->get_states( $country ) : array(); + $formatted_state = ( $states && isset( $states[ $state ] ) ) ? $states[ $state ] : $state; + + return $formatted_state; +} + +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; + } + } + + 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_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 ShippingProvider\Method + */ +function wc_gzd_get_shipping_provider_method( $instance_id ) { + $original_id = $instance_id; + $method = false; + $method_id = ''; + + 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; + } elseif ( ! is_numeric( $instance_id ) && is_string( $instance_id ) ) { + if ( strpos( $instance_id, ':' ) !== false ) { + $expl = explode( ':', $instance_id ); + $instance_id = ( ( ! empty( $expl ) && count( $expl ) > 1 ) ? (int) $expl[1] : 0 ); + $method_id = ( ! empty( $expl ) ) ? $expl[0] : $instance_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( '_', $instance_id ); + $numbers = array_values( array_filter( $expl, 'is_numeric' ) ); + $method_id = rtrim( preg_replace( '/[0-9]+/', '', $instance_id ), '_' ); + + if ( ! empty( $numbers ) ) { + $instance_id = absint( $numbers[0] ); + } else { + $instance_id = 0; + } + } + } + + if ( ! empty( $instance_id ) ) { + // Make sure shipping zones are loaded + include_once WC_ABSPATH . 'includes/class-wc-shipping-zones.php'; + + /** + * Cache methods within frontend + */ + if ( WC()->session && did_action( 'woocommerce_shipping_init' ) ) { + $cache_key = 'woocommerce_gzd_method_' . $instance_id; + $tmp_method = WC()->session->get( $cache_key ); + + if ( ! $tmp_method || ! is_object( $tmp_method ) || is_a( $tmp_method, '__PHP_Incomplete_Class' ) ) { + $method = WC_Shipping_Zones::get_shipping_method( $instance_id ); + + if ( $method ) { + WC()->session->set( $cache_key, $method ); + } + } else { + $method = $tmp_method; + } + } else { + $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 ( $method ) { + /** + * Filter to adjust the classname used to construct the shipping provider method + * which contains additional provider related settings useful for shipments. + * + * @param string $classname The classname. + * @param WC_Shipping_Method $method The shipping method instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + $classname = apply_filters( 'woocommerce_gzd_shipping_provider_method_classname', 'Vendidero\Germanized\Shipments\ShippingProvider\Method', $method ); + + return new $classname( $method ); + } + + // Load placeholder + $placeholder = new ShippingProvider\MethodPlaceholder( $original_id ); + + /** + * Filter to adjust the fallback shipping method to be loaded if no real + * shipping method was able to be constructed (e.g. a custom plugin is being used which + * replaces the default Woo shipping zones integration). + * + * @param ShippingProvider\MethodPlaceholder $placeholder The placeholder impl. + * @param string $original_id The shipping method id. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipping_provider_method_fallback', $placeholder, $original_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' ) ); +} + +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; +} + +function wc_gzd_get_shipping_providers() { + return ShippingProvider\Helper::instance()->get_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() { + $providers = wc_gzd_get_shipping_providers(); + $select = array( + '' => _x( 'None', 'shipments', 'woocommerce-germanized' ), + ); + + 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_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 + */ +function wc_gzd_get_shipment_order_shipping_method_id( $order ) { + $methods = $order->get_shipping_methods(); + $id = ''; + + if ( ! empty( $methods ) ) { + $method_vals = array_values( $methods ); + $method = array_shift( $method_vals ); + + if ( $method ) { + $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 ) { + if ( isset( $action['group'] ) ) { + $actions_html .= '
' . wc_gzd_render_shipment_action_buttons( $action['actions'] ) . '
'; + } elseif ( isset( $action['action'], $action['url'], $action['name'] ) ) { + $target = isset( $action['target'] ) ? $action['target'] : '_self'; + + $actions_html .= sprintf( '%5$s', esc_attr( $action['action'] ), esc_url( $action['url'] ), esc_attr( isset( $action['title'] ) ? $action['title'] : $action['name'] ), $target, 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 ); + } + + if ( ! $order ) { + return false; + } + + $provider = false; + + if ( $method = wc_gzd_get_shipping_provider_method( wc_gzd_get_shipment_order_shipping_method_id( $order ) ) ) { + $provider = $method->get_provider_instance(); + } + + /** + * 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 ''; + } +} + +/** + * 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..3f211f474 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/includes/wc-gzd-shipment-template-hooks.php @@ -0,0 +1,28 @@ + 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 '
' . esc_html_x( 'Invalid order.', 'shipments', 'woocommerce-germanized' ) . ' ' . esc_html_x( 'My account', 'shipments', 'woocommerce-germanized' ) . '
'; + + return; + } + + $shipments = wc_gzd_get_shipments( + array( + 'order_id' => $order_id, + 'type' => 'simple', + 'status' => wc_gzd_get_shipment_customer_visible_statuses(), + ) + ); + + $returns = wc_gzd_get_shipments( + array( + 'order_id' => $order_id, + 'type' => 'return', + 'status' => wc_gzd_get_shipment_customer_visible_statuses( 'return' ), + ) + ); + + wc_get_template( + 'myaccount/order-shipments.php', + array( + 'order_id' => $order_id, + 'order' => $order, + 'shipments' => $shipments, + 'returns' => $returns, + ) + ); + } +} + +if ( ! function_exists( 'woocommerce_gzd_shipments_template_view_shipment' ) ) { + + function woocommerce_gzd_shipments_template_view_shipment( $shipment_id ) { + + if ( ( ! ( $shipment = wc_gzd_get_shipment( $shipment_id ) ) ) || ( ! current_user_can( 'view_order', $shipment->get_order_id() ) ) ) { + echo '
' . esc_html_x( 'Invalid shipment.', 'shipments', 'woocommerce-germanized' ) . ' ' . esc_html_x( 'My account', 'shipments', 'woocommerce-germanized' ) . '
'; + + return; + } + + wc_get_template( + 'myaccount/view-shipment.php', + array( + 'shipment' => $shipment, + 'shipment_id' => $shipment_id, + ) + ); + } +} + +if ( ! function_exists( 'woocommerce_gzd_shipments_template_add_return_shipment' ) ) { + + function woocommerce_gzd_shipments_template_add_return_shipment( $order_id ) { + + if ( ( ! ( $order = wc_get_order( $order_id ) ) ) || ( ! wc_gzd_customer_can_add_return_shipment( $order_id ) ) ) { + echo '
' . esc_html_x( 'Invalid order.', 'shipments', 'woocommerce-germanized' ) . ' ' . esc_html_x( 'My account', 'shipments', 'woocommerce-germanized' ) . '
'; + return; + } + + if ( ! wc_gzd_order_is_customer_returnable( $order_id ) ) { + echo '
' . esc_html_x( 'Currently you cannot add new return requests to that order. If you have questions regarding the return of that order please contact us for further information.', 'shipments', 'woocommerce-germanized' ) . ( is_user_logged_in() ? ' ' . esc_html_x( 'View order', 'shipments', 'woocommerce-germanized' ) . '' : '' ) . '
'; + return; + } + + $notices = function_exists( 'wc_print_notices' ) ? wc_print_notices( true ) : ''; + + // Output notices in case notices have not been outputted yet. + if ( ! empty( $notices ) ) { + echo '
' . $notices . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + wc_get_template( + 'myaccount/add-return-shipment.php', + array( + 'order' => $order, + 'order_id' => $order_id, + 'shipment_order' => wc_gzd_get_shipment_order( $order ), + ) + ); + } +} + +if ( ! function_exists( 'woocommerce_gzd_shipment_details_table' ) ) { + + function woocommerce_gzd_shipment_details_table( $shipment_id ) { + if ( ! $shipment_id ) { + return; + } + + wc_get_template( + 'shipment/shipment-details.php', + array( + 'shipment_id' => $shipment_id, + ) + ); + } +} + +if ( ! function_exists( 'woocommerce_gzd_return_shipments_template_instructions' ) ) { + + function woocommerce_gzd_return_shipments_template_instructions( $shipment_id ) { + if ( $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + + if ( 'return' !== $shipment->get_type() ) { + return; + } + + if ( ! $shipment->has_status( array( 'processing', 'sent' ) ) ) { + return; + } + + wc_get_template( + 'shipment/shipment-return-instructions.php', + array( + 'shipment' => $shipment, + 'shipment_id' => $shipment_id, + ) + ); + } + } +} 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. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright © + + 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright © + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/packages/woocommerce-germanized-shipments/src/AddressSplitter.php b/packages/woocommerce-germanized-shipments/src/AddressSplitter.php new file mode 100644 index 000000000..2b59c5e0e --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/AddressSplitter.php @@ -0,0 +1,272 @@ + + * + */ + $addition_2_introducers = '(?: + # {{{ Additions relating to who (a natural person) is addressed + \s+ [Cc] \s* \/ \s* [Oo] \s + | ℅ + | \s+ care \s+ of \s+ + # German, Swiss, Austrian + | \s+ (?: p|p.\s*|per\s+ ) (?: A|A.|Adr.|(?<=\s)Adresse ) \s + | \s+ p. \s* A. \s + | \s+ (?: z | z.\s* | zu\s+ ) (?: Hd|Hd.|(?<=\s)Händen|(?<=\s)Haenden|(?<=\s)Handen) \s+ + ## o. V. i. A. = oder Vertreter im Amt + | \s+ (?: o | o.\s* | oder\s+ ) + (?: V | V.\s* | (?<=\s)Vertreter\s+ ) + (?: i | i.\s* | (?<=\s)im\s+ ) + (?: A | A.\s* | (?<=\s)Amt\s+ ) + # }}} + # {{{ Additions which further specify more precisely the location + | \s+ (?: Haus ) \s + | \s+ (?: WG | W\.G\. | WG\. | Wohngemeinschaft ) ($ | \s) + | \s+ (?: [Aa]partment | APT \.? | Apt \.? ) \s + | \s+ (?: [Ff]lat ) \s + | (?: # Numeric-based location specifiers (e.g., "3. Stock"): + \s+ + (?: + [\p{N}]+ # A number, … + (?i: st | nd | rd | th)? # …, optionally followed by an English number suffix + \.? # …, followed by an optional dot, + \s* # …, followed by optional spacing + )? + (?: # Specifying category: + (?i: Stock | Stockwerk) + | App \.? | Apt \.? | (?i: Appartment | Apartment) + ) + # At the end of the string or followed by a space + (?: $ | \s) + ) + | (?: + \s+ (?: + # English language stop words wrt location from source [1] + # (extracted only those which may not be _exclusively_ part of + # street names): + | ANX \.? | (?i: ANNEX) + | APT \.? | (?i: APARTMENT) + | ARC \.? | (?i: ARCADE) + | AVE \.? | (?i: AVENUE) + | BSMT \.? | (?i: BASEMENT) + | BLDG \.? | (?i: BUILDING) + | CP \.? | (?i: CAMP) + | COR \.? | (?i: CORNER) + | CORS \.? | (?i: CORNERS) + | CT \.? | (?i: COURT) + | CTS \.? | (?i: COURTS) + | DEPT \.? | (?i: DEPARTMENT) + | DV \.? | (?i: DIVIDE) + | EST \.? | (?i: ESTATE) + | EXT \.? | (?i: EXTENSION) + | FRY \.? | (?i: FERRY) + | FLD \.? | (?i: FIELD) + | FLDS \.? | (?i: FIELDS) + | FLT \.? | (?i: FLAT) + | FL \.? | (?i: FLOOR) + | FRNT \.? | (?i: FRONT) + | GDNS \.? | (?i: GARDEN) + | GDNS \.? | (?i: GARDENS) + | GTWY \.? | (?i: GATEWAY) + | GRN \.? | (?i: GREEN) + | GRV \.? | (?i: GROVE) + | HNGR \.? | (?i: HANGER) + | HBR \.? | (?i: HARBOR) + | HVN \.? | (?i: HAVEN) + | KY \.? | (?i: KEY) + | LBBY \.? | (?i: LOBBY) + | LCKS \.? | (?i: LOCK) + | LCKS \.? | (?i: LOCKS) + | LDG \.? | (?i: LODGE) + | MNR \.? | (?i: MANOR) + | OFC \.? | (?i: OFFICE) + | PKWY \.? | (?i: PARKWAY) + | PH \.? | (?i: PENTHOUSE) + | PRT \.? | (?i: PORT) + | RADL \.? | (?i: RADIAL) + | RM \.? | (?i: ROOM) + | SPC \.? | (?i: SPACE) + | SQ \.? | (?i: SQUARE) + | STA \.? | (?i: STATION) + | STE \.? | (?i: SUITE) + | TER \.? | (?i: TERRACE) + | TRAK \.? | (?i: TRACK) + | TRL \.? | (?i: TRAIL) + | TRLR \.? | (?i: TRAILER) + | TUNL \.? | (?i: TUNNEL) + | VW \.? | (?i: VIEW) + | VIS \.? | (?i: VISTA) + # Custom custom additions: + | (?i: Story | Storey) + | LVL \.? | (?i: Level) + ) + # May optionally be followed directly by a number+letter + # combination (e.g., "LVL3C"): + (?: [\p{N}]+[\p{L}]* )? + # Occurs at the end of the string or followed by a space: + ($ | \s) + ) + # Heuristic to match location specifiers. These must not be + # conflated with house number extensions as in "12 AB". Hence + # our heuristic is at least 3 letters with the first letter being + # spelled as a capital. E.g., it would match "Haus", "Gebäude" or + # "Arbeitspl.", but not "AAB". + | \s+ ( [\p{Lu}\p{Lt}] [\p{Ll}\p{Lo}]{2,} \.? ) ($ | \s) + # }}} + )'; + $regex = ' + /\A\s* + (?: ######################################################################### + # Option A: [] # + # [] # + ######################################################################### + (?:(?P.*?),\s*)? # Addition to address 1 + (?:No\.\s*)? + (?P + (?P + \pN+(\s+\d+\/\d+)? + ) + (?: + \s*[\-\/\.]?\s* + (?P(?:[a-zA-Z\pN]){1,2}) + \s+ + )? + ) + \s*,?\s* + (?P(?:[a-zA-Z]\s*|\pN\pL{2,}\s\pL)\S[^,#]*?(?(?!\s).*?))? # Addition to address 2 + | ######################################################################### + # Option B: [] # + # [] # + ######################################################################### + (?:(?P.*?),\s*(?=.*[,\/]))? # Addition to address 1 + (?!\s*No\.)(?P[^0-9# ]\s*\S(?:[^,#](?!\b\pN+\s))*?(? + (?P + \pN+ + ) + (?: + # Match house numbers that are (optionally) amended + # by a dash (e.g., 12-13) or slash (e.g., 12\/A): + (?: \s*[\-\/]\s* )* + (?P + (?: + # Do not match "care-of"-like additions as + # house numbers: + (?!' . $addition_2_introducers . ') + \s*[\pL\pN]+ + ) + # Match any further slash- or dash-based house + # number extensions: + (?: + # Do not match "care-of"-like additions as + # house numbers: + (?!' . $addition_2_introducers . ') + # Match any (optionally space-separated) + # additionals parts of house numbers after + # slashes or dashes. + \s* [\-\/] \s* + [\pL\pN]+ + )* + ) + )? + ) # House number + (?: + (?:\s*[-,\/]|(?=\#)|\s)\s*(?!\s*No\.)\s* + (?P(?!\s).*?) + )? + ) + \s*\Z/xu'; + $result = preg_match( $regex, $address, $matches ); + if ( 0 === $result ) { + throw new Exception( sprintf( 'Error occurred while trying to split address \'%s\'', $address ) ); + } elseif ( false === $result ) { + throw new RuntimeException( sprintf( 'Error occurred while trying to split address \'%s\'', $address ) ); + } + if ( ! empty( $matches['A_Street_name'] ) ) { + return array( + 'additionToAddress1' => $matches['A_Addition_to_address_1'], + 'streetName' => $matches['A_Street_name'], + 'houseNumber' => $matches['A_House_number_match'], + 'houseNumberParts' => array( + 'base' => $matches['A_House_number_base'], + 'extension' => isset( $matches['A_House_number_extension'] ) ? trim( $matches['A_House_number_extension'] ) : '', + ), + 'additionToAddress2' => ( isset( $matches['A_Addition_to_address_2'] ) ) ? $matches['A_Addition_to_address_2'] : '', + ); + } else { + return array( + 'additionToAddress1' => $matches['B_Addition_to_address_1'], + 'streetName' => $matches['B_Street_name'], + 'houseNumber' => $matches['B_House_number_match'], + 'houseNumberParts' => array( + 'base' => $matches['B_House_number_base'], + 'extension' => isset( $matches['B_House_number_extension'] ) ? trim( $matches['B_House_number_extension'] ) : '', + ), + 'additionToAddress2' => isset( $matches['B_Addition_to_address_2'] ) ? $matches['B_Addition_to_address_2'] : '', + ); + } + } + + /** + * @param string $house_number A house number string to split in base and extension + * + * @return array + * @throws Exception + */ + public static function split_house_number( $house_number ) { + $regex = + '/ + \A\s* # Trim white spaces at the beginning + (?:[nN][oO][\.:]?\s*)? # Trim sth. like No. + (?:\#\s*)? # Trim # + (? + [\pN]+ # House Number base (only the number) + ) + \s*[\/\-\.]?\s* # Separator (optional) + (? # House number extension (optional) + .*? # Here we allow every character. Everything after the separator is interpreted as extension + ) + \s*\Z # Trim white spaces at the end + /xu'; // Option (u)nicode and e(x)tended syntax + $result = preg_match( $regex, $house_number, $matches ); + if ( 0 === $result ) { + throw new Exception( sprintf( 'Error occurred while trying to house number \'%s\'', $house_number ) ); + } elseif ( false === $result ) { + throw new RuntimeException( sprintf( 'Error occurred while trying to house number \'%s\'', $house_number ) ); + } + + return array( + 'base' => $matches['House_number_base'], + 'extension' => $matches['House_number_extension'], + ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/Admin.php b/packages/woocommerce-germanized-shipments/src/Admin/Admin.php new file mode 100644 index 000000000..11f1c19e8 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/Admin.php @@ -0,0 +1,1193 @@ +get_id() !== $post_id ) { + $the_order = wc_get_order( $post_id ); + } + + if ( $shipment_order = wc_gzd_get_shipment_order( $the_order ) ) { + $shipping_status = $shipment_order->get_shipping_status(); + $status_html = '' . esc_html( wc_gzd_get_shipment_order_shipping_status_name( $shipping_status ) ) . ''; + + if ( in_array( $shipping_status, array( 'shipped', 'partially-shipped' ), true ) && $shipment_order->get_shipments() ) { + echo '' . wp_kses_post( $status_html ) . ''; + } else { + echo wp_kses_post( $status_html ); + } + } + } + } + + public static function register_order_shipping_status_column( $columns ) { + $new_columns = array(); + $added_column = false; + + foreach ( $columns as $column_name => $title ) { + if ( ! $added_column && ( 'shipping_address' === $column_name || 'wc_actions' === $column_name ) ) { + $new_columns['shipping_status'] = _x( 'Shipping Status', 'shipments-order-column-name', 'woocommerce-germanized' ); + $added_column = true; + } + + $new_columns[ $column_name ] = $title; + } + + if ( ! $added_column ) { + $new_columns['shipping_status'] = _x( 'Shipping Status', 'shipments-order-column-name', 'woocommerce-germanized' ); + } + + return $new_columns; + } + + /** + * In case the shipper/return country is set to AF (or DE with missing state) due to a bug in Woo, make sure + * to automatically adjust it to the right value in case the base country option is being saved. + * + * @return void + */ + public static function observe_base_country_setting() { + if ( isset( $_POST['woocommerce_default_country'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $new_base_country = wc_format_country_state_string( get_option( 'woocommerce_default_country' ) ); + + if ( 'AF' !== $new_base_country['country'] ) { + $shipper_country = wc_format_country_state_string( get_option( 'woocommerce_gzd_shipments_shipper_address_country' ) ); + $return_country = wc_format_country_state_string( get_option( 'woocommerce_gzd_shipments_return_address_country' ) ); + + if ( 'AF' === $shipper_country['country'] || ( 'DE' === $new_base_country['country'] && 'DE' === $shipper_country['country'] && empty( $shipper_country['state'] ) && ! empty( $new_base_country['state'] ) ) ) { + update_option( 'woocommerce_gzd_shipments_shipper_address_country', get_option( 'woocommerce_default_country' ) ); + } + + if ( 'AF' === $return_country['country'] || ( 'DE' === $new_base_country['country'] && 'DE' === $return_country['country'] && empty( $return_country['state'] ) && ! empty( $return_country['state'] ) ) ) { + update_option( 'woocommerce_gzd_shipments_return_address_country', get_option( 'woocommerce_default_country' ) ); + } + } + } + } + + public static function product_options() { + global $post, $thepostid, $product_object; + + $_product = wc_get_product( $product_object ); + $shipments_product = wc_gzd_shipments_get_product( $_product ); + + $countries = WC()->countries->get_countries(); + $countries = array_merge( array( '0' => _x( 'Select a country', 'shipments', 'woocommerce-germanized' ) ), $countries ); + + woocommerce_wp_text_input( + array( + 'id' => '_hs_code', + 'label' => _x( 'HS-Code (Customs)', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => true, + 'description' => _x( 'The HS Code is a number assigned to every possible commodity that can be imported or exported from any country.', 'shipments', 'woocommerce-germanized' ), + 'value' => $shipments_product->get_hs_code( 'edit' ), + ) + ); + + woocommerce_wp_select( + array( + 'options' => $countries, + 'id' => '_manufacture_country', + 'label' => _x( 'Country of manufacture (Customs)', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => true, + 'description' => _x( 'The country of manufacture is needed for customs of international shipping.', 'shipments', 'woocommerce-germanized' ), + 'value' => $shipments_product->get_manufacture_country( 'edit' ), + ) + ); + + do_action( 'woocommerce_gzd_shipments_product_options', $shipments_product ); + } + + /** + * @param \WC_Product $product + */ + public static function save_product( $product ) { + $hs_code = isset( $_POST['_hs_code'] ) ? wc_clean( wp_unslash( $_POST['_hs_code'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing + $country = isset( $_POST['_manufacture_country'] ) ? wc_clean( wp_unslash( $_POST['_manufacture_country'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + $shipments_product = wc_gzd_shipments_get_product( $product ); + $shipments_product->set_hs_code( $hs_code ); + $shipments_product->set_manufacture_country( $country ); + + /** + * Remove legacy data upon saving in case it is not transmitted (e.g. DHL standalone plugin). + */ + if ( apply_filters( 'woocommerce_gzd_shipments_remove_legacy_customs_meta', isset( $_POST['_dhl_hs_code'] ) ? false : true, $product ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $product->delete_meta_data( '_dhl_hs_code' ); + $product->delete_meta_data( '_dhl_manufacture_country' ); + } + + do_action( 'woocommerce_gzd_shipments_save_product_options', $shipments_product ); + } + + public static function check_upload_dir() { + $dir = Package::get_upload_dir(); + $path = $dir['basedir']; + $dirname = basename( $path ); + + if ( @is_dir( $dir['basedir'] ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + return; + } + ?> +
+

wp-content/uploads/' . esc_html( $dirname ) . '' ); ?>

+
+ $value ) { + if ( isset( $value['id'] ) && $value['id'] === $id ) { + if ( ! empty( $type ) && $type !== $value['type'] ) { + continue; + } + return $key; + } + } + } + + return false; + } + + protected static function add_settings_after( $settings, $id, $insert = array(), $type = '' ) { + $key = self::get_setting_key_by_id( $settings, $id, $type ); + + if ( is_numeric( $key ) ) { + $key ++; + $settings = array_merge( array_merge( array_slice( $settings, 0, $key, true ), $insert ), array_slice( $settings, $key, count( $settings ) - 1, true ) ); + } else { + $settings += $insert; + } + + return $settings; + } + + public static function register_endpoint_settings( $settings, $current_section ) { + if ( '' === $current_section ) { + $endpoints = array( + array( + 'title' => _x( 'View Shipments', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Endpoint for the "My account → View shipments" page.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_view_shipments_endpoint', + 'type' => 'text', + 'default' => 'view-shipments', + 'desc_tip' => true, + ), + array( + 'title' => _x( 'View shipment', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Endpoint for the "My account → View shipment" page.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_view_shipment_endpoint', + 'type' => 'text', + 'default' => 'view-shipment', + 'desc_tip' => true, + ), + array( + 'title' => _x( 'Add Return Shipment', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Endpoint for the "My account → Add return shipment" page.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_add_return_shipment_endpoint', + 'type' => 'text', + 'default' => 'add-return-shipment', + 'desc_tip' => true, + ), + ); + + $settings = self::add_settings_after( $settings, 'woocommerce_myaccount_downloads_endpoint', $endpoints ); + } + + return $settings; + } + + public static function menu_return_count() { + global $submenu; + + if ( isset( $submenu['woocommerce'] ) ) { + + /** + * Filter to adjust whether to include requested return count in admin menu or not. + * + * @param boolean $show_count Whether to show count or not. + * + * @since 3.1.3 + * @package Vendidero/Germanized/Shipments + */ + if ( apply_filters( 'woocommerce_gzd_shipments_include_requested_return_count_in_menu', true ) && current_user_can( 'edit_others_shop_orders' ) ) { + $return_count = wc_gzd_get_shipment_count( 'requested', 'return' ); + + if ( $return_count ) { + foreach ( $submenu['woocommerce'] as $key => $menu_item ) { + if ( 0 === strpos( $menu_item[0], _x( 'Returns', 'shipments', 'woocommerce-germanized' ) ) ) { + $submenu['woocommerce'][ $key ][0] .= ' ' . number_format_i18n( $return_count ) . ''; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + break; + } + } + } + } + } + } + + public static function get_admin_shipment_item_columns( $shipment ) { + $item_columns = array( + 'name' => array( + 'title' => _x( 'Item', 'shipments', 'woocommerce-germanized' ), + 'size' => 6, + 'order' => 5, + ), + 'quantity' => array( + 'title' => _x( 'Quantity', 'shipments', 'woocommerce-germanized' ), + 'size' => 3, + 'order' => 10, + ), + 'action' => array( + 'title' => _x( 'Actions', 'shipments', 'woocommerce-germanized' ), + 'size' => 3, + 'order' => 15, + ), + ); + + if ( 'return' === $shipment->get_type() ) { + $item_columns['return_reason'] = array( + 'title' => _x( 'Reason', 'shipments', 'woocommerce-germanized' ), + 'size' => 3, + 'order' => 7, + ); + + $item_columns['name']['size'] = 5; + $item_columns['quantity']['size'] = 2; + $item_columns['action']['size'] = 2; + } + + uasort( $item_columns, array( __CLASS__, 'sort_shipment_item_columns' ) ); + + /** + * Filter to adjust shipment item columns shown in admin view. + * + * @param array $item_columns The columns available. + * @param Shipment $shipment The shipment. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipments_meta_box_shipment_item_columns', $item_columns, $shipment ); + } + + protected static function sort_shipment_item_columns( $a, $b ) { + if ( $a['order'] === $b['order'] ) { + return 0; + } + + return ( $a['order'] < $b['order'] ) ? -1 : 1; + } + + public static function save_packaging_list() { + $current_key_list = array(); + $packaging_ids_after_save = array(); + + foreach ( wc_gzd_get_packaging_list() as $pack ) { + $current_key_list[] = $pack->get_id(); + } + + if ( isset( $_POST['packaging'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $packaging_post = wc_clean( wp_unslash( $_POST['packaging'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + $order = 0; + $available_types = array_keys( wc_gzd_get_packaging_types() ); + + foreach ( $packaging_post as $packaging ) { + $packaging = wc_clean( $packaging ); + $packaging_id = isset( $packaging['packaging_id'] ) ? absint( $packaging['packaging_id'] ) : 0; + $packaging_obj = wc_gzd_get_packaging( $packaging_id ); + + if ( $packaging_obj ) { + $packaging_obj->set_props( + array( + 'type' => ! in_array( $packaging['type'], $available_types, true ) ? 'cardboard' : $packaging['type'], + 'weight' => empty( $packaging['weight'] ) ? 0 : $packaging['weight'], + 'description' => empty( $packaging['description'] ) ? '' : $packaging['description'], + 'length' => empty( $packaging['length'] ) ? 0 : $packaging['length'], + 'width' => empty( $packaging['width'] ) ? 0 : $packaging['width'], + 'height' => empty( $packaging['height'] ) ? 0 : $packaging['height'], + 'max_content_weight' => empty( $packaging['max_content_weight'] ) ? 0 : $packaging['max_content_weight'], + 'order' => ++$order, + ) + ); + + if ( empty( $packaging_obj->get_description() ) ) { + if ( $packaging_obj->get_id() > 0 ) { + $packaging_obj->delete( true ); + continue; + } else { + continue; + } + } + + $packaging_obj->save(); + $packaging_ids_after_save[] = $packaging_obj->get_id(); + } + } + } + + $to_delete = array_diff( $current_key_list, $packaging_ids_after_save ); + + if ( ! empty( $to_delete ) ) { + foreach ( $to_delete as $delete_id ) { + if ( $packaging = wc_gzd_get_packaging( $delete_id ) ) { + $packaging->delete( true ); + } + } + } + } + + public static function save_return_reasons( $tab, $current_section ) { + if ( '' !== $current_section ) { + return; + } + + $reasons = array(); + + if ( isset( $_POST['shipment_return_reason'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $reasons_post = wc_clean( wp_unslash( $_POST['shipment_return_reason'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + $order = 0; + + foreach ( $reasons_post as $reason ) { + $code = isset( $reason['code'] ) ? $reason['code'] : ''; + $reason_text = isset( $reason['reason'] ) ? $reason['reason'] : ''; + + if ( empty( $code ) ) { + $code = sanitize_title( $reason_text ); + } + + if ( ! empty( $reason_text ) ) { + $reasons[] = array( + 'order' => ++$order, + 'code' => $code, + 'reason' => $reason_text, + ); + } + } + } + // phpcs:enable + + update_option( 'woocommerce_gzd_shipments_return_reasons', $reasons ); + } + + public static function output_return_reasons_field( $value ) { + ob_start(); + ?> + + + +
+ + + + + + + + + + + + + + '; + } + ?> + + + + + + +
 
+
+ + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
get_title() ); ?> get_status() ) ); ?> + get_date_start()->date_i18n( wc_date_format() ); + + printf( + '', + esc_attr( $report->get_date_start()->date( 'c' ) ), + esc_html( $report->get_date_start()->date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ), + esc_html( $show_date ) + ); + ?> + + get_date_end()->date_i18n( wc_date_format() ); + + printf( + '', + esc_attr( $report->get_date_end()->date( 'c' ) ), + esc_html( $report->get_date_end()->date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ), + esc_html( $show_date ) + ); + ?> + + get_total_weight(), wc_gzd_get_packaging_weight_unit() ) ); ?> + + get_total_count() ); ?> +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ + + + + + + + + + + + + + +
+
+ + + + 'shop_order', + 'bulk_action' => $report_action, + 'changed' => $changed, + 'ids' => join( ',', $ids ), + ); + + if ( Package::is_hpos_enabled() ) { + unset( $redirect_query_args['post_type'] ); + $redirect_query_args['page'] = 'wc-orders'; + } + + $redirect_to = add_query_arg( + $redirect_query_args, + $redirect_to + ); + + return esc_url_raw( $redirect_to ); + } else { + return $redirect_to; + } + } + + public static function define_order_bulk_actions( $actions ) { + $actions['gzd_create_shipments'] = _x( 'Create shipments', 'shipments', 'woocommerce-germanized' ); + + return $actions; + } + + public static function set_screen_option( $new_value, $option, $value ) { + + if ( in_array( $option, array( 'woocommerce_page_wc_gzd_shipments_per_page', 'woocommerce_page_wc_gzd_return_shipments_per_page' ), true ) ) { + return absint( $value ); + } + + return $new_value; + } + + public static function shipments_menu() { + add_submenu_page( 'woocommerce', _x( 'Shipments', 'shipments', 'woocommerce-germanized' ), _x( 'Shipments', 'shipments', 'woocommerce-germanized' ), 'edit_others_shop_orders', 'wc-gzd-shipments', array( __CLASS__, 'shipments_page' ) ); + add_submenu_page( 'woocommerce', _x( 'Returns', 'shipments', 'woocommerce-germanized' ), _x( 'Returns', 'shipments', 'woocommerce-germanized' ), 'edit_others_shop_orders', 'wc-gzd-return-shipments', array( __CLASS__, 'returns_page' ) ); + } + + /** + * @param Shipment $shipment + */ + public static function get_shipment_tracking_html( $shipment ) { + $tracking_html = ''; + + if ( $tracking_id = $shipment->get_tracking_id() ) { + + if ( $tracking_url = $shipment->get_tracking_url() ) { + $tracking_html = '' . $tracking_id . ''; + } else { + $tracking_html = '' . $tracking_id . ''; + } + } + + return $tracking_html; + } + + /** + * @param Table $table + */ + protected static function setup_table( $table ) { + global $wp_list_table; + + $wp_list_table = $table; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $doaction = $wp_list_table->current_action(); + + if ( $doaction ) { + check_admin_referer( 'bulk-shipments' ); + + $pagenum = $wp_list_table->get_pagenum(); + $parent_file = $wp_list_table->get_main_page(); + $sendback = remove_query_arg( array( 'deleted', 'ids', 'changed', 'bulk_action' ), wp_get_referer() ); + + if ( ! $sendback ) { + $sendback = admin_url( $parent_file ); + } + + $sendback = add_query_arg( 'paged', $pagenum, $sendback ); + $shipment_ids = array(); + + if ( isset( $_REQUEST['ids'] ) ) { + $shipment_ids = array_map( 'absint', explode( ',', wp_unslash( $_REQUEST['ids'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + } elseif ( ! empty( $_REQUEST['shipment'] ) ) { + $shipment_ids = array_map( 'absint', wp_unslash( $_REQUEST['shipment'] ) ); + } + + if ( ! empty( $shipment_ids ) ) { + $sendback = $wp_list_table->handle_bulk_actions( $doaction, $shipment_ids, $sendback ); + } + + $sendback = remove_query_arg( array( 'action', 'action2', '_status', 'bulk_edit', 'shipment' ), $sendback ); + + wp_safe_redirect( esc_url_raw( $sendback ) ); + exit(); + + } elseif ( ! empty( $_REQUEST['_wp_http_referer'] ) ) { + wp_safe_redirect( esc_url_raw( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated + exit; + } + + $wp_list_table->set_bulk_notice(); + $wp_list_table->prepare_items(); + + add_screen_option( 'per_page' ); + } + + public static function setup_shipments_table() { + $table = new Table(); + + self::setup_table( $table ); + } + + public static function setup_returns_table() { + $table = new ReturnTable( array( 'type' => 'return' ) ); + + self::setup_table( $table ); + } + + public static function shipments_page() { + global $wp_list_table; + + ?> +
+

+
+ + output_notice(); + $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'updated', 'changed', 'deleted', 'trashed', 'untrashed' ), ( isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : admin_url( 'admin.php?page=wc-gzd-shipments' ) ) ); + ?> + + views(); ?> + +
+ + search_box( _x( 'Search shipments', 'shipments', 'woocommerce-germanized' ), 'shipment' ); ?> + + + + + + + + display(); ?> +
+ +
+
+
+ +
+

+
+ + output_notice(); + $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'updated', 'changed', 'deleted', 'trashed', 'untrashed' ), ( isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : admin_url( 'admin.php?page=wc-gzd-shipments' ) ) ); + ?> + + views(); ?> + +
+ + search_box( _x( 'Search returns', 'shipments', 'woocommerce-germanized' ), 'shipment' ); ?> + + + + + + + + display(); ?> +
+ +
+
+
+ id : ''; + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + + // Register admin styles. + wp_register_style( 'woocommerce_gzd_shipments_admin', Package::get_assets_url() . '/css/admin' . $suffix . '.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_shipments_admin' ); + } + + if ( 'woocommerce_page_wc-settings' === $screen_id && isset( $_GET['tab'] ) && in_array( $_GET['tab'], array( 'germanized-shipments', 'germanized-shipping_provider' ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + wp_enqueue_style( 'woocommerce_gzd_shipments_admin' ); + } + + // Shipping zone methods + if ( 'woocommerce_page_wc-settings' === $screen_id && isset( $_GET['tab'] ) && 'shipping' === $_GET['tab'] && ( isset( $_GET['zone_id'] ) || isset( $_GET['instance_id'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + wp_enqueue_style( 'woocommerce_gzd_shipments_admin' ); + } + } + + public static function admin_scripts() { + global $post, $theorder; + + $screen = get_current_screen(); + $screen_id = $screen ? $screen->id : ''; + $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; + $post_id = isset( $post->ID ) ? $post->ID : ''; + $order_or_post_object = $post; + + if ( ( $theorder instanceof \WC_Order ) && self::is_order_meta_box_screen( $screen_id ) ) { + $order_or_post_object = $theorder; + } + + wp_register_script( 'wc-gzd-admin-shipment-label-backbone', Package::get_assets_url() . '/js/admin-shipment-label-backbone' . $suffix . '.js', array( 'jquery', 'woocommerce_admin', 'wc-backbone-modal' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + wp_register_script( 'wc-gzd-admin-shipment', Package::get_assets_url() . '/js/admin-shipment' . $suffix . '.js', array( 'wc-gzd-admin-shipment-label-backbone' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + wp_register_script( 'wc-gzd-admin-shipments', Package::get_assets_url() . '/js/admin-shipments' . $suffix . '.js', array( 'wc-admin-order-meta-boxes', 'wc-gzd-admin-shipment' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + wp_register_script( 'wc-gzd-admin-shipments-table', Package::get_assets_url() . '/js/admin-shipments-table' . $suffix . '.js', array( 'woocommerce_admin', 'wc-gzd-admin-shipment-label-backbone' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + wp_register_script( 'wc-gzd-admin-shipping-providers', Package::get_assets_url() . '/js/admin-shipping-providers' . $suffix . '.js', array( 'jquery', 'jquery-ui-sortable' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + wp_register_script( 'wc-gzd-admin-shipping-provider-method', Package::get_assets_url() . '/js/admin-shipping-provider-method' . $suffix . '.js', array( 'jquery' ), Package::get_version() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter + + // Orders. + if ( self::is_order_meta_box_screen( $screen_id ) ) { + wp_enqueue_script( 'wc-gzd-admin-shipments' ); + wp_enqueue_script( 'wc-gzd-admin-shipment' ); + + $order_order_post_id = $post_id; + + if ( self::is_order_meta_box_screen( $screen_id ) && isset( $order_or_post_object ) && is_callable( array( '\Automattic\WooCommerce\Utilities\OrderUtil', 'get_post_or_order_id' ) ) ) { + $order_order_post_id = \Automattic\WooCommerce\Utilities\OrderUtil::get_post_or_order_id( $order_or_post_object ); + } + + wp_localize_script( + 'wc-gzd-admin-shipments', + 'wc_gzd_admin_shipments_params', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'edit_shipments_nonce' => wp_create_nonce( 'edit-shipments' ), + 'order_id' => $order_order_post_id, + 'shipment_locked_excluded_fields' => array( 'status' ), + 'i18n_remove_shipment_notice' => _x( 'Do you really want to delete the shipment?', 'shipments', 'woocommerce-germanized' ), + 'remove_label_nonce' => wp_create_nonce( 'remove-shipment-label' ), + 'edit_label_nonce' => wp_create_nonce( 'edit-shipment-label' ), + 'send_return_notification_nonce' => wp_create_nonce( 'send-return-shipment-notification' ), + 'refresh_packaging_nonce' => wp_create_nonce( 'refresh-shipment-packaging' ), + 'confirm_return_request_nonce' => wp_create_nonce( 'confirm-return-request' ), + 'i18n_remove_label_notice' => _x( 'Do you really want to delete the label?', 'shipments', 'woocommerce-germanized' ), + 'i18n_create_label_enabled' => _x( 'Create new label', 'shipments', 'woocommerce-germanized' ), + 'i18n_create_label_disabled' => _x( 'Please save the shipment before creating a new label', 'shipments', 'woocommerce-germanized' ), + ) + ); + } + + // Table + if ( 'woocommerce_page_wc-gzd-shipments' === $screen_id || 'woocommerce_page_wc-gzd-return-shipments' === $screen_id ) { + wp_enqueue_script( 'wc-gzd-admin-shipments-table' ); + + $bulk_actions = array(); + + foreach ( self::get_bulk_action_handlers() as $handler ) { + $bulk_actions[ sanitize_key( $handler->get_action() ) ] = array( + 'title' => $handler->get_title(), + 'nonce' => wp_create_nonce( $handler->get_nonce_name() ), + ); + } + + wp_localize_script( + 'wc-gzd-admin-shipments-table', + 'wc_gzd_admin_shipments_table_params', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'search_orders_nonce' => wp_create_nonce( 'search-orders' ), + 'search_shipping_provider_nonce' => wp_create_nonce( 'search-shipping-provider' ), + 'bulk_actions' => $bulk_actions, + ) + ); + } + + wp_localize_script( + 'wc-gzd-admin-shipment-label-backbone', + 'wc_gzd_admin_shipment_label_backbone_params', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'i18n_modal_close' => _x( 'Close', 'shipments-close-modal', 'woocommerce-germanized' ), + 'create_label_form_nonce' => wp_create_nonce( 'create-shipment-label-form' ), + 'create_label_nonce' => wp_create_nonce( 'create-shipment-label' ), + ) + ); + + // Shipping provider settings + if ( 'woocommerce_page_wc-settings' === $screen_id && isset( $_GET['tab'] ) && 'germanized-shipping_provider' === $_GET['tab'] && empty( $_GET['provider'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + wp_enqueue_script( 'wc-gzd-admin-shipping-providers' ); + + wp_localize_script( + 'wc-gzd-admin-shipping-providers', + 'wc_gzd_admin_shipping_providers_params', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'edit_shipping_providers_nonce' => wp_create_nonce( 'edit-shipping-providers' ), + 'remove_shipping_provider_nonce' => wp_create_nonce( 'remove-shipping-provider' ), + 'sort_shipping_provider_nonce' => wp_create_nonce( 'sort-shipping-provider' ), + 'i18n_remove_shipping_provider_notice' => _x( 'Do you really want to delete the shipping provider? Some of your existing shipments might be linked to that provider and might need adjustments.', 'shipments', 'woocommerce-germanized' ), + ) + ); + } + + // Shipping provider method + if ( 'woocommerce_page_wc-settings' === $screen_id && isset( $_GET['tab'] ) && 'shipping' === $_GET['tab'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $excluded_sections = array( 'classes' ) + Package::get_excluded_methods(); + + /** + * Older third-party shipping methods may not support instance-settings and will have their settings + * output in a separate section under Settings > Shipping. + */ + if ( ( isset( $_GET['zone_id'] ) || isset( $_GET['instance_id'] ) ) || ( isset( $_GET['section'] ) && ! in_array( $_GET['section'], $excluded_sections, true ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + wp_enqueue_script( 'wc-gzd-admin-shipping-provider-method' ); + $providers = array_filter( array_keys( wc_gzd_get_shipping_provider_select() ) ); + + wp_localize_script( + 'wc-gzd-admin-shipping-provider-method', + 'wc_gzd_admin_shipping_provider_method_params', + array( + 'shipping_providers' => $providers, + ) + ); + } + } + } + + /** + * @return BulkActionHandler[] $handler + */ + public static function get_bulk_action_handlers() { + if ( is_null( self::$bulk_handlers ) ) { + self::$bulk_handlers = array(); + + /** + * Filter to register new BulkActionHandler for certain Shipment bulk actions. + * + * @param array $handlers Array containing key => classname. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $handlers = apply_filters( + 'woocommerce_gzd_shipments_table_bulk_action_handlers', + array( + 'labels' => '\Vendidero\Germanized\Shipments\Admin\BulkLabel', + ) + ); + + foreach ( $handlers as $key => $handler ) { + self::$bulk_handlers[ $key ] = new $handler(); + } + } + + return self::$bulk_handlers; + } + + public static function get_bulk_action_handler( $action ) { + $handlers = self::get_bulk_action_handlers(); + + return array_key_exists( $action, $handlers ) ? $handlers[ $action ] : false; + } + + /** + * Helper function to determine whether the current screen is an order edit screen. + * + * @param string $screen_id Screen ID. + * + * @return bool Whether the current screen is an order edit screen. + */ + protected static function is_order_meta_box_screen( $screen_id ) { + return in_array( str_replace( 'edit-', '', $screen_id ), self::get_order_screen_ids(), true ); + } + + public static function get_order_screen_id() { + return function_exists( 'wc_get_page_screen_id' ) ? wc_get_page_screen_id( 'shop-order' ) : 'shop_order'; + } + + protected static function get_order_screen_ids() { + $screen_ids = array(); + + foreach ( wc_get_order_types() as $type ) { + $screen_ids[] = $type; + $screen_ids[] = 'edit-' . $type; + } + + $screen_ids[] = self::get_order_screen_id(); + + return array_filter( $screen_ids ); + } + + public static function get_screen_ids() { + $screen_ids = array( + 'woocommerce_page_wc-gzd-shipments', + 'woocommerce_page_wc-gzd-return-shipments', + ); + + return array_merge( $screen_ids, self::get_order_screen_ids() ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/BulkActionHandler.php b/packages/woocommerce-germanized-shipments/src/Admin/BulkActionHandler.php new file mode 100644 index 000000000..805ed7d7b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/BulkActionHandler.php @@ -0,0 +1,163 @@ +notices = array_filter( (array) get_user_meta( get_current_user_id(), $this->get_notice_option_name(), true ) ); + } + + protected function get_notice_option_name() { + $action = sanitize_key( $this->get_action() ); + + return "woocommerce_gzd_shipments_{$action}_bulk_notices"; + } + + abstract public function get_title(); + + public function get_nonce_name() { + $action = sanitize_key( $this->get_action() ); + + return "woocommerce_gzd_shipments_{$action}"; + } + + public function get_shipment_type() { + return $this->type; + } + + public function set_shipment_type( $type ) { + $this->type = $type; + } + + public function get_success_redirect_url() { + $page = 'wc-gzd-shipments'; + + if ( 'simple' !== $this->get_shipment_type() ) { + $page = 'wc-gzd-' . $this->get_shipment_type() . '-shipments'; + } + + return admin_url( 'admin.php?page=' . $page . '&bulk_action_handling=finished¤t_bulk_action=' . sanitize_key( $this->get_action() ) ); + } + + public function get_step() { + return $this->step; + } + + public function set_step( $step ) { + $this->step = $step; + } + + public function get_notices( $type = 'error' ) { + $notices = array_key_exists( $type, $this->notices ) ? $this->notices[ $type ] : array(); + + return $notices; + } + + public function get_success_message() { + return _x( 'Successfully processed shipments.', 'shipments', 'woocommerce-germanized' ); + } + + public function admin_handled() { + + } + + public function admin_after_error() { + + } + + public function add_notice( $notice, $type = 'error' ) { + if ( ! isset( $this->notices[ $type ] ) ) { + $this->notices[ $type ] = array(); + } + + $this->notices[ $type ][] = $notice; + } + + public function update_notices() { + update_user_meta( get_current_user_id(), $this->get_notice_option_name(), $this->notices ); + } + + public function reset( $is_new = false ) { + delete_user_meta( get_current_user_id(), $this->get_notice_option_name() ); + } + + abstract public function get_action(); + + public function get_max_step() { + return (int) ceil( count( $this->get_ids() ) / $this->get_limit() ); + } + + abstract public function get_limit(); + + public function get_total() { + return count( $this->get_ids() ); + } + + abstract public function handle(); + + public function set_ids( $ids ) { + $this->ids = $ids; + } + + public function get_ids() { + return $this->ids; + } + + public function get_current_ids() { + return array_slice( $this->get_ids(), ( $this->get_step() - 1 ) * $this->get_limit(), $this->get_limit() ); + } + + /** + * Get count of records exported. + * + * @since 3.0.6 + * @return int + */ + public function get_total_processed() { + return ( $this->get_step() * $this->get_limit() ); + } + + /** + * Get total % complete. + * + * @since 3.0.6 + * @return int + */ + public function get_percent_complete() { + return floor( ( $this->get_total_processed() / $this->get_total() ) * 100 ); + } + + public function is_last_step() { + $current_step = $this->get_step(); + $max_step = $this->get_max_step(); + + if ( $max_step === $current_step ) { + return true; + } + + return false; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/BulkLabel.php b/packages/woocommerce-germanized-shipments/src/Admin/BulkLabel.php new file mode 100644 index 000000000..2fada1f79 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/BulkLabel.php @@ -0,0 +1,215 @@ +get_file_option_name(), true ); + + if ( $file ) { + $uploads = Package::get_upload_dir(); + $path = trailingslashit( $uploads['basedir'] ) . $file; + + return $path; + } + + return ''; + } + + protected function update_file( $path ) { + update_user_meta( get_current_user_id(), $this->get_file_option_name(), $path ); + } + + protected function get_file_option_name() { + $action = sanitize_key( $this->get_action() ); + + return "woocommerce_gzd_shipments_{$action}_bulk_path"; + } + + public function get_filename() { + if ( $file = $this->get_file() ) { + return basename( $file ); + } + + return ''; + } + + public function reset( $is_new = false ) { + parent::reset( $is_new ); + + if ( $is_new ) { + delete_user_meta( get_current_user_id(), $this->get_file_option_name() ); + delete_user_meta( get_current_user_id(), $this->get_files_option_name() ); + } + } + + protected function get_download_button() { + $download_button = ''; + + if ( ( $path = $this->get_file() ) && file_exists( $path ) ) { + + $download_url = add_query_arg( + array( + 'action' => 'wc-gzd-download-export-shipment-label', + 'force' => 'no', + ), + wp_nonce_url( admin_url(), 'download-export-shipment-label' ) + ); + + $download_button = '' . esc_html_x( 'Download labels', 'shipments', 'woocommerce-germanized' ) . ''; + } + + return $download_button; + } + + public function get_success_message() { + $download_button = $this->get_download_button(); + + if ( empty( $download_button ) ) { + return sprintf( _x( 'The chosen shipments were not suitable for automatic label creation. Please check the shipping provider option of the corresponding shipments.', 'shipments', 'woocommerce-germanized' ), $download_button ); + } else { + return sprintf( _x( 'Successfully generated labels. %s', 'shipments', 'woocommerce-germanized' ), $download_button ); + } + } + + public function admin_after_error() { + $download_button = $this->get_download_button(); + + if ( ! empty( $download_button ) ) { + echo '

' . sprintf( esc_html_x( 'Labels partially generated. %s', 'shipments', 'woocommerce-germanized' ), $download_button ) . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } + + protected function get_files_option_name() { + $action = sanitize_key( $this->get_action() ); + + return "woocommerce_gzd_shipments_{$action}_bulk_files"; + } + + protected function get_files() { + $files = get_user_meta( get_current_user_id(), $this->get_files_option_name(), true ); + + if ( empty( $files ) || ! is_array( $files ) ) { + $files = array(); + } + + return $files; + } + + protected function add_file( $path ) { + $files = $this->get_files(); + + if ( ! in_array( $path, $files, true ) ) { + $files[] = $path; + update_user_meta( get_current_user_id(), $this->get_files_option_name(), $files ); + } + } + + public function handle() { + $current = $this->get_current_ids(); + + if ( ! empty( $current ) ) { + foreach ( $current as $shipment_id ) { + $label = false; + + if ( $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + if ( $shipment->supports_label() ) { + if ( $shipment->needs_label() ) { + $result = $shipment->create_label(); + + if ( is_wp_error( $result ) ) { + $result = wc_gzd_get_shipment_error( $result ); + } + + if ( is_wp_error( $result ) ) { + foreach ( $result->get_error_messages_by_type() as $type => $messages ) { + foreach ( $messages as $message ) { + if ( 'soft' === $type ) { + $this->add_notice( sprintf( _x( 'Notice while creating label for %1$s: %2$s', 'shipments', 'woocommerce-germanized' ), '' . sprintf( _x( 'shipment #%d', 'shipments', 'woocommerce-germanized' ), $shipment_id ) . '', $message ), 'info' ); + } else { + $this->add_notice( sprintf( _x( 'Error while creating label for %1$s: %2$s', 'shipments', 'woocommerce-germanized' ), '' . sprintf( _x( 'shipment #%d', 'shipments', 'woocommerce-germanized' ), $shipment_id ) . '', $message ), 'error' ); + } + } + } + } + + if ( $shipment->has_label() ) { + $label = $shipment->get_label(); + } + } else { + $label = $shipment->get_label(); + } + } + } + + if ( $label ) { + $this->add_file( $label->get_file() ); + } + } + } + + if ( $this->is_last_step() ) { + try { + $files = $this->get_files(); + $pdf = new PDFMerger(); + + if ( ! empty( $files ) ) { + foreach ( $files as $file ) { + if ( ! file_exists( $file ) ) { + continue; + } + + $pdf->add( $file ); + } + + /** + * Filter to adjust the default filename chosen for bulk exporting shipment labels. + * + * @param string $filename The filename. + * @param BulkLabel $this The `BulkLabel instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/shipments + */ + $filename = apply_filters( 'woocommerce_gzd_shipment_labels_bulk_filename', 'export.pdf', $this ); + $file = $pdf->output( $filename, 'S' ); + + if ( $path = wc_gzd_shipments_upload_data( $filename, $file ) ) { + $this->update_file( $path ); + } + } + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } + } + + $this->update_notices(); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/MetaBox.php b/packages/woocommerce-germanized-shipments/src/Admin/MetaBox.php new file mode 100644 index 000000000..d34957581 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/MetaBox.php @@ -0,0 +1,178 @@ +get_shipments() as $shipment ) { + $id = $shipment->get_id(); + $props = array(); + + // Update items + self::refresh_shipment_items( $order, $shipment ); + + // Do only update props if they exist + if ( isset( $_POST['shipment_weight'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['weight'] = wc_clean( wp_unslash( $_POST['shipment_weight'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_length'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['length'] = wc_clean( wp_unslash( $_POST['shipment_length'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_width'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['width'] = wc_clean( wp_unslash( $_POST['shipment_width'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_height'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['height'] = wc_clean( wp_unslash( $_POST['shipment_height'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_shipping_method'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['shipping_method'] = wc_clean( wp_unslash( $_POST['shipment_shipping_method'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_tracking_id'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['tracking_id'] = wc_clean( wp_unslash( $_POST['shipment_tracking_id'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_packaging_id'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['packaging_id'] = wc_clean( wp_unslash( $_POST['shipment_packaging_id'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_shipping_provider'][ $id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $provider = wc_clean( wp_unslash( $_POST['shipment_shipping_provider'][ $id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + $providers = wc_gzd_get_shipping_providers(); + + if ( empty( $provider ) || array_key_exists( $provider, $providers ) ) { + $props['shipping_provider'] = $provider; + } + } + + $new_status = isset( $_POST['shipment_status'][ $id ] ) ? str_replace( 'gzd-', '', wc_clean( wp_unslash( $_POST['shipment_status'][ $id ] ) ) ) : 'draft'; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + // Sync the shipment - make sure gets refresh on status switch (e.g. from shipped to processing) + if ( $shipment->is_editable() || in_array( $new_status, wc_gzd_get_shipment_editable_statuses(), true ) ) { + $shipment->sync( $props ); + } + } + } + + /** + * @param Order $order + * @param bool $shipment + */ + public static function refresh_shipment_items( &$order, &$shipment = false ) { + $shipments = $shipment ? array( $shipment ) : $order->get_shipments(); + + foreach ( $shipments as $shipment ) { + $id = $shipment->get_id(); + + if ( ! $shipment->is_editable() ) { + continue; + } + + // Update items + foreach ( $shipment->get_items() as $item ) { + $item_id = $item->get_id(); + $props = array(); + + // Set quantity to 1 by default + if ( $shipment->is_editable() ) { + $props['quantity'] = 1; + } + + if ( isset( $_POST['shipment_item'][ $id ]['quantity'][ $item_id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['quantity'] = absint( wp_unslash( $_POST['shipment_item'][ $id ]['quantity'][ $item_id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( isset( $_POST['shipment_item'][ $id ]['return_reason_code'][ $item_id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $props['return_reason_code'] = wc_clean( wp_unslash( $_POST['shipment_item'][ $id ]['return_reason_code'][ $item_id ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + $item->sync( $props ); + } + } + } + + /** + * @param Order $order + */ + public static function refresh_status( &$order ) { + foreach ( $order->get_shipments() as $shipment ) { + $id = $shipment->get_id(); + $status = isset( $_POST['shipment_status'][ $id ] ) ? wc_clean( wp_unslash( $_POST['shipment_status'][ $id ] ) ) : 'draft'; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + if ( ! wc_gzd_is_shipment_status( $status ) ) { + $status = 'draft'; + } + + $shipment->set_status( $status ); + } + } + + protected static function init_order_object( $post ) { + if ( is_callable( array( '\Automattic\WooCommerce\Utilities\OrderUtil', 'init_theorder_object' ) ) ) { + \Automattic\WooCommerce\Utilities\OrderUtil::init_theorder_object( $post ); + } else { + global $post, $thepostid, $theorder; + + if ( ! is_int( $thepostid ) ) { + $thepostid = $post->ID; + } + + if ( ! is_object( $theorder ) ) { + $theorder = wc_get_order( $thepostid ); + } + } + } + + /** + * Output the metabox. + * + * @param \WP_Post $post + */ + public static function output( $post ) { + global $theorder; + + self::init_order_object( $post ); + + $order = $theorder; + $order_shipment = wc_gzd_get_shipment_order( $order ); + $active_shipment = isset( $_GET['shipment_id'] ) ? absint( $_GET['shipment_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + include Package::get_path() . '/includes/admin/views/html-order-shipments.php'; + } + + /** + * Save meta box data. + * + * @param int $post_id + */ + public static function save( $order_id ) { + // Get order object. + $order_shipment = wc_gzd_get_shipment_order( $order_id ); + + self::refresh_shipments( $order_shipment ); + + $order_shipment->validate_shipments( array( 'save' => false ) ); + + // Refresh status just before saving + self::refresh_status( $order_shipment ); + + $order_shipment->save(); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/ProviderSettings.php b/packages/woocommerce-germanized-shipments/src/Admin/ProviderSettings.php new file mode 100644 index 000000000..56218e2a0 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/ProviderSettings.php @@ -0,0 +1,260 @@ +get_shipping_providers(); + + if ( ! empty( $provider_name ) && 'new' !== $provider_name ) { + $provider = $helper->get_shipping_provider( $provider_name ); + } else { + $provider = new Simple(); + } + } + + return $provider; + } + + public static function get_help_link() { + if ( $provider = self::get_current_provider() ) { + return $provider->get_help_link(); + } else { + return 'https://vendidero.de/dokument/versanddienstleister-verwalten'; + } + } + + public static function get_next_pointers_link( $provider_name = false ) { + $providers = wc_gzd_get_shipping_providers(); + $next_url = admin_url( 'admin.php?page=wc-settings&tab=germanized-emails&tutorial=yes' ); + $provider_indexes = array(); + $provider_counts = array(); + $count = 0; + + foreach ( $providers as $provider_key => $provider ) { + if ( is_a( $provider, '\Vendidero\Germanized\Shipments\ShippingProvider\Auto' ) && ! empty( $provider->get_settings_help_pointers() ) ) { + $provider_indexes[ $provider_key ] = $count; + $provider_counts[ $count ] = $provider_key; + $count++; + } + } + + $next_index = isset( $provider_indexes[ $provider_name ] ) ? $provider_indexes[ $provider_name ] + 1 : -1; + + // By default use the first provider + if ( ! $provider_name ) { + $next_index = 0; + } + + if ( isset( $provider_counts[ $next_index ] ) ) { + $next_provider = $providers[ $provider_counts[ $next_index ] ]; + $next_url = add_query_arg( array( 'tutorial' => 'yes' ), $next_provider->get_edit_link() ); + } + + return $next_url; + } + + public static function get_pointers( $section ) { + $pointers = array(); + + if ( $provider = self::get_current_provider() ) { + if ( is_a( $provider, '\Vendidero\Germanized\Shipments\ShippingProvider\Auto' ) ) { + $pointers = $provider->get_settings_help_pointers( $section ); + } + } else { + $pointers = array( + 'pointers' => array( + 'provider' => array( + 'target' => '.wc-gzd-setting-tab-rows tr:first-child .wc-gzd-shipping-provider-title a.wc-gzd-shipping-provider-edit-link', + 'next' => 'activate', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '

' . esc_html_x( 'Shipping Provider', 'shipments', 'woocommerce-germanized' ) . '

' . esc_html_x( 'You may find all the available shipping providers as a list here. Click on the link to edit the provider-specific settings.', 'shipments', 'woocommerce-germanized' ) . '

', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'activate' => array( + 'target' => '.wc-gzd-setting-tab-rows tr:first-child .wc-gzd-shipping-provider-activated .woocommerce-gzd-input-toggle-trigger', + 'next' => 'new', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '

' . esc_html_x( 'Activate', 'shipments', 'woocommerce-germanized' ) . '

' . esc_html_x( 'Activate or deactivate a shipping provider by toggling this button.', 'shipments', 'woocommerce-germanized' ) . '

', + 'position' => array( + 'edge' => 'right', + 'align' => 'left', + ), + ), + ), + 'new' => array( + 'target' => 'ul.wc-gzd-settings-breadcrumb .breadcrumb-item-active a.page-title-action:first', + 'next' => '', + 'next_url' => self::get_next_pointers_link(), + 'next_trigger' => array(), + 'options' => array( + 'content' => '

' . esc_html_x( 'Add new', 'shipments', 'woocommerce-germanized' ) . '

' . esc_html_x( 'You may want to manually add a new shipping provider in case an automatic integration does not exist.', 'shipments', 'woocommerce-germanized' ) . '

', + 'position' => array( + 'edge' => 'top', + 'align' => 'top', + ), + ), + ), + ), + ); + } + + return $pointers; + } + + public static function get_description() { + if ( $provider = self::get_current_provider() ) { + return $provider->get_description( 'edit' ); + } + + return ''; + } + + public static function get_breadcrumb( $current_section = '' ) { + $provider = self::get_current_provider(); + + $breadcrumb[] = array( + 'class' => 'tab', + 'href' => $provider ? admin_url( 'admin.php?page=wc-settings&tab=germanized-shipping_provider' ) : '', + 'title' => ! $provider ? self::get_breadcrumb_label( _x( 'Shipping Provider', 'shipments', 'woocommerce-germanized' ) ) : _x( 'Shipping Provider', 'shipments', 'woocommerce-germanized' ), + ); + + if ( $provider = self::get_current_provider() ) { + $breadcrumb[] = array( + 'class' => 'section', + 'href' => ! empty( $current_section ) ? $provider->get_edit_link() : '', + 'title' => ( $provider->get_id() <= 0 && '' === $provider->get_title() ) ? self::get_breadcrumb_label( _x( 'New', 'shipments-shipping-provider', 'woocommerce-germanized' ), $current_section ) : self::get_breadcrumb_label( $provider->get_title(), $current_section ), + ); + } + + if ( ! empty( $current_section ) ) { + $breadcrumb[] = array( + 'class' => 'section', + 'href' => '', + 'title' => self::get_section_title( $current_section ), + ); + } + + return $breadcrumb; + } + + protected static function get_section_title( $section = '' ) { + $sections = self::get_sections(); + $section_label = isset( $sections[ $section ] ) ? $sections[ $section ] : ''; + + return $section_label; + } + + protected static function get_breadcrumb_label( $label, $current_section = '' ) { + $help_link = self::get_help_link(); + $provider = self::get_current_provider(); + + if ( $provider && empty( $current_section ) ) { + if ( ! empty( $help_link ) ) { + $label = $label . '' . _x( 'Learn more', 'shipments', 'woocommerce-germanized' ) . ''; + } + + if ( ! empty( $provider->get_signup_link() ) ) { + $label = $label . '' . _x( 'Not yet a customer?', 'shipments', 'woocommerce-germanized' ) . ''; + } + } elseif ( ! $provider ) { + $label = $label . '' . _x( 'Add provider', 'shipments', 'woocommerce-germanized' ) . ''; + + if ( ! empty( $help_link ) ) { + $label = $label . '' . _x( 'Learn more', 'shipments', 'woocommerce-germanized' ) . ''; + } + } + + return $label; + } + + public static function save( $section = '' ) { + if ( $provider = self::get_current_provider() ) { + $is_new = $provider->get_id() <= 0 ? true : false; + + $provider->update_settings( $section, null, false ); + + if ( $is_new ) { + if ( empty( $provider->get_tracking_desc_placeholder( 'edit' ) ) ) { + $provider->set_tracking_desc_placeholder( $provider->get_default_tracking_desc_placeholder() ); + } + + if ( empty( $provider->get_tracking_url_placeholder( 'edit' ) ) ) { + $provider->set_tracking_url_placeholder( $provider->get_default_tracking_url_placeholder() ); + } + } + + if ( isset( $_GET['provider'] ) && 'new' === $_GET['provider'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + add_filter( 'woocommerce_gzd_shipments_shipping_provider_is_manual_creation_request', '__return_true', 15 ); + } + + $provider->save(); + + if ( isset( $_GET['provider'] ) && 'new' === $_GET['provider'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + remove_filter( 'woocommerce_gzd_shipments_shipping_provider_is_manual_creation_request', '__return_true', 15 ); + } + + if ( $is_new ) { + $url = admin_url( 'admin.php?page=wc-settings&tab=germanized-shipping_provider&provider=' . $provider->get_name() ); + wp_safe_redirect( $url ); + } + } + } + + public static function get_settings( $current_section = '' ) { + if ( $provider = self::get_current_provider() ) { + return $provider->get_settings( $current_section ); + } else { + return array(); + } + } + + public static function output_providers() { + global $hide_save_button; + + $hide_save_button = true; + self::provider_screen(); + } + + protected static function provider_screen() { + $helper = Helper::instance(); + $providers = $helper->get_shipping_providers(); + $providers = apply_filters( 'woocommerce_gzd_shipment_admin_provider_list', $providers ); + + include_once Package::get_path() . '/includes/admin/views/html-settings-provider-list.php'; + } + + public static function get_sections() { + if ( $provider = self::get_current_provider() ) { + return $provider->get_setting_sections(); + } else { + return array(); + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/ReturnTable.php b/packages/woocommerce-germanized-shipments/src/Admin/ReturnTable.php new file mode 100644 index 000000000..be9ffee3b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/ReturnTable.php @@ -0,0 +1,101 @@ +'; + $columns['title'] = _x( 'Title', 'shipments', 'woocommerce-germanized' ); + $columns['date'] = _x( 'Date', 'shipments', 'woocommerce-germanized' ); + $columns['status'] = _x( 'Status', 'shipments', 'woocommerce-germanized' ); + $columns['items'] = _x( 'Items', 'shipments', 'woocommerce-germanized' ); + $columns['sender'] = _x( 'Sender', 'shipments', 'woocommerce-germanized' ); + $columns['weight'] = _x( 'Weight', 'shipments', 'woocommerce-germanized' ); + $columns['dimensions'] = _x( 'Dimensions', 'shipments', 'woocommerce-germanized' ); + $columns['order'] = _x( 'Order', 'shipments', 'woocommerce-germanized' ); + $columns['actions'] = _x( 'Actions', 'shipments', 'woocommerce-germanized' ); + + return $columns; + } + + /** + * @param ReturnShipment $shipment + * @param $actions + * + * @return mixed + */ + protected function get_custom_actions( $shipment, $actions ) { + + if ( isset( $actions['shipped'] ) ) { + unset( $actions['shipped'] ); + } + + if ( ! $shipment->has_status( 'delivered' ) && ! $shipment->has_status( 'requested' ) ) { + $actions['received'] = array( + 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_gzd_update_shipment_status&status=delivered&shipment_id=' . $shipment->get_id() ), 'update-shipment-status' ), + 'name' => _x( 'Delivered', 'shipments', 'woocommerce-germanized' ), + 'action' => 'delivered', + ); + } + + if ( $shipment->has_status( 'processing' ) ) { + $actions['email_notification'] = array( + 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_gzd_send_return_shipment_notification_email&shipment_id=' . $shipment->get_id() ), 'send-return-shipment-notification' ), + 'name' => _x( 'Send notification to customer', 'shipments', 'woocommerce-germanized' ), + 'action' => 'send-return-notification email', + ); + } + + if ( $shipment->is_customer_requested() && $shipment->has_status( 'requested' ) ) { + $actions['confirm'] = array( + 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_gzd_confirm_return_request&shipment_id=' . $shipment->get_id() ), 'confirm-return-request' ), + 'name' => _x( 'Confirm return request', 'shipments', 'woocommerce-germanized' ), + 'action' => 'confirm', + ); + } + + return $actions; + } + + public function get_main_page() { + return 'admin.php?page=wc-gzd-return-shipments'; + } + + protected function get_custom_bulk_actions( $actions ) { + $actions['confirm_requests'] = _x( 'Confirm open return requests', 'shipments', 'woocommerce-germanized' ); + + return $actions; + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param ReturnShipment $shipment The current shipment object. + */ + public function column_sender( $shipment ) { + $address = $shipment->get_formatted_sender_address(); + + if ( $address ) { + echo '' . esc_html( preg_replace( '##i', ', ', $address ) ) . ''; + } else { + echo '–'; + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/Settings.php b/packages/woocommerce-germanized-shipments/src/Admin/Settings.php new file mode 100644 index 000000000..c6cfb8b56 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/Settings.php @@ -0,0 +1,612 @@ + array( + 'menu' => array( + 'target' => '.wc-gzd-settings-breadcrumb .page-title-action:last', + 'next' => 'default', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '

' . esc_html_x( 'Manage shipments', 'shipments', 'woocommerce-germanized' ) . '

' . esc_html_x( 'To view all your existing shipments in a list you might follow this link or click on the shipments link within the WooCommerce sub-menu.', 'shipments', 'woocommerce-germanized' ) . '

', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'default' => array( + 'target' => '#woocommerce_gzd_shipments_notify_enable-toggle', + 'next' => 'auto', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '

' . esc_html_x( 'E-Mail Notification', 'shipments', 'woocommerce-germanized' ) . '

' . esc_html_x( 'By enabling this option customers receive an email notification as soon as a shipment is marked as shipped.', 'shipments', 'woocommerce-germanized' ) . '

', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'auto' => array( + 'target' => '#woocommerce_gzd_shipments_auto_enable-toggle', + 'next' => 'returns', + 'next_url' => '', + 'next_trigger' => array(), + 'options' => array( + 'content' => '

' . esc_html_x( 'Automation', 'shipments', 'woocommerce-germanized' ) . '

' . esc_html_x( 'Decide whether you want to automatically create shipments to orders reaching a specific status. You can always adjust your shipments by manually editing the shipment within the edit order screen.', 'shipments', 'woocommerce-germanized' ) . '

', + 'position' => array( + 'edge' => 'left', + 'align' => 'left', + ), + ), + ), + 'returns' => array( + 'target' => '#shipments_return_options-description', + 'next' => '', + 'next_url' => $next_url, + 'next_trigger' => array(), + 'options' => array( + 'content' => '

' . esc_html_x( 'Returns', 'shipments', 'woocommerce-germanized' ) . '

' . sprintf( _x( 'Germanized can help you to minimize manual work while handling customer returns. Learn more about returns within our %s.', 'shipments', 'woocommerce-germanized' ), '' . _x( 'documentation', 'shipments', 'woocommerce-germanized' ) . '' ) . '

', + 'position' => array( + 'edge' => 'top', + 'align' => 'top', + ), + ), + ), + ), + ); + } + + return $pointers; + } + + protected static function get_general_settings() { + + $statuses = array_diff_key( wc_gzd_get_shipment_statuses(), array_flip( array( 'gzd-requested' ) ) ); + + $settings = array( + array( + 'title' => '', + 'type' => 'title', + 'id' => 'shipments_options', + ), + + array( + 'title' => _x( 'Notify', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Notify customers about new shipments.', 'shipments', 'woocommerce-germanized' ) . '
' . sprintf( _x( 'Notify customers by email as soon as a shipment is marked as shipped. %s the notification email.', 'shipments', 'woocommerce-germanized' ), '' . _x( 'Manage', 'shipments notification', 'woocommerce-germanized' ) . '' ) . '
', + 'id' => 'woocommerce_gzd_shipments_notify_enable', + 'default' => 'yes', + 'type' => 'gzd_toggle', + ), + + array( + 'title' => _x( 'Default provider', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => _x( 'Select a default shipping provider which will be selected by default in case no provider could be determined automatically.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_default_shipping_provider', + 'default' => '', + 'type' => 'select', + 'options' => wc_gzd_get_shipping_provider_select(), + 'class' => 'wc-enhanced-select', + ), + + array( + 'type' => 'sectionend', + 'id' => 'shipments_options', + ), + + array( + 'title' => _x( 'Automation', 'shipments', 'woocommerce-germanized' ), + 'type' => 'title', + 'id' => 'shipments_auto_options', + ), + + array( + 'title' => _x( 'Enable', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Automatically create shipments for orders.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_auto_enable', + 'default' => 'yes', + 'type' => 'gzd_toggle', + ), + + array( + 'title' => _x( 'Order statuses', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => _x( 'Create shipments as soon as the order reaches one of the following status(es).', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_auto_statuses', + 'default' => array( 'wc-processing', 'wc-on-hold' ), + 'class' => 'wc-enhanced-select-nostd', + 'options' => wc_get_order_statuses(), + 'type' => 'multiselect', + 'custom_attributes' => array( + 'data-show_if_woocommerce_gzd_shipments_auto_enable' => '', + 'data-placeholder' => _x( 'On new order creation', 'shipments', 'woocommerce-germanized' ), + ), + ), + + array( + 'title' => _x( 'Default status', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => _x( 'Choose a default status for the automatically created shipment.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_auto_default_status', + 'default' => 'gzd-processing', + 'class' => 'wc-enhanced-select', + 'options' => $statuses, + 'type' => 'select', + 'custom_attributes' => array( + 'data-show_if_woocommerce_gzd_shipments_auto_enable' => '', + ), + ), + + array( + 'title' => _x( 'Update status', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Mark order as completed after order is fully shipped.', 'shipments', 'woocommerce-germanized' ) . '
' . _x( 'This option will automatically update the order status to completed as soon as all required shipments have been marked as shipped.', 'shipments', 'woocommerce-germanized' ) . '
', + 'id' => 'woocommerce_gzd_shipments_auto_order_shipped_completed_enable', + 'default' => 'yes', + 'type' => 'gzd_toggle', + ), + + array( + 'title' => _x( 'Mark as shipped', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Mark shipments as shipped after order completion.', 'shipments', 'woocommerce-germanized' ) . '
' . _x( 'This option will automatically update contained shipments to shipped (if possible, e.g. not yet delivered) as soon as the order was marked as completed.', 'shipments', 'woocommerce-germanized' ) . '
', + 'id' => 'woocommerce_gzd_shipments_auto_order_completed_shipped_enable', + 'default' => 'no', + 'type' => 'gzd_toggle', + ), + + array( + 'type' => 'sectionend', + 'id' => 'shipments_auto_options', + ), + + array( + 'title' => _x( 'Returns', 'shipments', 'woocommerce-germanized' ), + 'type' => 'title', + 'id' => 'shipments_return_options', + 'desc' => sprintf( _x( 'Returns can be added manually by the shop manager or by the customer. Decide what suits you best by turning customer-added returns on or off in your %s.', 'shipments', 'woocommerce-germanized' ), '' . _x( 'shipping provider settings', 'shipments', 'woocommerce-germanized' ) . '' ), + ), + + array( + 'type' => 'shipment_return_reasons', + ), + + array( + 'title' => _x( 'Days to return', 'shipments', 'woocommerce-germanized' ), + 'desc' => '
' . sprintf( _x( 'In case one of your %s supports returns added by customers you might want to limit the number of days a customer is allowed to add returns to an order. The days are counted starting with the date the order was shipped, completed or created (by checking for existance in this order).', 'shipments', 'woocommerce-germanized' ), '' . _x( 'shipping providers', 'shipments', 'woocommerce-germanized' ) . '' ) . '
', + 'css' => 'max-width: 60px;', + 'type' => 'number', + 'id' => 'woocommerce_gzd_shipments_customer_return_open_days', + 'default' => '14', + ), + + array( + 'type' => 'sectionend', + 'id' => 'shipments_return_options', + ), + + array( + 'title' => _x( 'Customer Account', 'shipments', 'woocommerce-germanized' ), + 'type' => 'title', + 'id' => 'shipments_customer_options', + ), + + array( + 'title' => _x( 'List', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'List shipments on customer account order screen.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_customer_account_enable', + 'default' => 'yes', + 'type' => 'gzd_toggle', + ), + + array( + 'type' => 'sectionend', + 'id' => 'shipments_customer_options', + ), + ); + + return $settings; + } + + public static function get_address_label_by_prop( $prop, $type = 'shipper' ) { + $label = ''; + $fields = wc_gzd_get_shipment_setting_default_address_fields( $type ); + + if ( array_key_exists( $prop, $fields ) ) { + $label = $fields[ $prop ]; + } + + return $label; + } + + protected static function get_address_field_type_by_prop( $prop ) { + $type = 'text'; + + if ( 'country' === $prop ) { + $type = 'single_select_country'; + } + + return $type; + } + + protected static function get_address_desc_by_prop( $prop ) { + $desc = false; + + if ( 'customs_reference_number' === $prop ) { + $desc = _x( 'Your customs reference number, e.g. EORI number', 'shipments', 'woocommerce-germanized' ); + } elseif ( 'customs_uk_vat_id' === $prop ) { + $desc = _x( 'Your UK VAT ID, e.g. for UK exports <= 135 GBP.', 'shipments', 'woocommerce-germanized' ); + } + + return $desc; + } + + protected static function get_address_fields_to_skip() { + return array( 'state', 'street', 'street_number', 'full_name' ); + } + + protected static function get_address_settings() { + $shipper_fields = wc_gzd_get_shipment_setting_address_fields( 'shipper' ); + $return_fields = wc_gzd_get_shipment_setting_address_fields( 'return' ); + + $settings = array( + array( + 'title' => _x( 'Shipper Address', 'shipments', 'woocommerce-germanized' ), + 'type' => 'title', + 'id' => 'shipments_shipper_address', + ), + ); + + // Use WooCommerce address data as fallback/default data on install + $default_shipper_address_data = array( + 'company' => get_option( 'name' ), + 'address_1' => get_option( 'woocommerce_store_address' ), + 'address_2' => get_option( 'woocommerce_store_address_2' ), + 'city' => get_option( 'woocommerce_store_city' ), + 'postcode' => get_option( 'woocommerce_store_postcode' ), + 'email' => get_option( 'woocommerce_email_from_address' ), + 'country' => get_option( 'woocommerce_default_country' ), + ); + + foreach ( $shipper_fields as $field => $value ) { + if ( in_array( $field, self::get_address_fields_to_skip(), true ) ) { + continue; + } + + $default_value = ''; + + if ( array_key_exists( $field, $default_shipper_address_data ) ) { + $default_value = $default_shipper_address_data[ $field ]; + } + + $settings = array_merge( + $settings, + array( + array( + 'title' => self::get_address_label_by_prop( $field ), + 'type' => self::get_address_field_type_by_prop( $field ), + 'id' => "woocommerce_gzd_shipments_shipper_address_{$field}", + 'default' => $default_value, + 'desc_tip' => self::get_address_desc_by_prop( $field ), + ), + ) + ); + } + + $settings = array_merge( + $settings, + array( + array( + 'type' => 'sectionend', + 'id' => 'shipments_shipper_address', + ), + + array( + 'title' => _x( 'Return Address', 'shipments', 'woocommerce-germanized' ), + 'type' => 'title', + 'id' => 'shipments_return_address', + ), + ) + ); + + foreach ( $return_fields as $field => $value ) { + if ( in_array( $field, self::get_address_fields_to_skip(), true ) ) { + continue; + } + + $settings = array_merge( + $settings, + array( + array( + 'title' => self::get_address_label_by_prop( $field ), + 'type' => self::get_address_field_type_by_prop( $field ), + 'id' => "woocommerce_gzd_shipments_return_address_{$field}", + 'default' => '', + 'placeholder' => $value, + 'desc_tip' => self::get_address_desc_by_prop( $field ), + ), + ) + ); + } + + $settings = array_merge( + $settings, + array( + array( + 'type' => 'sectionend', + 'id' => 'shipments_return_address', + ), + ) + ); + + return $settings; + } + + protected static function get_packaging_settings() { + $settings = array( + array( + 'title' => '', + 'type' => 'title', + 'id' => 'packaging_options', + ), + + array( + 'type' => 'packaging_list', + ), + + array( + 'title' => _x( 'Default packaging', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => _x( 'Choose a packaging which serves as fallback or default in case no suitable packaging could be matched for a certain shipment.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'woocommerce_gzd_shipments_default_packaging', + 'default' => '', + 'type' => 'select', + 'options' => wc_gzd_get_packaging_select(), + 'class' => 'wc-enhanced-select', + ), + + array( + 'type' => 'packaging_reports', + 'title' => _x( 'Packaging Report', 'shipments', 'woocommerce-germanized' ), + 'id' => 'packaging_reports', + ), + + array( + 'type' => 'sectionend', + 'id' => 'packaging_options', + ), + ); + + return $settings; + } + + public static function get_settings( $current_section = '' ) { + $settings = array(); + + if ( '' === $current_section ) { + $settings = self::get_general_settings(); + } elseif ( 'packaging' === $current_section ) { + $settings = self::get_packaging_settings(); + } elseif ( 'address' === $current_section ) { + $settings = self::get_address_settings(); + } + + return $settings; + } + + public static function get_additional_breadcrumb_items( $breadcrumb ) { + return $breadcrumb; + } + + public static function get_sections() { + return array( + '' => _x( 'General', 'shipments', 'woocommerce-germanized' ), + 'packaging' => _x( 'Packaging', 'shipments', 'woocommerce-germanized' ), + 'address' => _x( 'Addresses', 'shipments', 'woocommerce-germanized' ), + ); + } + + public static function after_save( $current_section = '' ) { + if ( 'packaging' === $current_section ) { + if ( isset( $_POST['save'] ) && 'create_report' === wc_clean( wp_unslash( $_POST['save'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + $start_date = isset( $_POST['report_year'] ) ? wc_clean( wp_unslash( $_POST['report_year'] ) ) : '01-01-' . ( (int) date( 'Y' ) - 1 ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.DateTime.RestrictedFunctions.date_date + $start_date = ReportHelper::string_to_datetime( $start_date ); + + ReportQueue::start( 'yearly', $start_date ); + } + } + } + + public static function get_sanitized_settings( $settings, $data = null ) { + if ( is_null( $data ) ) { + $data = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + if ( empty( $data ) ) { + return false; + } + + $settings_to_save = array(); + + // Loop options and get values to save. + foreach ( $settings as $option ) { + + if ( ! isset( $option['id'] ) || empty( $option['id'] ) || ! isset( $option['type'] ) || in_array( $option['type'], array( 'title', 'sectionend' ), true ) || ( isset( $option['is_option'] ) && false === $option['is_option'] ) ) { + continue; + } + + $option_key = $option['id']; + $raw_value = isset( $data[ $option_key ] ) ? wp_unslash( $data[ $option_key ] ) : null; + + // Format the value based on option type. + switch ( $option['type'] ) { + case 'checkbox': + $value = '1' === $raw_value || 'yes' === $raw_value ? 'yes' : 'no'; + break; + case 'textarea': + $value = wp_kses_post( trim( $raw_value ) ); + break; + case 'password': + $value = is_null( $raw_value ) ? '' : addslashes( $raw_value ); + $value = trim( $value ); + + if ( class_exists( 'WC_GZD_Secret_Box_Helper' ) ) { + $encrypted = \WC_GZD_Secret_Box_Helper::encrypt( $value ); + + if ( ! is_wp_error( $encrypted ) ) { + $value = $encrypted; + } + } + break; + case 'multiselect': + case 'multi_select_countries': + $value = array_filter( array_map( 'wc_clean', (array) $raw_value ) ); + break; + case 'image_width': + $value = array(); + if ( isset( $raw_value['width'] ) ) { + $value['width'] = wc_clean( $raw_value['width'] ); + $value['height'] = wc_clean( $raw_value['height'] ); + $value['crop'] = isset( $raw_value['crop'] ) ? 1 : 0; + } else { + $value['width'] = $option['default']['width']; + $value['height'] = $option['default']['height']; + $value['crop'] = $option['default']['crop']; + } + break; + case 'select': + $allowed_values = empty( $option['options'] ) ? array() : array_map( 'strval', array_keys( $option['options'] ) ); + if ( empty( $option['default'] ) && empty( $allowed_values ) ) { + $value = null; + break; + } + $default = ( empty( $option['default'] ) ? $allowed_values[0] : $option['default'] ); + $value = in_array( $raw_value, $allowed_values, true ) ? $raw_value : $default; + break; + case 'relative_date_selector': + $value = wc_parse_relative_date_option( $raw_value ); + break; + default: + $value = wc_clean( $raw_value ); + break; + } + + /** + * Sanitize the value of an option. + * + * @since 2.4.0 + */ + $value = apply_filters( 'woocommerce_admin_settings_sanitize_option', $value, $option, $raw_value ); + + $settings_to_save[ $option_key ] = $value; + } + + return $settings_to_save; + } + + public static function render_label_fields( $settings, $shipment, $echo = false ) { + $missing_div_closes = 0; + ob_start(); + foreach ( $settings as $setting ) { + $setting = wp_parse_args( + $setting, + array( + 'id' => '', + 'type' => 'text', + 'custom_attributes' => array(), + ) + ); + + if ( has_action( "woocommerce_gzd_shipment_label_admin_field_{$setting['id']}" ) ) { + do_action( "woocommerce_gzd_shipment_label_admin_field_{$setting['id']}", $setting, $shipment ); + } elseif ( 'select' === $setting['type'] ) { + woocommerce_wp_select( $setting ); + } elseif ( 'multiselect' === $setting['type'] ) { + $setting['class'] = 'select short wc-enhanced-select'; + $setting['custom_attributes'] = array_merge( $setting['custom_attributes'], array( 'multiple' => 'multiple' ) ); + + if ( ! strstr( $setting['id'], '[]' ) ) { + $setting['name'] = $setting['id'] . '[]'; + } + + woocommerce_wp_select( $setting ); + } elseif ( 'checkbox' === $setting['type'] ) { + woocommerce_wp_checkbox( $setting ); + } elseif ( 'textarea' === $setting['type'] ) { + woocommerce_wp_textarea_input( $setting ); + } elseif ( 'text' === $setting['type'] ) { + woocommerce_wp_text_input( $setting ); + } elseif ( 'date' === $setting['type'] ) { + $setting['class'] = 'datepicker'; + $setting['type'] = 'date'; + + woocommerce_wp_text_input( $setting ); + } elseif ( 'number' === $setting['type'] ) { + woocommerce_wp_text_input( $setting ); + } elseif ( 'services_start' === $setting['type'] ) { + $hide_default = isset( $setting['hide_default'] ) ? wc_string_to_bool( $setting['hide_default'] ) : false; + $missing_div_closes++; + ?> +

+ + + + + + +

+
+ +
+ +
+ +
+ 0 ) { + while ( $missing_div_closes > 0 ) { + $missing_div_closes--; + echo '
'; + } + } + + $html = ob_get_clean(); + + if ( ! $echo ) { + return $html; + } else { + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Admin/Table.php b/packages/woocommerce-germanized-shipments/src/Admin/Table.php new file mode 100644 index 000000000..6b5ca0eb4 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Admin/Table.php @@ -0,0 +1,1200 @@ + 'simple', + ) + ); + + $this->shipment_type = $args['type']; + + parent::__construct( + array( + 'plural' => 'shipments', + 'screen' => isset( $args['screen'] ) ? $args['screen'] : null, + ) + ); + } + + public function set_default_hidden_columns( $columns, $screen ) { + if ( $this->screen->id === $screen->id ) { + $columns = array_merge( $columns, $this->get_default_hidden_columns() ); + } + + return $columns; + } + + protected function get_default_hidden_columns() { + return array( + 'weight', + 'dimensions', + 'packaging', + ); + } + + public function enable_query_removing( $args ) { + $args = array_merge( + $args, + array( + 'changed', + 'bulk_action', + ) + ); + + return $args; + } + + /** + * Handle bulk actions. + * + * @param string $redirect_to URL to redirect to. + * @param string $action Action name. + * @param array $ids List of ids. + * @return string + */ + public function handle_bulk_actions( $action, $ids, $redirect_to ) { + $ids = array_reverse( array_map( 'absint', $ids ) ); + $changed = 0; + + if ( false !== strpos( $action, 'mark_' ) ) { + + $shipment_statuses = wc_gzd_get_shipment_statuses(); + $new_status = substr( $action, 5 ); // Get the status name from action. + + // Sanity check: bail out if this is actually not a status, or is not a registered status. + if ( isset( $shipment_statuses[ 'gzd-' . $new_status ] ) ) { + + foreach ( $ids as $id ) { + + if ( $shipment = wc_gzd_get_shipment( $id ) ) { + $shipment->update_status( $new_status, true ); + + /** + * Action that fires after a shipment bulk status update has been processed. + * + * @param integer $shipment_id The shipment id. + * @param string $new_status The new shipment status. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_edit_status', $id, $new_status ); + $changed++; + } + } + } + } elseif ( 'delete' === $action ) { + foreach ( $ids as $id ) { + if ( $shipment = wc_gzd_get_shipment( $id ) ) { + $shipment->delete( true ); + $changed++; + } + } + } elseif ( 'confirm_requests' === $action ) { + foreach ( $ids as $id ) { + if ( $shipment = wc_gzd_get_shipment( $id ) ) { + if ( 'return' === $shipment->get_type() ) { + if ( $shipment->is_customer_requested() && $shipment->has_status( 'requested' ) ) { + if ( $shipment->confirm_customer_request() ) { + $changed++; + } + } + } + } + } + } + + /** + * Filter to decide whether a Shipment has changed during bulk action or not. + * + * @param boolean $changed Whether the Shipment has changed or not. + * @param string $action The bulk action + * @param string $redirect_to The redirect URL. + * @param Table $table The table instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $changed = apply_filters( 'woocommerce_gzd_shipments_bulk_action', $changed, $action, $ids, $redirect_to, $this ); + + if ( $changed ) { + $redirect_to = add_query_arg( + array( + 'changed' => $changed, + 'ids' => join( ',', $ids ), + 'bulk_action' => $action, + ), + $redirect_to + ); + } + + return esc_url_raw( $redirect_to ); + } + + public function output_notice() { + + if ( ! empty( $this->notice ) ) { + $type = isset( $this->notice['type'] ) ? $this->notice['type'] : 'success'; + + echo '
' . wp_kses_post( wpautop( $this->notice['message'] ) ) . '
'; + } + + $this->notice = array(); + } + + /** + * Show confirmation message that order status changed for number of orders. + */ + public function set_bulk_notice() { + + $number = isset( $_REQUEST['changed'] ) ? absint( $_REQUEST['changed'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $bulk_action = isset( $_REQUEST['bulk_action'] ) ? wc_clean( wp_unslash( $_REQUEST['bulk_action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + if ( 'delete' === $bulk_action ) { + + $this->set_notice( sprintf( _nx( '%d shipment deleted.', '%d shipments deleted.', $number, 'shipments', 'woocommerce-germanized' ), number_format_i18n( $number ) ) ); + + } elseif ( strpos( $bulk_action, 'mark_' ) !== false ) { + + $shipment_statuses = wc_gzd_get_shipment_statuses(); + + // Check if any status changes happened. + foreach ( $shipment_statuses as $slug => $name ) { + + if ( 'mark_' . str_replace( 'gzd-', '', $slug ) === $bulk_action ) { // WPCS: input var ok, CSRF ok. + $this->set_notice( sprintf( _nx( '%d shipment status changed.', '%d shipment statuses changed.', $number, 'shipments', 'woocommerce-germanized' ), number_format_i18n( $number ) ) ); + break; + } + } + } + + /** + * Action that fires after bulk updating shipments. Action might be usefull to add + * custom notices after custom bulk actions have been applied. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_bulk_notice + * + * @param string $bulk_action The bulk action. + * @param Table $shipment_table The table object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$this->get_hook_prefix()}bulk_notice", $bulk_action, $this ); + } + + public function set_notice( $message, $type = 'success' ) { + $this->notice = array( + 'message' => $message, + 'type' => $type, + ); + } + + protected function get_stati() { + return $this->stati; + } + + /** + * @return bool + */ + public function ajax_user_can() { + return current_user_can( 'edit_shop_orders' ); + } + + public function get_page_option() { + return 'woocommerce_page_wc_gzd_shipments_per_page'; + } + + /** + * @global array $avail_post_stati + * @global WP_Query $wp_query + * @global int $per_page + * @global string $mode + */ + public function prepare_items() { + global $per_page; + + $per_page = $this->get_items_per_page( $this->get_page_option(), 10 ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + + /** + * Filter to adjust Shipment's table items per page. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_edit_per_page + * + * @param integer $per_page Number of Shipments per page. + * @param string $type The type in this case shipment. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $per_page = apply_filters( "{$this->get_hook_prefix()}edit_per_page", $per_page, 'shipment' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $this->stati = wc_gzd_get_shipment_statuses(); + $this->counts = wc_gzd_get_shipment_counts( $this->shipment_type ); + $paged = $this->get_pagenum(); + + $args = array( + 'limit' => $per_page, + 'paginate' => true, + 'offset' => ( $paged - 1 ) * $per_page, + 'count_total' => true, + 'type' => $this->shipment_type, + ); + + if ( isset( $_REQUEST['shipment_status'] ) && in_array( $_REQUEST['shipment_status'], array_keys( $this->stati ), true ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $args['status'] = wc_clean( wp_unslash( $_REQUEST['shipment_status'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + if ( isset( $_REQUEST['orderby'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( 'weight' === $_REQUEST['orderby'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $args['orderby'] = 'weight'; + } else { + $args['orderby'] = wc_clean( wp_unslash( $_REQUEST['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + } + + if ( isset( $_REQUEST['order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $args['order'] = 'asc' === $_REQUEST['order'] ? 'ASC' : 'DESC'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + if ( isset( $_REQUEST['parent_id'] ) && ! empty( $_REQUEST['parent_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $args['parent_id'] = absint( $_REQUEST['parent_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + if ( isset( $_REQUEST['order_id'] ) && ! empty( $_REQUEST['order_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $args['order_id'] = absint( $_REQUEST['order_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + if ( isset( $_REQUEST['shipping_provider'] ) && ! empty( $_REQUEST['shipping_provider'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $args['shipping_provider'] = wc_clean( wp_unslash( $_REQUEST['shipping_provider'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + if ( isset( $_REQUEST['m'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $m = wc_clean( wp_unslash( $_REQUEST['m'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $year = substr( $m, 0, 4 ); + + if ( ! empty( $year ) ) { + $month = ''; + $day = ''; + + if ( strlen( $m ) > 5 ) { + $month = substr( $m, 4, 2 ); + } + + if ( strlen( $m ) > 7 ) { + $day = substr( $m, 6, 2 ); + } + + $datetime = new WC_DateTime(); + $datetime->setDate( $year, 1, 1 ); + + if ( ! empty( $month ) ) { + $datetime->setDate( $year, $month, 1 ); + } + + if ( ! empty( $day ) ) { + $datetime->setDate( $year, $month, $day ); + } + + $next_month = clone $datetime; + $next_month->modify( '+ 1 month' ); + // Make sure to not include next month first day + $next_month->modify( '-1 day' ); + + $args['date_created'] = $datetime->format( 'Y-m-d' ) . '...' . $next_month->format( 'Y-m-d' ); + } + } + + if ( isset( $_REQUEST['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $search = wc_clean( wp_unslash( $_REQUEST['s'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + if ( ! is_numeric( $search ) ) { + $search = '*' . $search . '*'; + } + + $args['search'] = $search; + } + + // Query the user IDs for this page + $this->query = new ShipmentQuery( apply_filters( "{$this->get_hook_prefix()}query_args", $args, $this ) ); + $this->items = $this->query->get_shipments(); + + $this->set_pagination_args( + array( + 'total_items' => $this->query->get_total(), + 'per_page' => $per_page, + ) + ); + } + + /** + */ + public function no_items() { + echo esc_html_x( 'No shipments found', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Determine if the current view is the "All" view. + * + * @since 4.2.0 + * + * @return bool Whether the current view is the "All" view. + */ + protected function is_base_request() { + $vars = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + unset( $vars['paged'] ); + + if ( empty( $vars ) ) { + return true; + } + + return 1 === count( $vars ); + } + + /** + * @global array $locked_post_status This seems to be deprecated. + * @global array $avail_post_stati + * @return array + */ + protected function get_views() { + + $status_links = array(); + $num_shipments = $this->counts; + $total_shipments = array_sum( (array) $num_shipments ); + $class = ''; + $all_args = array(); + + if ( empty( $class ) && ( $this->is_base_request() || isset( $_REQUEST['all_shipments'] ) ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $class = 'current'; + } + + $all_inner_html = sprintf( + _nx( + 'All (%s)', + 'All (%s)', + $total_shipments, + 'shipments', + 'woocommerce-germanized-shipments' + ), + number_format_i18n( $total_shipments ) + ); + + $status_links['all'] = $this->get_edit_link( $all_args, $all_inner_html, $class ); + + foreach ( wc_gzd_get_shipment_statuses() as $status => $title ) { + $class = ''; + + if ( ! in_array( $status, array_keys( $this->stati ), true ) || empty( $num_shipments[ $status ] ) ) { + continue; + } + + if ( isset( $_REQUEST['shipment_status'] ) && $status === $_REQUEST['shipment_status'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $class = 'current'; + } + + $status_args = array( + 'shipment_status' => $status, + ); + + $status_label = sprintf( + translate_nooped_plural( _nx_noop( ( $title . ' (%s)' ), ( $title . ' (%s)' ), 'shipments', 'woocommerce-germanized' ), $num_shipments[ $status ] ), // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralSingle,WordPress.WP.I18n.NonSingularStringLiteralPlural + number_format_i18n( $num_shipments[ $status ] ) + ); + + $status_links[ $status ] = $this->get_edit_link( $status_args, $status_label, $class ); + } + + return $status_links; + } + + /** + * Helper to create links to edit.php with params. + * + * @since 4.4.0 + * + * @param string[] $args Associative array of URL parameters for the link. + * @param string $label Link text. + * @param string $class Optional. Class attribute. Default empty string. + * @return string The formatted link string. + */ + protected function get_edit_link( $args, $label, $class = '' ) { + $url = add_query_arg( $args, $this->get_main_page() ); + + $class_html = $aria_current = ''; + if ( ! empty( $class ) ) { + $class_html = sprintf( + ' class="%s"', + esc_attr( $class ) + ); + + if ( 'current' === $class ) { + $aria_current = ' aria-current="page"'; + } + } + + return sprintf( + '%s', + esc_url( $url ), + $class_html, + $aria_current, + $label + ); + } + + /** + * @return string + */ + public function current_action() { + if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + return 'delete_all'; + } + + return parent::current_action(); + } + + /** + * Display a monthly dropdown for filtering items + * + * @since 3.0.6 + * + * @global wpdb $wpdb + * @global WP_Locale $wp_locale + * + * @param string $post_type + */ + protected function months_dropdown( $type ) { + global $wpdb, $wp_locale; + + $extra_checks = "AND shipment_status != 'auto-draft'"; + + if ( isset( $_GET['shipment_status'] ) && 'all' !== $_GET['shipment_status'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $extra_checks = $wpdb->prepare( ' AND shipment_status = %s', wc_clean( wp_unslash( $_GET['shipment_status'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + $months = $wpdb->get_results( "SELECT DISTINCT YEAR( shipment_date_created ) AS year, MONTH( shipment_date_created ) AS month FROM $wpdb->gzd_shipments WHERE 1=1 $extra_checks ORDER BY shipment_date_created DESC" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $month_count = count( $months ); + + if ( ! $month_count || ( 1 === $month_count && 0 === $months[0]->month ) ) { + return; + } + + $m = isset( $_GET['m'] ) ? absint( wp_unslash( $_GET['m'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + ?> + + + +
+

+
+ +
+ get_notices( 'error' ); + $info = $handler->get_notices( 'info' ); + $success = $handler->get_notices( 'success' ); + ?> + + +
+

+
+ + + admin_after_error(); ?> + + + +
+

+
+ + + +
+

get_success_message() ); ?>

+
+ + + admin_handled(); + /** + * Action that fires after a certain bulk action result has been rendered. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * `$bulk_action` refers to the bulk action handled. + * + * Example hook name: woocommerce_gzd_return_shipments_table_mark_processing_handled + * + * @param BulkActionHandler $bulk_action_handler The bulk action handler. + * @param string $bulk_action The bulk action. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$this->get_hook_prefix()}bulk_action_{$bulk_action}_handled", $handler, $bulk_action ); + ?> + + reset(); ?> + + +
+ months_dropdown( 'shipment' ); + $this->order_filter(); + $this->shipping_provider_filter(); + + /** + * Action that fires after outputting Shipments table view filters. + * Might be used to add custom filters to the Shipments table view. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_filters + * + * @param string $which top or bottom. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$this->get_hook_prefix()}filters", $which ); + + $output = ob_get_clean(); + + if ( ! empty( $output ) ) { + echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + + submit_button( _x( 'Filter', 'shipments', 'woocommerce-germanized' ), '', 'filter_action', false, array( 'id' => 'shipment-query-submit' ) ); + } + } + ?> +
+ get_title(); + } + } + ?> + + + + '; + $columns['title'] = _x( 'Title', 'shipments', 'woocommerce-germanized' ); + $columns['date'] = _x( 'Date', 'shipments', 'woocommerce-germanized' ); + $columns['status'] = _x( 'Status', 'shipments', 'woocommerce-germanized' ); + $columns['items'] = _x( 'Items', 'shipments', 'woocommerce-germanized' ); + $columns['address'] = _x( 'Address', 'shipments', 'woocommerce-germanized' ); + $columns['packaging'] = _x( 'Packaging', 'shipments', 'woocommerce-germanized' ); + $columns['weight'] = _x( 'Weight', 'shipments', 'woocommerce-germanized' ); + $columns['dimensions'] = _x( 'Dimensions', 'shipments', 'woocommerce-germanized' ); + $columns['order'] = _x( 'Order', 'shipments', 'woocommerce-germanized' ); + $columns['actions'] = _x( 'Actions', 'shipments', 'woocommerce-germanized' ); + + return $columns; + } + + /** + * @return array + */ + public function get_columns() { + $columns = $this->get_custom_columns(); + + /** + * Filters the columns displayed in the Shipments list table. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_edit_per_page + * + * @param string[] $columns An associative array of column headings. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $columns = apply_filters( "{$this->get_hook_prefix()}columns", $columns ); + + return $columns; + } + + /** + * @return array + */ + protected function get_sortable_columns() { + return apply_filters( + "{$this->get_hook_prefix()}sortable_columns", + array( + 'date' => array( 'date_created', false ), + 'weight' => 'weight', + 'order' => 'order_id', + ), + $this + ); + } + + /** + * Gets the name of the default primary column. + * + * @since 4.3.0 + * + * @return string Name of the default primary column, in this case, 'title'. + */ + protected function get_default_primary_column_name() { + return 'title'; + } + + /** + * Handles the default column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + * @param string $column_name The current column name. + */ + public function column_default( $shipment, $column_name ) { + + /** + * Fires in each custom column in the Shipments list table. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_filters + * + * @param string $column_name The name of the column to display. + * @param integer $shipment_id The current shipment id. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$this->get_hook_prefix()}custom_column", $column_name, $shipment->get_id() ); + } + + public function get_main_page() { + return 'admin.php?page=wc-gzd-shipments'; + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + */ + public function column_title( $shipment ) { + $title = sprintf( _x( '%1$s #%2$s', 'shipment title', 'woocommerce-germanized' ), wc_gzd_get_shipment_label_title( $shipment->get_type() ), $shipment->get_shipment_number() ); + + if ( $order = $shipment->get_order() ) { + echo '' . wp_kses_post( $title ) . ' '; + } else { + echo wp_kses_post( $title ) . ' '; + } + + echo '

'; + + if ( $packaging = $shipment->get_packaging() ) { + echo '' . wp_kses_post( $packaging->get_description() ) . ' '; + } + + $provider = $shipment->get_shipping_provider(); + + if ( ! empty( $provider ) ) { + echo '' . sprintf( esc_html_x( 'via %s', 'shipments', 'woocommerce-germanized' ), wp_kses_post( wc_gzd_get_shipping_provider_title( $provider ) ) ) . ' '; + } + + if ( $tracking_id = $shipment->get_tracking_id() ) { + if ( $shipment->has_tracking() && ( $tracking_url = $shipment->get_tracking_url() ) ) { + echo '' . esc_html( $tracking_id ) . ''; + } else { + echo '' . esc_html( $tracking_id ) . ''; + } + } + + echo '

'; + } + + protected function get_custom_actions( $shipment, $actions ) { + return $actions; + } + + /** + * Handles shipment actions. + * + * @since 0.0.1 + * + * @param Shipment $shipment The current shipment object. + */ + protected function column_actions( $shipment ) { + echo '

'; + + /** + * Action that fires before table actions are outputted for a Shipment. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_actions_start + * + * @param Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$this->get_hook_prefix()}actions_start", $shipment ); + + $actions = array(); + + if ( $shipment->has_status( array( 'draft' ) ) ) { + $actions['processing'] = array( + 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_gzd_update_shipment_status&status=processing&shipment_id=' . $shipment->get_id() ), 'update-shipment-status' ), + 'name' => _x( 'Processing', 'shipments', 'woocommerce-germanized' ), + 'action' => 'processing', + ); + } + + if ( $shipment->has_status( array( 'draft', 'processing' ) ) ) { + $actions['shipped'] = array( + 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_gzd_update_shipment_status&status=shipped&shipment_id=' . $shipment->get_id() ), 'update-shipment-status' ), + 'name' => _x( 'Shipped', 'shipments', 'woocommerce-germanized' ), + 'action' => 'shipped', + ); + } + + if ( $shipment->supports_label() ) { + + if ( $label = $shipment->get_label() ) { + + $actions['download_label'] = array( + 'url' => $label->get_download_url(), + 'name' => _x( 'Download label', 'shipments', 'woocommerce-germanized' ), + 'action' => 'download-label download', + 'target' => '_blank', + ); + + } elseif ( $shipment->needs_label() ) { + + $actions['generate_label'] = array( + 'url' => '#', + 'name' => _x( 'Generate label', 'shipments', 'woocommerce-germanized' ), + 'action' => 'generate-label generate', + ); + + include Package::get_path() . '/includes/admin/views/label/html-shipment-label-backbone.php'; + } + } + + $actions = $this->get_custom_actions( $shipment, $actions ); + + /** + * Filters the actions available for Shipments table list column. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_actions + * + * @param array $actions The registered Shipment actions. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $actions = apply_filters( "{$this->get_hook_prefix()}actions", $actions, $shipment ); + + echo wc_gzd_render_shipment_action_buttons( $actions ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + + /** + * Action that fires after table actions are outputted for a Shipment. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_actions_end + * + * @param Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$this->get_hook_prefix()}actions_end", $shipment ); + + echo '

'; + } + + public function column_cb( $shipment ) { + if ( current_user_can( 'edit_shop_orders' ) ) : + ?> + + + + + + get_items() as $item ) : ?> + + + + + + +
+ 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_formatted_address(); + + if ( $address ) { + echo '' . esc_html( preg_replace( '##i', ', ', $address ) ) . ''; + } else { + echo '–'; + } + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + */ + public function column_status( $shipment ) { + echo '' . esc_html( wc_gzd_get_shipment_status_name( $shipment->get_status() ) ) . ''; + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + */ + public function column_weight( $shipment ) { + echo wc_gzd_format_shipment_weight( $shipment->get_total_weight(), $shipment->get_weight_unit() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + */ + public function column_packaging( $shipment ) { + if ( $packaging = $shipment->get_packaging() ) { + echo wp_kses_post( $packaging->get_description() ); + } else { + echo '–'; + } + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + */ + public function column_dimensions( $shipment ) { + echo wc_gzd_format_shipment_dimensions( $shipment->get_dimensions(), $shipment->get_dimension_unit() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + */ + public function column_date( $shipment ) { + $shipment_timestamp = $shipment->get_date_created() ? $shipment->get_date_created()->getTimestamp() : ''; + + if ( ! $shipment_timestamp ) { + echo '–'; + return; + } + + // Check if the order was created within the last 24 hours, and not in the future. + if ( $shipment_timestamp > strtotime( '-1 day', time() ) && $shipment_timestamp <= time() ) { + $show_date = sprintf( + /* translators: %s: human-readable time difference */ + _x( '%s ago', '%s = human-readable time difference', 'woocommerce-germanized' ), + human_time_diff( $shipment->get_date_created()->getTimestamp(), time() ) + ); + } else { + /** + * Filter to adjust the Shipment date format in table view. + * + * @param string $format The date format. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $show_date = $shipment->get_date_created()->date_i18n( apply_filters( 'woocommerce_gzd_admin_shipment_date_format', _x( 'M j, Y', 'shipments', 'woocommerce-germanized' ) ) ); + } + + printf( + '', + esc_attr( $shipment->get_date_created()->date( 'c' ) ), + esc_html( $shipment->get_date_created()->date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ), + esc_html( $show_date ) + ); + } + + /** + * Handles the post author column output. + * + * @since 4.3.0 + * + * @param Shipment $shipment The current shipment object. + */ + public function column_order( $shipment ) { + if ( ( $order = $shipment->get_order() ) && is_callable( array( $order, 'get_edit_order_url' ) ) ) { + echo '' . esc_html( $order->get_order_number() ) . ''; + } else { + echo esc_html( $shipment->get_order_id() ); + } + } + + /** + * + * @param int|WC_GZD_Shipment $shipment + */ + public function single_row( $shipment ) { + $GLOBALS['shipment'] = $shipment; + $classes = 'shipment shipment-status-' . $shipment->get_status(); + ?> + + single_row_columns( $shipment ); ?> + + shipment_type ? '' : '_' . $this->shipment_type ); + + return "woocommerce_gzd{$suffix}_shipments_table_"; + } + + /** + * @return array + */ + protected function get_bulk_actions() { + $actions = array(); + + if ( current_user_can( 'delete_shop_orders' ) ) { + $actions['delete'] = _x( 'Delete Permanently', 'shipments', 'woocommerce-germanized' ); + } + + $actions['mark_processing'] = _x( 'Change status to processing', 'shipments', 'woocommerce-germanized' ); + $actions['mark_shipped'] = _x( 'Change status to shipped', 'shipments', 'woocommerce-germanized' ); + $actions['mark_delivered'] = _x( 'Change status to delivered', 'shipments', 'woocommerce-germanized' ); + $actions['labels'] = _x( 'Generate and download labels', 'shipments', 'woocommerce-germanized' ); + + $actions = $this->get_custom_bulk_actions( $actions ); + + /** + * Filter to register addtional bulk actions for shipments. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type e.g. return. In case of simple shipments the type is omitted. + * + * Example hook name: woocommerce_gzd_return_shipments_table_bulk_actions + * + * @param array $actions Array containing key => value pairs. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}bulk_actions", $actions ); + } + +} diff --git a/packages/woocommerce-germanized-shipments/src/Ajax.php b/packages/woocommerce-germanized-shipments/src/Ajax.php new file mode 100644 index 000000000..8c7415359 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Ajax.php @@ -0,0 +1,1558 @@ +hide_errors(); + } + + public static function send_return_shipment_notification_email() { + $success = false; + + if ( current_user_can( 'edit_shop_orders' ) && isset( $_REQUEST['shipment_id'] ) ) { + + if ( isset( $_GET['shipment_id'] ) ) { + $referrer = check_admin_referer( 'send-return-shipment-notification' ); + } else { + $referrer = check_ajax_referer( 'send-return-shipment-notification', 'security' ); + } + + if ( $referrer ) { + $shipment_id = absint( wp_unslash( $_REQUEST['shipment_id'] ) ); + + if ( $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + + if ( 'return' === $shipment->get_type() ) { + WC()->mailer()->emails['WC_GZD_Email_Customer_Return_Shipment']->trigger( $shipment_id ); + $success = true; + } + } + } + + if ( isset( $_GET['shipment_id'] ) ) { + wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'admin.php?page=wc-gzd-return-shipments' ) ); + exit; + } else { + if ( $success ) { + wp_send_json( + array( + 'success' => true, + 'messages' => array( + _x( 'Notification successfully sent to customer.', 'shipments', 'woocommerce-germanized' ), + ), + ) + ); + } else { + wp_send_json( + array( + 'success' => false, + 'messages' => array( + _x( 'There was an error while sending the notification.', 'shipments', 'woocommerce-germanized' ), + ), + ) + ); + } + } + } + } + + public static function confirm_return_request() { + $success = false; + + if ( current_user_can( 'edit_shop_orders' ) && isset( $_REQUEST['shipment_id'] ) ) { + + if ( isset( $_GET['shipment_id'] ) ) { + $referrer = check_admin_referer( 'confirm-return-request' ); + } else { + $referrer = check_ajax_referer( 'confirm-return-request', 'security' ); + } + + if ( $referrer ) { + $shipment_id = absint( wp_unslash( $_REQUEST['shipment_id'] ) ); + + if ( $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + if ( 'return' === $shipment->get_type() ) { + + if ( $shipment->confirm_customer_request() ) { + $success = true; + } + } + } + } + + if ( isset( $_GET['shipment_id'] ) ) { + wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'admin.php?page=wc-gzd-return-shipments' ) ); + exit; + } else { + if ( $success ) { + wp_send_json( + array( + 'success' => true, + 'messages' => array( + _x( 'Return request confirmed successfully.', 'shipments', 'woocommerce-germanized' ), + ), + 'shipment_id' => $shipment->get_id(), + 'needs_refresh' => true, + 'fragments' => array( + 'div#shipment-' . $shipment_id => self::get_shipment_html( $shipment ), + ), + ) + ); + } else { + wp_send_json( + array( + 'success' => false, + 'messages' => array( + _x( 'There was an error while confirming the request.', 'shipments', 'woocommerce-germanized' ), + ), + ) + ); + } + } + } + } + + public static function create_shipment_label_form() { + check_ajax_referer( 'create-shipment-label-form', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['shipment_id'] ) ) { + wp_die( -1 ); + } + + $shipment_id = absint( $_POST['shipment_id'] ); + $response = array(); + $response_error = array( + 'success' => false, + 'messages' => array( + _x( 'There was an error creating the label.', 'shipments', 'woocommerce-germanized' ), + ), + ); + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( $shipment->supports_label() && $shipment->needs_label() ) { + $html = $shipment->get_label_settings_html(); + } + + $response = array( + 'fragments' => array( + '.wc-gzd-shipment-create-label' => '
' . $html . '
', + ), + 'shipment_id' => $shipment_id, + 'success' => true, + ); + + wp_send_json( $response ); + } + + public static function remove_shipment_label() { + check_ajax_referer( 'remove-shipment-label', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['shipment_id'] ) ) { + wp_die( -1 ); + } + + $response = array(); + $response_error = array( + 'success' => false, + 'messages' => array( + _x( 'There was an error deleting the label.', 'shipments', 'woocommerce-germanized' ), + ), + ); + + $shipment_id = absint( $_POST['shipment_id'] ); + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $label = $shipment->get_label() ) { + wp_send_json( $response_error ); + } + + if ( $shipment->delete_label( true ) ) { + $response = array( + 'success' => true, + 'shipment_id' => $shipment->get_id(), + 'needs_refresh' => true, + 'fragments' => array( + 'div#shipment-' . $shipment_id => self::get_shipment_html( $shipment ), + ), + ); + } else { + wp_send_json( $response_error ); + } + + wp_send_json( $response ); + } + + public static function create_shipment_label() { + check_ajax_referer( 'create-shipment-label', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['shipment_id'] ) ) { + wp_die( -1 ); + } + + $response = array(); + $response_error = array( + 'success' => false, + 'messages' => array( + _x( 'There was an error processing the label.', 'shipments', 'woocommerce-germanized' ), + ), + ); + + $shipment_id = absint( $_POST['shipment_id'] ); + $result = false; + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( $shipment->supports_label() && $shipment->needs_label() ) { + $data = array(); + + foreach ( $_POST as $key => $value ) { + if ( in_array( $key, array( 'action', 'security' ), true ) ) { + continue; + } + + $data[ $key ] = wc_clean( wp_unslash( $value ) ); + } + + $result = $shipment->create_label( $data ); + } + + if ( is_wp_error( $result ) ) { + $result = wc_gzd_get_shipment_error( $result ); + } + + if ( is_wp_error( $result ) && ! $result->is_soft_error() ) { + $response = array( + 'success' => false, + 'messages' => $result->get_error_messages_by_type(), + ); + } elseif ( $label = $shipment->get_label() ) { + $order_shipment = wc_gzd_get_shipment_order( $shipment->get_order() ); + $order_status_html = $order_shipment ? self::get_global_order_status_html( $order_shipment->get_order() ) : array(); + + $response = array( + 'success' => true, + 'label_id' => $label->get_id(), + 'shipment_id' => $shipment_id, + 'messages' => is_wp_error( $result ) ? $result->get_error_messages_by_type() : array(), + 'needs_refresh' => true, + 'fragments' => array( + 'div#shipment-' . $shipment_id => self::get_shipment_html( $shipment ), + '.order-shipping-status' => $order_shipment ? self::get_order_status_html( $order_shipment ) : '', + '.order-return-status' => $order_shipment ? self::get_order_return_status_html( $order_shipment ) : '', + '.order_data_column p.wc-order-status' => ! empty( $order_status_html ) ? $order_status_html['status'] : '', + 'input[name=post_status]' => ! empty( $order_status_html ) ? $order_status_html['input'] : '', + 'tr#shipment-' . $shipment_id . ' td.actions .wc-gzd-shipment-action-button-generate-label' => self::label_download_button_html( $label ), + ), + ); + + if ( empty( $response['fragments']['.order_data_column p.wc-order-status'] ) ) { + unset( $response['fragments']['.order_data_column p.wc-order-status'] ); + } + } else { + $response = $response_error; + } + + wp_send_json( $response ); + } + + protected static function get_shipment_html( $p_shipment, $p_is_active = true ) { + $is_active = $p_is_active; + $shipment = $p_shipment; + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-shipment.php'; + $html = ob_get_clean(); + + return $html; + } + + protected static function get_label_html( $p_shipment, $p_label = false ) { + $shipment = $p_shipment; + + if ( $p_label ) { + $label = $p_label; + } + + ob_start(); + include Package::get_path() . '/includes/admin/views/label/html-shipment-label.php'; + $html = ob_get_clean(); + + return $html; + } + + /** + * @param ShipmentLabel $label + * + * @return string + */ + protected static function label_download_button_html( $label ) { + return '' . _x( 'Download label', 'shipments', 'woocommerce-germanized' ) . ''; + } + + public static function edit_shipping_provider_status() { + check_ajax_referer( 'edit-shipping-providers', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['provider'] ) || ! isset( $_POST['enable'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error while trying to save the shipping provider status.', 'shipments', 'woocommerce-germanized' ), + ); + + $provider = sanitize_key( wc_clean( wp_unslash( $_POST['provider'] ) ) ); + $enable = wc_clean( wp_unslash( $_POST['enable'] ) ); + $helper = Helper::instance(); + $response = array( + 'success' => true, + 'provider' => $provider, + 'message' => '', + ); + + $helper->load_shipping_providers(); + + if ( $shipping_provider = $helper->get_shipping_provider( $provider ) ) { + if ( 'yes' === $enable ) { + $response['activated'] = 'yes'; + $shipping_provider->activate(); + } else { + $response['activated'] = 'no'; + $shipping_provider->deactivate(); + } + + wp_send_json( $response ); + } else { + wp_send_json( $response_error ); + } + } + + public static function remove_shipping_provider() { + check_ajax_referer( 'remove-shipping-provider', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['provider'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error while trying to delete the shipping provider.', 'shipments', 'woocommerce-germanized' ), + ); + + $provider = sanitize_key( wc_clean( wp_unslash( $_POST['provider'] ) ) ); + $helper = Helper::instance(); + $response = array( + 'success' => true, + 'provider' => $provider, + 'message' => '', + ); + + $helper->load_shipping_providers(); + + if ( $shipping_provider = $helper->get_shipping_provider( $provider ) ) { + $shipping_provider->delete(); + wp_send_json( $response ); + } else { + wp_send_json( $response_error ); + } + } + + public static function sort_shipping_provider() { + check_ajax_referer( 'sort-shipping-provider', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['order'] ) ) { + wp_die( -1 ); + } + + $order = wc_clean( wp_unslash( $_POST['order'] ) ); + $order_count = 0; + $helper = Helper::instance(); + $response = array( + 'success' => true, + 'message' => '', + ); + + $helper->load_shipping_providers(); + + foreach ( $order as $shipping_provider_name ) { + if ( $shipping_provider = $helper->get_shipping_provider( $shipping_provider_name ) ) { + $shipping_provider->set_order( ++$order_count ); + $shipping_provider->save(); + } + } + + wp_send_json( $response ); + } + + public static function shipments_bulk_action_handle() { + $action = isset( $_POST['bulk_action'] ) ? wc_clean( wp_unslash( $_POST['bulk_action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing + $type = isset( $_POST['type'] ) ? wc_clean( wp_unslash( $_POST['type'] ) ) : 'simple'; // phpcs:ignore WordPress.Security.NonceVerification.Missing + + check_ajax_referer( "woocommerce_gzd_shipments_{$action}", 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) || ! isset( $_POST['step'] ) || ! isset( $_POST['ids'] ) ) { + wp_die( -1 ); + } + + $response_error = array( + 'success' => false, + 'message' => _x( 'There was an error while bulk processing shipments.', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + ); + + $handlers = Admin::get_bulk_action_handlers(); + + if ( ! array_key_exists( $action, $handlers ) ) { + wp_send_json( $response_error ); + } + + $ids = isset( $_POST['ids'] ) ? array_map( 'absint', $_POST['ids'] ) : array(); + $step = isset( $_POST['step'] ) ? absint( $_POST['step'] ) : 1; + + $handler = $handlers[ $action ]; + + if ( 1 === $step ) { + $handler->reset( true ); + } + + $handler->set_step( $step ); + $handler->set_ids( $ids ); + $handler->set_shipment_type( $type ); + + $handler->handle(); + + if ( $handler->get_percent_complete() >= 100 ) { + $errors = $handler->get_notices( 'error' ); + + if ( empty( $errors ) ) { + $handler->add_notice( $handler->get_success_message(), 'success' ); + $handler->update_notices(); + } + + wp_send_json_success( + array( + 'step' => 'done', + 'percentage' => 100, + 'url' => $handler->get_success_redirect_url(), + 'type' => $handler->get_shipment_type(), + ) + ); + } else { + wp_send_json_success( + array( + 'step' => ++$step, + 'percentage' => $handler->get_percent_complete(), + 'ids' => $handler->get_ids(), + 'type' => $handler->get_shipment_type(), + ) + ); + } + } + + /** + * @param Order $order + */ + private static function refresh_shipments( &$order ) { + MetaBox::refresh_shipments( $order ); + } + + /** + * @param Order $order + * @param bool $shipment + */ + private static function refresh_shipment_items( &$order, &$shipment = false ) { + MetaBox::refresh_shipment_items( $order, $shipment ); + } + + /** + * @param Order $order + */ + private static function refresh_status( &$order ) { + MetaBox::refresh_status( $order ); + } + + public static function update_shipment_status() { + if ( current_user_can( 'edit_shop_orders' ) && check_admin_referer( 'update-shipment-status' ) && isset( $_GET['status'], $_GET['shipment_id'] ) ) { + $status = sanitize_text_field( wp_unslash( $_GET['status'] ) ); + $shipment = wc_gzd_get_shipment( absint( wp_unslash( $_GET['shipment_id'] ) ) ); + + if ( wc_gzd_is_shipment_status( 'gzd-' . $status ) && $shipment ) { + $shipment->update_status( $status, true ); + /** + * Action to indicate Shipment status change via WP Admin. + * + * @param integer $shipment_id The shipment id. + * @param string $status The status to be switched to. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_updated_shipment_status', $shipment->get_id(), $status ); + } + } + + wp_safe_redirect( wp_get_referer() ? wp_get_referer() : admin_url( 'admin.php?page=wc-gzd-shipments' ) ); + exit; + } + + private static function get_shipment_ids( $shipments ) { + return array_values( + array_map( + function( $s ) { + return $s->get_id(); + }, + $shipments + ) + ); + } + + public static function remove_shipment() { + check_ajax_referer( 'edit-shipments', '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' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + ); + + $shipment_id = absint( $_POST['shipment_id'] ); + + if ( ! $shipment = wc_gzd_get_shipment( $shipment_id ) ) { + wp_send_json( $response_error ); + } + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $shipment->get_order() ) ) { + wp_send_json( $response_error ); + } + + $shipment_ids = self::get_shipment_ids( $order_shipment->get_shipments() ); + + if ( $shipment->delete( true ) ) { + $order_shipment->remove_shipment( $shipment_id ); + + if ( 'return' === $shipment->get_type() ) { + $order_shipment->validate_shipments(); + } + + /* + * Check which shipments have been deleted (e.g. multiple in case a return has been removed) + */ + $shipments_removed = array_values( array_diff( $shipment_ids, self::get_shipment_ids( $order_shipment->get_shipments() ) ) ); + $response['shipment_id'] = $shipments_removed; + + $response['fragments'] = array( + '.order-shipping-status' => self::get_order_status_html( $order_shipment ), + '.order-return-status' => self::get_order_return_status_html( $order_shipment ), + ); + + self::send_json_success( $response, $order_shipment ); + } else { + wp_send_json( $response_error ); + } + } + + public static function add_shipment() { + 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 while adding the shipment', 'shipments', 'woocommerce-germanized' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + ); + + $order_id = absint( $_POST['order_id'] ); + + 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 ); + } + + self::refresh_shipment_items( $order_shipment ); + + if ( ! $order_shipment->needs_shipping() ) { + $response_error['message'] = _x( 'This order contains enough shipments already.', 'shipments', 'woocommerce-germanized' ); + wp_send_json( $response_error ); + } + + $shipment = wc_gzd_create_shipment( $order_shipment ); + + if ( is_wp_error( $shipment ) ) { + wp_send_json( $response_error ); + } + + $order_shipment->add_shipment( $shipment ); + + // Mark as active + $is_active = true; + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-shipment.php'; + $html = ob_get_clean(); + + $response['new_shipment'] = $html; + $response['new_shipment_type'] = $shipment->get_type(); + $response['fragments'] = array( + '.order-shipping-status' => self::get_order_status_html( $order_shipment ), + '.order-return-status' => self::get_order_return_status_html( $order_shipment ), + ); + + self::send_json_success( $response, $order_shipment ); + } + + public static function add_return_shipment() { + 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' => '', + 'new_shipment' => '', + ); + + $order_id = absint( $_POST['order_id'] ); + $items = isset( $_POST['return_item'] ) ? (array) wc_clean( wp_unslash( $_POST['return_item'] ) ) : array(); + + if ( ! $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) { + wp_send_json( $response_error ); + } + + self::refresh_shipment_items( $order_shipment ); + + if ( ! $order_shipment->needs_return() ) { + $response_error['message'] = _x( 'This order contains enough returns already.', 'shipments', 'woocommerce-germanized' ); + wp_send_json( $response_error ); + } + + $shipment = wc_gzd_create_return_shipment( $order_shipment, array( 'items' => $items ) ); + + if ( is_wp_error( $shipment ) ) { + wp_send_json( $response_error ); + } + + $order_shipment->add_shipment( $shipment ); + + // Mark as active + $is_active = true; + + ob_start(); + include Package::get_path() . '/includes/admin/views/html-order-shipment.php'; + $html = ob_get_clean(); + + $response['new_shipment'] = $html; + $response['new_shipment_type'] = $shipment->get_type(); + $response['fragments'] = array( + '.order-shipping-status' => self::get_order_status_html( $order_shipment ), + '.order-return-status' => self::get_order_return_status_html( $order_shipment ), + ); + + self::send_json_success( $response, $order_shipment ); + } + + public static function validate_shipment_item_quantities() { + 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 ); + } + + static::refresh_shipments( $order_shipment ); + + $order_shipment->validate_shipments(); + + $response['fragments'] = self::get_shipments_html( $order_shipment, $active ); + + self::send_json_success( $response, $order_shipment ); + } + + public static function sync_shipment_items() { + check_ajax_referer( 'edit-shipments', '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' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + ); + + $shipment_id = absint( $_POST['shipment_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 ); + } + + $shipment = $order_shipment->get_shipment( $shipment_id ); + + static::refresh_shipment_items( $order_shipment ); + + if ( $shipment->is_editable() ) { + $shipment = $order_shipment->get_shipment( $shipment_id ); + + // Make sure we are working based on the current instance. + $shipment->set_order_shipment( $order_shipment ); + $shipment->sync_items(); + $shipment->save(); + } + + ob_start(); + foreach ( $shipment->get_items() as $item ) { + include Package::get_path() . '/includes/admin/views/html-order-shipment-item.php'; + } + $html = ob_get_clean(); + + $response['fragments'] = array( + '#shipment-' . $shipment->get_id() . ' .shipment-item-list:first' => '
' . $html . '
', + '#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 json_search_shipping_provider() { + ob_start(); + + check_ajax_referer( 'search-shipping-provider', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) ) { + wp_die( -1 ); + } + + $term = isset( $_GET['term'] ) ? (string) wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + $found_providers = array(); + + if ( empty( $term ) ) { + wp_die(); + } + + global $wpdb; + + $names = $wpdb->get_col( + $wpdb->prepare( + "SELECT DISTINCT p1.shipping_provider_name FROM {$wpdb->gzd_shipping_provider} p1 WHERE p1.shipping_provider_title LIKE %s AND p1.shipping_provider_activated = 1", // @codingStandardsIgnoreLine + $wpdb->esc_like( wc_clean( $term ) ) . '%' + ) + ); + + foreach ( $names as $name ) { + if ( $shipping_provider = wc_gzd_get_shipping_provider( $name ) ) { + $found_providers[ $name ] = esc_html( $shipping_provider->get_title() ); + } + } + + /** + * Filter to adjust found shipping providers to filter Shipments. + * + * @param array $result The shipping provider search result. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + wp_send_json( apply_filters( 'woocommerce_gzd_json_search_found_shipment_shipping_providers', $found_providers ) ); + } + + public static function json_search_orders() { + ob_start(); + + check_ajax_referer( 'search-orders', 'security' ); + + if ( ! current_user_can( 'edit_shop_orders' ) ) { + wp_die( -1 ); + } + + $term = isset( $_GET['term'] ) ? (string) wc_clean( wp_unslash( $_GET['term'] ) ) : ''; + $limit = 0; + + if ( empty( $term ) ) { + wp_die(); + } + + if ( Package::is_hpos_enabled() ) { + $ids = wc_get_orders( array( 's' => $term ) ); + } else { + if ( ! is_numeric( $term ) ) { + $ids = wc_get_orders( array( 's' => $term ) ); + } else { + global $wpdb; + + $ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT DISTINCT p1.ID FROM {$wpdb->posts} p1 WHERE p1.ID LIKE %s AND post_type = 'shop_order'", // @codingStandardsIgnoreLine + $wpdb->esc_like( wc_clean( $term ) ) . '%' + ) + ); + } + } + + $excluded = array(); + + if ( ! empty( $_GET['exclude'] ) ) { + $excluded = array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ); + } + + foreach ( $ids as $id ) { + if ( $order = wc_get_order( $id ) ) { + if ( in_array( absint( $order->get_id() ), $excluded, true ) ) { + continue; + } + + $found_orders[ $order->get_id() ] = sprintf( + esc_html_x( 'Order #%s', 'shipments', 'woocommerce-germanized' ), + $order->get_order_number() + ); + } + } + + /** + * Filter to adjust found orders to filter Shipments. + * + * @param array $result The order search result. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + wp_send_json( apply_filters( 'woocommerce_gzd_json_search_found_shipment_orders', $found_orders ) ); + } + + private static function get_order_status_html( $order_shipment ) { + $status_html = '' . wc_gzd_get_shipment_order_shipping_status_name( $order_shipment->get_shipping_status() ) . ''; + + return $status_html; + } + + /** + * @param \WC_Order $order + * + * @return string[] + */ + private static function get_global_order_status_html( $order ) { + $old_status = $order->get_status(); + $result = array( + 'status' => '', + 'input' => '', + ); + + /** + * Load a clean instance to make sure order status updates are reflected. + */ + $order = wc_get_order( $order->get_id() ); + + /** + * In case the current request has not changed the status do not return html + */ + if ( ! $order || $old_status === $order->get_status() ) { + return $result; + } + ob_start(); + ?> +

+ + +

+ 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 get_available_return_shipment_items() { + 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' => '', + 'html' => '', + ); + + $order_id = absint( $_POST['order_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['html'] = ob_get_clean(); + + self::send_json_success( $response, $order_shipment ); + } + + public static function get_available_shipment_items() { + check_ajax_referer( 'edit-shipments', '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' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + 'items' => array(), + ); + + $shipment_id = absint( $_POST['shipment_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 ); + + if ( 'return' === $shipment->get_type() ) { + $response['items'] = $order_shipment->get_available_items_for_return( + array( + 'shipment_id' => $shipment->get_id(), + 'disable_duplicates' => true, + ) + ); + } else { + $response['items'] = $order_shipment->get_available_items_for_shipment( + array( + 'shipment_id' => $shipment_id, + 'disable_duplicates' => true, + ) + ); + } + + self::send_json_success( $response, $order_shipment ); + } + + public static function add_shipment_item() { + check_ajax_referer( 'edit-shipments', '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' ), + ); + + $response = array( + 'success' => true, + 'message' => '', + 'new_item' => '', + ); + + $shipment_id = absint( $_POST['shipment_id'] ); + $original_item_id = isset( $_POST['original_item_id'] ) ? absint( $_POST['original_item_id'] ) : 0; + $item_quantity = isset( $_POST['quantity'] ) ? absint( $_POST['quantity'] ) : 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..51cc8d7c8 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Api.php @@ -0,0 +1,215 @@ +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['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['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['hs_code'] = array( + 'description' => _x( 'HS-Code (Customs)', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ); + + $schema_properties['manufacture_country'] = array( + 'description' => _x( 'Country of manufacture (Customs)', '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['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..fcca035e8 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Automation.php @@ -0,0 +1,257 @@ +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' ), 70 ); + } else { + self::mark_shipments_shipped( $order_id ); + } + } + } + + private static function is_admin_edit_order_request() { + return ( 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 + } + + 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' ) ); + } + } + + public static function create_shipments( $order, $enable_auto_filter = true ) { + if ( is_numeric( $order ) ) { + $order = wc_get_order( $order ); + } + + if ( ! $order ) { + return; + } + + $shipment_status = Package::get_setting( 'auto_default_status' ); + + if ( empty( $shipment_status ) ) { + $shipment_status = 'processing'; + } + + /** + * 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 ( $enable_auto_filter && ! apply_filters( 'woocommerce_gzd_auto_create_shipments_for_order', true, $order->get_id(), $order ) ) { + return; + } + + if ( $order_shipment = wc_gzd_get_shipment_order( $order ) ) { + if ( ! apply_filters( 'woocommerce_gzd_auto_create_custom_shipments_for_order', false, $order->get_id(), $order ) ) { + $shipments = $order_shipment->get_simple_shipments(); + + foreach ( $shipments as $shipment ) { + if ( $shipment->is_editable() ) { + $shipment->sync(); + $shipment->sync_items(); + $shipment->save(); + } + } + + if ( $order_shipment->needs_shipping() ) { + $shipment = wc_gzd_create_shipment( $order_shipment, array( 'props' => array( 'status' => $shipment_status ) ) ); + + if ( ! is_wp_error( $shipment ) ) { + $order_shipment->add_shipment( $shipment ); + } + } + } + + do_action( 'woocommerce_gzd_after_auto_create_shipments_for_order', $order->get_id(), $shipment_status, $order ); + } + } + + 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 ) { + $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', array( __CLASS__, 'create_shipments' ), 70 ); + } else { + self::create_shipments( $order_id ); + } + } + } + + public static function maybe_create_subscription_shipments( $renewal_order ) { + self::create_shipments( $renewal_order->get_id() ); + + return $renewal_order; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/DataStores/Label.php b/packages/woocommerce-germanized-shipments/src/DataStores/Label.php new file mode 100644 index 000000000..252fcf188 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/DataStores/Label.php @@ -0,0 +1,558 @@ +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..4425c023b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/DataStores/Packaging.php @@ -0,0 +1,694 @@ +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_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, + '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' ); + } + + /* + |-------------------------------------------------------------------------- + | 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 ); + } + + public function get_packaging_list( $args = array() ) { + global $wpdb; + + $all_types = array_keys( wc_gzd_get_packaging_types() ); + + $args = wp_parse_args( + $args, + array( + 'type' => $all_types, + ) + ); + + if ( ! is_array( $args['type'] ) ) { + $args['type'] = array( $args['type'] ); + } + + $types = array_filter( wc_clean( $args['type'] ) ); + $types = empty( $types ) ? $all_types : $types; + + $query = " + SELECT packaging_id FROM {$wpdb->gzd_packaging} + WHERE packaging_type IN ( '" . implode( "','", $types ) . "' ) + ORDER BY packaging_order ASC + "; + + if ( $all_types === $types ) { + // 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' ); + } + } else { + $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + } + + foreach ( $results as $key => $packaging ) { + $results[ $key ] = wc_gzd_get_packaging( $packaging ); + } + + return $results; + } + + /** + * @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; + } + + $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() ), 1 ); + $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; + $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_to_pack = $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_to_pack ) > 0 ) { + $available_packaging_ids = array(); + + if ( Package::is_packing_supported() ) { + $packaging_list = wc_gzd_get_packaging_list(); + $items = \DVDoug\BoxPacker\ItemList::fromArray( $items_to_pack ); + + 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 ); + $org_size = count( $items_to_pack ); + + $packer = new VolumePacker( $box, $items ); + $packed = $packer->pack(); + + if ( count( $packed->getItems() ) === $org_size ) { + $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() ), 1 ); + $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, + (packaging_length - %f) AS length_diff, + (packaging_width - %f) AS width_diff, + (packaging_height - %f) AS height_diff, + ((packaging_length - %f) + (packaging_width - %f) + (packaging_height - %f)) AS total_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 total_diff ASC, packaging_weight ASC, packaging_order ASC + "; + + $query = $wpdb->prepare( $query_sql, $length, $width, $height, $length, $width, $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 ) { + $available_packaging_ids[] = $result->packaging_id; + $packaging_available[] = wc_gzd_get_packaging( $result->packaging_id ); + } + } + } + + wp_cache_set( 'available-packaging-' . $shipment->get_id(), $available_packaging_ids, 'shipments' ); + } elseif ( count( $items_to_pack ) <= 0 ) { + $packaging_available = wc_gzd_get_packaging_list(); + } else { + foreach ( (array) $results as $packaging_id ) { + $packaging_available[] = wc_gzd_get_packaging( $packaging_id ); + } + } + + $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..bc1debefe --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/DataStores/ShipmentItem.php @@ -0,0 +1,354 @@ +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..a0c772f1c --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Emails.php @@ -0,0 +1,232 @@ +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 ( 'return' === $shipment->get_type() || in_array( $email, array( 'customer_return_shipment_delivered' ), true ) ) { + if ( $provider = $shipment->get_shipping_provider_instance() ) { + if ( $provider->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..f8207387f --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Install.php @@ -0,0 +1,361 @@ +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' => 22.9, + 'width' => 32.4, + '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, + 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/ShipmentLabel.php b/packages/woocommerce-germanized-shipments/src/Interfaces/ShipmentLabel.php new file mode 100644 index 000000000..d1512fcf2 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Interfaces/ShipmentLabel.php @@ -0,0 +1,99 @@ +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::do_automation( $shipment, false ); + } + + /** + * @param Shipment $shipment + * @param boolean $is_hook + */ + protected static function do_automation( $shipment, $is_hook = true ) { + $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 ( $is_hook ) { + add_action( $hook_prefix . $status, array( __CLASS__, 'maybe_create_label' ), 5, 2 ); + } elseif ( $shipment->has_status( $status ) ) { + self::maybe_create_label( $shipment->get_id(), $shipment ); + } + } + } + } + + /** + * @param $shipment_id + * @param Shipment $shipment + */ + public static function set_automation( $shipment_id, $shipment ) { + self::do_automation( $shipment, true ); + } + + 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 ) ) ); + } + } + } + } + + private static function is_admin_edit_order_request() { + return ( isset( $_POST['action'] ) && 'editpost' === $_POST['action'] && isset( $_POST['post_type'] ) && 'shop_order' === $_POST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing + } + + public static function maybe_create_label( $shipment_id, $shipment = false ) { + // Make sure that MetaBox is saved before we process automation + if ( self::is_admin_edit_order_request() && ! did_action( 'woocommerce_process_shop_order_meta' ) ) { + add_action( 'woocommerce_process_shop_order_meta', array( __CLASS__, 'create_label' ), 70 ); + } else { + self::create_label( $shipment_id, $shipment ); + } + } +} 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..96ab1dfb6 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/Label.php @@ -0,0 +1,861 @@ + null, + 'shipment_id' => 0, + 'product_id' => '', + 'parent_id' => 0, + 'number' => '', + 'shipping_provider' => '', + 'weight' => '', + 'net_weight' => '', + 'length' => '', + 'width' => '', + 'height' => '', + 'path' => '', + 'created_via' => '', + 'services' => array(), + ); + + 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 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 ) ); + } + + 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_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( _x( 'Error while downloading the PDF file.', 'shipments', 'woocommerce-germanized' ) ); + } + } catch ( \Exception $e ) { + 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_order_supports_parcel_delivery_reminder( $order ); + } + + 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 ); + $item_count = count( $item_weights ); + + /** + * Discrepancies detected between item weights an 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 ) { + $per_item_diff = $diff / $item_count; + // Round down to int + $per_item_diff_rounded = $this->round_customs_item_weight( $per_item_diff ); + $diff_applied = 0; + + if ( abs( $per_item_diff_rounded ) > 0 ) { + foreach ( $item_weights as $key => $weight ) { + $shipment_item = $shipment_items[ $key ]; + $item_min_weight = 1 * $shipment_item->get_quantity(); + + $item_weight_before = $item_weights[ $key ]; + $new_item_weight = $item_weights[ $key ] += $per_item_diff_rounded; + $item_diff_applied = $per_item_diff_rounded; + + /** + * In case the diff is negative make sure we are not + * subtracting more than available as min weight per item. + */ + if ( $new_item_weight <= $item_min_weight ) { + $new_item_weight = $item_min_weight; + $item_diff_applied = $item_min_weight - $item_weight_before; + } + + $item_weights[ $key ] = $new_item_weight; + $diff_applied += $item_diff_applied; + } + } + + // Check rounding diff and apply the diff to one item + $diff_left = $diff - $diff_applied; + + if ( abs( $diff_left ) > 0 ) { + foreach ( $item_weights as $key => $weight ) { + $shipment_item = $shipment_items[ $key ]; + $item_min_weight = 1 * $shipment_item->get_quantity(); + + if ( $diff_left > 0 ) { + /** + * Add the diff left to the first item and stop. + */ + $item_weights[ $key ] += $diff_left; + break; + } else { + /** + * Remove the diff left from the first item with a weight greater than 0.01 to prevent 0 weights. + */ + if ( $weight > $item_min_weight ) { + $item_weights[ $key ] += $diff_left; + break; + } + } + } + } + } + } + + 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 = $this->round_customs_item_weight( wc_add_number_precision( $this->get_net_weight() ) ); + $total_gross_weight = $this->round_customs_item_weight( wc_add_number_precision( $this->get_weight() ) ); + $item_weights = array(); + $shipment_items = $shipment->get_items(); + $order = $shipment->get_order(); + + foreach ( $shipment_items as $key => $item ) { + $per_item_weight = wc_format_decimal( floatval( wc_get_weight( $item->get_weight(), 'kg', $shipment->get_weight_unit() ) ), 2 ); + $per_item_weight = wc_add_number_precision( $per_item_weight ); + $per_item_weight = $per_item_weight * $item->get_quantity(); + $per_item_min_weight = 1 * $item->get_quantity(); + + /** + * Set min weight to 0.01 to prevent missing weight error messages + * for really small product weights. + */ + 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 ) { + $item_description .= ! empty( $item_description ) ? ', ' : ''; + $item_description .= $item->get_name(); + + // Use total before discounts for customs + $product_total = floatval( (float) ( $use_subtotal ? $item->get_subtotal() : $item->get_total() ) / $item->get_quantity() ); + $dhl_product = false; + $product = $item->get_product(); + + if ( $product ) { + $shipment_product = wc_gzd_shipments_get_product( $product ); + } + + 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 = floatval( (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( $item->get_name(), 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' => wc_remove_number_precision( $item_weights[ $key ] ), + 'single_weight_in_kg' => $this->round_customs_item_weight( wc_remove_number_precision( $item_weights[ $key ] / $item->get_quantity() ), 2 ), + 'weight_in_kg_raw' => $item_weights[ $key ], + 'gross_weight_in_kg' => wc_remove_number_precision( $item_gross_weights[ $key ] ), + 'single_gross_weight_in_kg' => $this->round_customs_item_weight( wc_remove_number_precision( $item_gross_weights[ $key ] / $item->get_quantity() ), 2 ), + 'gross_weight_in_kg_raw' => $item_gross_weights[ $key ], + 'single_value' => $product_value, + 'value' => wc_format_decimal( $product_value * $item->get_quantity(), 2 ), + ), + $item, + $shipment, + $this + ); + + $total_weight += (float) $customs_items[ $key ]['weight_in_kg']; + $total_gross_weight += (float) $customs_items[ $key ]['gross_weight_in_kg']; + $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 ), + 'export_type_description' => $item_description, + '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' => $total_weight, + 'item_total_gross_weight_in_kg' => $total_gross_weight, + 'item_total_value' => $total_value, + 'currency' => $order ? $order->get_currency() : get_woocommerce_currency(), + 'invoice_number' => '', + ), + $this, + $shipment + ); + + return $customs_data; + } +} 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..53b3494a7 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Labels/ReturnLabel.php @@ -0,0 +1,119 @@ + 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; + } + + 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' ) { + return $this->get_sender_address_prop( 'street', $context ); + } + + public function get_sender_street_number( $context = 'view' ) { + return $this->get_sender_address_prop( 'street_number', $context ); + } + + public function get_sender_street_addition( $context = 'view' ) { + return $this->get_sender_address_prop( 'street_addition', $context ); + } + + public function get_sender_company( $context = 'view' ) { + return $this->get_sender_address_prop( 'company', $context ); + } + + public function get_sender_name( $context = 'view' ) { + return $this->get_sender_address_prop( 'name', $context ); + } + + public function get_sender_formatted_full_name() { + return sprintf( _x( '%1$s', 'shipments full name', 'woocommerce-germanized' ), $this->get_sender_name() ); // phpcs:ignore WordPress.WP.I18n.NoEmptyStrings + } + + 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..5ab084465 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Order.php @@ -0,0 +1,1009 @@ +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_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 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 ); + } + + 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 ) { + $shipments = $this->get_shipments(); + + $this->shipments[] = $shipment; + } + + public function remove_shipment( $shipment_id ) { + $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 ( $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; + } + + /** + * @param ShipmentItem $item + */ + public function get_item_quantity_left_for_returning( $order_item_id, $args = array() ) { + $quantity_left = 0; + $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 ); + + 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 $group_by_shipping_class + * + * @return OrderItem[] + */ + public function get_items_to_pack_left_for_shipping( $group_by_shipping_class = false ) { + $items = $this->get_available_items_for_shipment(); + $items_to_be_packed = array(); + + foreach ( $items as $order_item_id => $item ) { + if ( ! $order_item = $this->get_order()->get_item( $order_item_id ) ) { + continue; + } + + $shipping_class = ''; + + if ( $group_by_shipping_class ) { + if ( $product = $order_item->get_product() ) { + $shipping_class = $product->get_shipping_class(); + } + } + + for ( $i = 0; $i < $item['max_quantity']; $i++ ) { + try { + $box_item = new Packing\OrderItem( $order_item ); + + if ( ! isset( $items_to_be_packed[ $shipping_class ] ) ) { + $items_to_be_packed[ $shipping_class ] = array(); + } + + $items_to_be_packed[ $shipping_class ][] = $box_item; + } catch ( \Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } + } + } + + return apply_filters( 'woocommerce_gzd_shipment_order_items_to_pack_left_for_shipping', $items_to_be_packed, $group_by_shipping_class ); + } + + /** + * @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; + } + + /** + * @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 ( ! 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; + } + + /** + * 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(); + } + } + + /** + * 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..4a241f352 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Package.php @@ -0,0 +1,740 @@ +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 .= '
' . $notices . '
'; + } + + $html .= wc_get_template_html( 'global/form-return-request.php', $args ); + + return $html; + } + + public static function load_wpml_compatibility( $compatibility ) { + WPMLHelper::init( $compatibility ); + } + + public static function get_excluded_methods() { + return apply_filters( 'woocommerce_gzd_shipments_get_methods_excluded_from_provider_settings', array( 'pr_dhl_paket', 'flexible_shipping_info' ) ); + } + + public static function set_method_filters( $methods ) { + foreach ( $methods as $method => $class ) { + if ( in_array( $method, self::get_excluded_methods(), true ) ) { + continue; + } + + add_filter( 'woocommerce_shipping_instance_form_fields_' . $method, array( __CLASS__, 'add_method_settings' ), 10, 1 ); + /** + * 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 ); + add_filter( 'woocommerce_shipping_' . $method . '_instance_settings_values', array( __CLASS__, 'filter_method_settings' ), 10, 2 ); + } + + return $methods; + } + + /** + * Indicates whether the BoxPack library for improved packing calculation is supported + * + * @return bool + */ + public static function is_packing_supported() { + return version_compare( phpversion(), '7.1', '>=' ) && apply_filters( 'woocommerce_gzd_enable_rucksack_packaging', true ); + } + + public static function get_method_settings() { + if ( is_null( self::$method_settings ) ) { + self::$method_settings = Method::get_admin_settings(); + } + + return self::$method_settings; + } + + public static function filter_method_settings( $p_settings, $method ) { + $shipping_provider_settings = self::get_method_settings(); + $shipping_provider = isset( $p_settings['shipping_provider'] ) ? $p_settings['shipping_provider'] : ''; + $shipping_method = wc_gzd_get_shipping_provider_method( $method ); + + /** + * Make sure the (maybe) new selected provider is used on updating the settings. + */ + $shipping_method->set_provider( $shipping_provider ); + + foreach ( $p_settings as $setting => $value ) { + if ( array_key_exists( $setting, $shipping_provider_settings ) ) { + // Check if setting does neither belong to global setting nor shipping provider prefix + if ( 'shipping_provider' !== $setting && ! $shipping_method->setting_belongs_to_provider( $setting ) ) { + unset( $p_settings[ $setting ] ); + } elseif ( $shipping_method->get_fallback_setting_value( $setting ) === $value ) { + unset( $p_settings[ $setting ] ); + } elseif ( '' === $value ) { + unset( $p_settings[ $setting ] ); + } + } + } + + /** + * Filter that returns shipping method settings cleaned from global shipping provider method settings. + * This filter might be useful to remove some default setting values from + * shipping provider method settings e.g. DHL settings. + * + * @param array $p_settings The settings + * @param WC_Shipping_Method $method The shipping method instance + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipping_provider_method_clean_settings', $p_settings, $method ); + } + + 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 ); + } + + public static function load_shipping_methods( $package ) { + $shipping = WC_Shipping::instance(); + + foreach ( $shipping->shipping_methods as $key => $method ) { + if ( in_array( $key, self::get_excluded_methods(), true ) ) { + continue; + } + + $shipping_provider_method = new Method( $method ); + } + } + + public static function is_hpos_enabled() { + if ( ! is_callable( array( '\Automattic\WooCommerce\Utilities\OrderUtil', 'custom_orders_table_usage_is_enabled' ) ) ) { + return false; + } + + return \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); + } + + public static function inject_endpoints() { + if ( function_exists( 'WC' ) && WC()->query ) { + foreach ( self::get_endpoints() as $endpoint ) { + if ( ! array_key_exists( $endpoint, WC()->query->query_vars ) ) { + $option_name = str_replace( '-', '_', $endpoint ); + WC()->query->query_vars[ $endpoint ] = get_option( "woocommerce_gzd_shipments_{$option_name}_endpoint", $endpoint ); + } + } + } + } + + public static function get_base_country() { + $default_country = wc_get_base_location()['country']; + $shipment_country = wc_format_country_state_string( self::get_setting( 'shipper_address_country' ) )['country']; + + if ( empty( $shipment_country ) ) { + $shipment_country = $default_country; + } + + return apply_filters( 'woocommerce_gzd_shipment_base_country', $shipment_country ); + } + + public static function get_base_postcode() { + $default_postcode = WC()->countries->get_base_postcode(); + $shipment_postcode = self::get_setting( 'shipper_address_postcode' ); + + if ( empty( $shipment_postcode ) ) { + $shipment_postcode = $default_postcode; + } + + return apply_filters( 'woocommerce_gzd_shipment_base_postcode', $shipment_postcode ); + } + + public static function base_country_belongs_to_eu_customs_area() { + return self::country_belongs_to_eu_customs_area( self::get_base_country(), self::get_base_postcode() ); + } + + public static function country_belongs_to_eu_customs_area( $country, $postcode = '' ) { + $country = wc_strtoupper( $country ); + $eu_countries = WC()->countries->get_european_union_countries(); + $belongs = false; + $postcode = wc_normalize_postcode( $postcode ); + $postcode_wildcards = wc_get_wildcard_postcodes( $postcode, $country ); + + if ( in_array( $country, $eu_countries, true ) ) { + $belongs = true; + } + + if ( $belongs ) { + $exemptions = array( + 'DE' => array( + '27498', // Helgoland + '78266', // Büsingen am Hochrhein + ), + 'ES' => array( + '35*', // Canary Islands + '38*', // Canary Islands + '51*', // Ceuta + '52*', // Melilla + ), + 'GR' => array( + '63086', // Mount Athos + '63087', // Mount Athos + ), + 'IT' => array( + '22060', // Livigno, Campione d’Italia + '23030', // Lake Lugano + ), + 'FI' => array( + 'AX*', // Åland Islands + ), + 'CY' => array( + '9*', // Northern Cyprus + '5*', // Northern Cyprus + ), + ); + + if ( array_key_exists( $country, $exemptions ) ) { + foreach ( $exemptions[ $country ] as $exempt_postcode ) { + if ( in_array( $exempt_postcode, $postcode_wildcards, true ) ) { + $belongs = false; + break; + } + } + } + } + + return apply_filters( 'woocommerce_gzd_country_belongs_to_eu_customs_area', $belongs, $country, $postcode ); + } + + public static function is_shipping_international( $country, $args = array() ) { + $args = self::parse_location_data( $args ); + /** + * In case the sender country belongs to EU customs area, a third country needs to lie outside of the EU customs area + */ + if ( self::country_belongs_to_eu_customs_area( $args['sender_country'], $args['sender_postcode'] ) ) { + if ( ! self::country_belongs_to_eu_customs_area( $country, $args['postcode'] ) ) { + return true; + } + + return false; + } else { + if ( ! self::is_shipping_domestic( $country, $args ) ) { + return true; + } + + return false; + } + } + + public static function is_shipping_domestic( $country, $args = array() ) { + $args = self::parse_location_data( $args ); + $is_domestic = $country === $args['sender_country']; + + /** + * If the sender country belongs to EU customs area but the postcode (e.g. Helgoland in DE) not, do not consider domestic shipping + */ + if ( $is_domestic && self::country_belongs_to_eu_customs_area( $args['sender_country'], $args['sender_postcode'] ) ) { + if ( ! self::country_belongs_to_eu_customs_area( $country, $args['postcode'] ) ) { + $is_domestic = false; + } + } + + return $is_domestic; + } + + private static function parse_location_data( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'postcode' => '', + 'sender_country' => self::get_base_country(), + 'sender_postcode' => self::get_base_postcode(), + ) + ); + + return $args; + } + + /** + * Whether shipping is inner EU (from one EU country to another) shipment or not. + * + * @param $country + * @param array $args + * + * @return mixed|void + */ + public static function is_shipping_inner_eu_country( $country, $args = array() ) { + $args = self::parse_location_data( $args ); + + if ( self::is_shipping_domestic( $country, $args ) || ! self::country_belongs_to_eu_customs_area( $args['sender_country'], $args['sender_postcode'] ) ) { + return false; + } + + return self::country_belongs_to_eu_customs_area( $country, $args['postcode'] ); + } + + public static function get_endpoints() { + return array( + 'view-shipment', + 'add-return-shipment', + 'view-shipments', + ); + } + + public static function register_endpoints( $query_vars ) { + foreach ( self::get_endpoints() as $endpoint ) { + if ( ! array_key_exists( $endpoint, $query_vars ) ) { + $option_name = str_replace( '-', '_', $endpoint ); + $query_vars[ $endpoint ] = get_option( "woocommerce_gzd_shipments_{$option_name}_endpoint", $endpoint ); + } + } + + return $query_vars; + } + + public static function install() { + self::init(); + Install::install(); + } + + public static function install_integration() { + self::init(); + self::install(); + } + + public static function maybe_set_upload_dir() { + // Create a dir suffix + if ( ! get_option( 'woocommerce_gzd_shipments_upload_dir_suffix', false ) ) { + self::$upload_dir_suffix = substr( self::generate_key(), 0, 10 ); + update_option( 'woocommerce_gzd_shipments_upload_dir_suffix', self::$upload_dir_suffix ); + } else { + self::$upload_dir_suffix = get_option( 'woocommerce_gzd_shipments_upload_dir_suffix' ); + } + } + + public static function is_feature_plugin() { + return defined( 'WC_GZD_SHIPMENTS_IS_FEATURE_PLUGIN' ) && WC_GZD_SHIPMENTS_IS_FEATURE_PLUGIN; + } + + public static function check_version() { + if ( self::is_feature_plugin() && self::has_dependencies() && ! defined( 'IFRAME_REQUEST' ) && ( get_option( 'woocommerce_gzd_shipments_version' ) !== self::get_version() ) ) { + Install::install(); + + do_action( 'woocommerce_gzd_shipments_updated' ); + } + } + + /** + * Generate a unique key. + * + * @return string + */ + protected static function generate_key() { + $key = array( ABSPATH, time() ); + $constants = array( 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY', 'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT', 'SECRET_KEY' ); + + foreach ( $constants as $constant ) { + if ( defined( $constant ) ) { + $key[] = constant( $constant ); + } + } + + shuffle( $key ); + + return md5( serialize( $key ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize + } + + public static function log( $message, $type = 'info' ) { + $enable_logging = defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false; + + /** + * Filter that allows adjusting whether to enable or disable + * logging for the shipments package + * + * @param boolean $enable_logging True if logging should be enabled. False otherwise. + * + * @package Vendidero/Germanized/Shipments + */ + if ( ! apply_filters( 'woocommerce_gzd_shipments_enable_logging', $enable_logging ) ) { + return false; + } + + $logger = wc_get_logger(); + + if ( ! $logger ) { + return false; + } + + if ( ! is_callable( array( $logger, $type ) ) ) { + $type = 'info'; + } + + $logger->{$type}( $message, array( 'source' => 'wc-gzd-shipments' ) ); + } + + public static function get_upload_dir_suffix() { + return self::$upload_dir_suffix; + } + + public static function get_upload_dir() { + self::set_upload_dir_filter(); + $upload_dir = wp_upload_dir(); + self::unset_upload_dir_filter(); + + /** + * Filter to adjust the upload directory used to store shipment related files. By default + * files are stored in a custom directory under wp-content/uploads. + * + * @param array $upload_dir Array containing `wp_upload_dir` data. + * + * @since 3.0.1 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipments_upload_dir', $upload_dir ); + } + + public static function get_relative_upload_dir( $path ) { + self::set_upload_dir_filter(); + $path = _wp_relative_upload_path( $path ); + self::unset_upload_dir_filter(); + + /** + * Filter to retrieve the relative upload path used for storing shipment related files. + * + * @param array $path Relative path. + * + * @since 3.0.1 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipments_relative_upload_dir', $path ); + } + + public static function set_upload_dir_filter() { + add_filter( 'upload_dir', array( __CLASS__, 'filter_upload_dir' ), 150, 1 ); + } + + public static function unset_upload_dir_filter() { + remove_filter( 'upload_dir', array( __CLASS__, 'filter_upload_dir' ), 150 ); + } + + public static function get_file_by_path( $file ) { + // If the file is relative, prepend upload dir. + if ( $file && 0 !== strpos( $file, '/' ) && ( ( $uploads = self::get_upload_dir() ) && false === $uploads['error'] ) ) { + $file = $uploads['basedir'] . "/$file"; + + return $file; + } else { + return $file; + } + } + + public static function filter_upload_dir( $args ) { + $upload_base = trailingslashit( $args['basedir'] ); + $upload_url = trailingslashit( $args['baseurl'] ); + + /** + * Filter to adjust the upload path used to store shipment related files. By default + * files are stored in a custom directory under wp-content/uploads. + * + * @param string $path Path to the upload directory. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + $args['basedir'] = apply_filters( 'woocommerce_gzd_shipments_upload_path', $upload_base . 'wc-gzd-shipments-' . self::get_upload_dir_suffix() ); + /** + * Filter to adjust the upload URL used to retrieve shipment related files. By default + * files are stored in a custom directory under wp-content/uploads. + * + * @param string $url URL to the upload directory. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + $args['baseurl'] = apply_filters( 'woocommerce_gzd_shipments_upload_url', $upload_url . 'wc-gzd-shipments-' . self::get_upload_dir_suffix() ); + + $args['path'] = $args['basedir'] . $args['subdir']; + $args['url'] = $args['baseurl'] . $args['subdir']; + + return $args; + } + + public static function has_dependencies() { + return class_exists( 'WooCommerce' ) && apply_filters( 'woocommerce_gzd_shipments_enabled', true ); + } + + private static function includes() { + if ( is_admin() ) { + Admin\Admin::init(); + } + + Ajax::init(); + Automation::init(); + Labels\Automation::init(); + Labels\DownloadHandler::init(); + Emails::init(); + Validation::init(); + Api::init(); + FormHandler::init(); + ReportHelper::init(); + + if ( self::is_frontend_request() ) { + include_once self::get_path() . '/includes/wc-gzd-shipment-template-hooks.php'; + } + + include_once self::get_path() . '/includes/wc-gzd-shipment-functions.php'; + include_once self::get_path() . '/includes/wc-gzd-label-functions.php'; + include_once self::get_path() . '/includes/wc-gzd-packaging-functions.php'; + } + + private static function is_frontend_request() { + return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' ); + } + + /** + * Function used to Init WooCommerce Template Functions - This makes them pluggable by plugins and themes. + */ + public static function include_template_functions() { + include_once self::get_path() . '/includes/wc-gzd-shipments-template-functions.php'; + } + + public static function filter_templates( $path, $template_name ) { + + if ( file_exists( self::get_path() . '/templates/' . $template_name ) ) { + $path = self::get_path() . '/templates/' . $template_name; + } + + return $path; + } + + /** + * Register custom tables within $wpdb object. + */ + private static function define_tables() { + global $wpdb; + + // List of tables without prefixes. + $tables = array( + 'gzd_shipment_itemmeta' => 'woocommerce_gzd_shipment_itemmeta', + 'gzd_shipmentmeta' => 'woocommerce_gzd_shipmentmeta', + 'gzd_shipments' => 'woocommerce_gzd_shipments', + 'gzd_shipment_labelmeta' => 'woocommerce_gzd_shipment_labelmeta', + 'gzd_shipment_labels' => 'woocommerce_gzd_shipment_labels', + 'gzd_shipment_items' => 'woocommerce_gzd_shipment_items', + 'gzd_shipping_provider' => 'woocommerce_gzd_shipping_provider', + 'gzd_shipping_providermeta' => 'woocommerce_gzd_shipping_providermeta', + 'gzd_packaging' => 'woocommerce_gzd_packaging', + 'gzd_packagingmeta' => 'woocommerce_gzd_packagingmeta', + ); + + foreach ( $tables as $name => $table ) { + $wpdb->$name = $wpdb->prefix . $table; + $wpdb->tables[] = $table; + } + } + + public static function register_data_stores( $stores ) { + $stores['shipment'] = 'Vendidero\Germanized\Shipments\DataStores\Shipment'; + $stores['shipment-label'] = 'Vendidero\Germanized\Shipments\DataStores\Label'; + $stores['packaging'] = 'Vendidero\Germanized\Shipments\DataStores\Packaging'; + $stores['shipment-item'] = 'Vendidero\Germanized\Shipments\DataStores\ShipmentItem'; + $stores['shipping-provider'] = 'Vendidero\Germanized\Shipments\DataStores\ShippingProvider'; + + do_action( 'woocommerce_gzd_shipments_registered_data_stores' ); + + return $stores; + } + + /** + * Return the version of the package. + * + * @return string + */ + public static function get_version() { + return self::VERSION; + } + + /** + * Return the path to the package. + * + * @return string + */ + public static function get_path() { + return dirname( __DIR__ ); + } + + /** + * Return the path to the package. + * + * @return string + */ + public static function get_url() { + return plugins_url( '', __DIR__ ); + } + + public static function get_assets_url() { + return self::get_url() . '/assets'; + } + + public static function get_setting( $name, $default = false ) { + $option_name = "woocommerce_gzd_shipments_{$name}"; + + return get_option( $option_name, $default ); + } + + public static function get_store_address_country() { + $default = get_option( 'woocommerce_store_country' ); + + return $default; + } + + public static function get_store_address_street() { + $store_address = wc_gzd_split_shipment_street( get_option( 'woocommerce_store_address' ) ); + + return $store_address['street']; + } + + public static function get_store_address_street_number() { + $store_address = wc_gzd_split_shipment_street( get_option( 'woocommerce_store_address' ) ); + + return $store_address['number']; + } + + public static function 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; + } + + public static function is_valid_mysql_date( $mysql_date ) { + return ( '0000-00-00 00:00:00' === $mysql_date || null === $mysql_date ) ? false : true; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packaging.php b/packages/woocommerce-germanized-shipments/src/Packaging.php new file mode 100644 index 000000000..232881501 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packaging.php @@ -0,0 +1,339 @@ + null, + 'weight' => 0, + 'max_content_weight' => 0, + 'width' => 0, + 'height' => 0, + 'length' => 0, + 'order' => 0, + 'type' => '', + 'description' => '', + ); + + /** + * Get the packaging if ID is passed, otherwise the packaging is new and empty. + * This class should NOT be instantiated, but the `wc_gzd_get_packaging` function should be used. + * + * @param int|object|Packaging $packaging packaging to read. + */ + public function __construct( $data = 0 ) { + parent::__construct( $data ); + + if ( $data instanceof Packaging ) { + $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 ); + } + } + + /** + * Merge changes with data and clear. + * Overrides WC_Data::apply_changes. + * + * @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. + * + * @return string + */ + protected function get_hook_prefix() { + return $this->get_general_hook_prefix() . 'get_'; + } + + /** + * Prefix for action and filter hooks on data. + * + * @return string + */ + protected function get_general_hook_prefix() { + return 'woocommerce_gzd_packaging_'; + } + + /** + * Return the date this packaging was created. + * + * @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 ); + } + + /** + * Returns the packaging weight in kg. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_weight( $context = 'view' ) { + return $this->get_prop( 'weight', $context ); + } + + /** + * Returns the packaging max content weight in kg. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_max_content_weight( $context = 'view' ) { + return $this->get_prop( 'max_content_weight', $context ); + } + + /** + * Returns the packaging order within its list. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_order( $context = 'view' ) { + return $this->get_prop( 'order', $context ); + } + + /** + * Returns the packaging type e.g. box or letter. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_type( $context = 'view' ) { + return $this->get_prop( 'type', $context ); + } + + /** + * Returns the packaging description. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_description( $context = 'view' ) { + return $this->get_prop( 'description', $context ); + } + + /** + * Returns the packaging length in cm. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_length( $context = 'view' ) { + return $this->get_prop( 'length', $context ); + } + + /** + * Returns the packaging width in cm. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_width( $context = 'view' ) { + return $this->get_prop( 'width', $context ); + } + + /** + * Returns the packaging height in cm. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_height( $context = 'view' ) { + return $this->get_prop( '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 ) ); + } + + /** + * Returns dimensions. + * + * @return string|array + */ + public function get_dimensions() { + return array( + 'length' => wc_format_decimal( $this->get_length(), false, true ), + 'width' => wc_format_decimal( $this->get_width(), false, true ), + 'height' => wc_format_decimal( $this->get_height(), false, true ), + ); + } + + public function get_formatted_dimensions() { + return wc_gzd_format_shipment_dimensions( $this->get_dimensions(), wc_gzd_get_packaging_dimension_unit() ); + } + + public function get_volume() { + return (float) $this->get_length() * (float) $this->get_width() * (float) $this->get_height(); + } + + /** + * Set the date this packaging was created. + * + * @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 ); + } + + /** + * Set packaging weight in kg. + * + * @param string $weight The weight. + */ + public function set_weight( $weight ) { + $this->set_prop( 'weight', empty( $weight ) ? 0 : wc_format_decimal( $weight, 2, true ) ); + } + + public function get_title() { + $description = $this->get_description(); + + return sprintf( + _x( '%1$s (%2$s, %3$s)', 'shipments-packaging-title', 'woocommerce-germanized' ), + $description, + $this->get_formatted_dimensions(), + wc_gzd_format_shipment_weight( wc_format_decimal( $this->get_weight(), false, true ), wc_gzd_get_packaging_weight_unit() ) + ); + } + + /** + * Set packaging order. + * + * @param integer $order The order. + */ + public function set_order( $order ) { + $this->set_prop( 'order', absint( $order ) ); + } + + /** + * Set packaging max content weight in kg. + * + * @param string $weight The weight. + */ + public function set_max_content_weight( $weight ) { + $this->set_prop( 'max_content_weight', empty( $weight ) ? 0 : wc_format_decimal( $weight, 2, true ) ); + } + + /** + * Set packaging type + * + * @param string $type The type. + */ + public function set_type( $type ) { + $this->set_prop( 'type', $type ); + } + + /** + * Set packaging description + * + * @param string $description The description. + */ + public function set_description( $description ) { + $this->set_prop( 'description', $description ); + } + + /** + * Set packaging width in cm. + * + * @param string $width The width. + */ + public function set_width( $width ) { + $this->set_prop( 'width', empty( $width ) ? 0 : wc_format_decimal( $width, 1, true ) ); + } + + /** + * Set packaging length in cm. + * + * @param string $length The length. + */ + public function set_length( $length ) { + $this->set_prop( 'length', empty( $length ) ? 0 : wc_format_decimal( $length, 1, true ) ); + } + + /** + * Set packaging height in cm. + * + * @param string $height The height. + */ + public function set_height( $height ) { + $this->set_prop( 'height', empty( $height ) ? 0 : wc_format_decimal( $height, 1, true ) ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packaging/AsyncReportGenerator.php b/packages/woocommerce-germanized-shipments/src/Packaging/AsyncReportGenerator.php new file mode 100644 index 000000000..2fe1db778 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packaging/AsyncReportGenerator.php @@ -0,0 +1,195 @@ +type = $type; + $default_end = new \WC_DateTime(); + $default_start = new \WC_DateTime( 'now' ); + $default_start->modify( '-1 year' ); + + $args = wp_parse_args( + $args, + array( + 'start' => $default_start->format( 'Y-m-d' ), + 'end' => $default_end->format( 'Y-m-d' ), + 'limit' => ReportQueue::get_batch_size(), + 'status' => ReportQueue::get_shipment_statuses(), + 'offset' => 0, + 'type' => apply_filters( 'woocommerce_gzd_shipments_packaging_report_default_shipment_types', array( 'simple' ) ), + 'processed' => 0, + ) + ); + + foreach ( array( 'start', 'end' ) as $date_field ) { + if ( is_a( $args[ $date_field ], 'WC_DateTime' ) ) { + $args[ $date_field ] = $args[ $date_field ]->format( 'Y-m-d' ); + } elseif ( is_numeric( $args[ $date_field ] ) ) { + $date = new \WC_DateTime( '@' . $args[ $date_field ] ); + $args[ $date_field ] = $date->format( 'Y-m-d' ); + } + } + + $this->args = $args; + } + + public function get_type() { + return $this->type; + } + + public function get_args() { + return $this->args; + } + + public function get_id() { + return ReportHelper::get_report_id( + array( + 'type' => $this->type, + 'date_start' => $this->args['start'], + 'date_end' => $this->args['end'], + ) + ); + } + + public function delete() { + $report = new Report( $this->get_id() ); + $report->delete(); + + delete_option( $this->get_id() . '_tmp_result' ); + } + + public function start() { + $report = new Report( $this->get_id() ); + $report->reset(); + $report->save(); + + return $report; + } + + /** + * @return true|\WP_Error + */ + public function next() { + $args = $this->args; + $shipments = ReportQueue::query( $args ); + $shipments_processed = 0; + $packaging_data = $this->get_temporary_result(); + + Package::log( sprintf( '%d applicable shipments found', count( $shipments ) ) ); + + if ( ! empty( $shipments ) ) { + foreach ( $shipments as $shipment ) { + $packaging_weight = $shipment->get_packaging_weight(); + $packaging = $shipment->get_packaging(); + $country = $shipment->get_country(); + $packaging_id = 'other'; + + if ( ! $shipment->get_packaging_weight() ) { + Package::log( sprintf( 'Skipping shipment #%1$s due to missing packaging weight.', $shipment->get_id() ) ); + continue; + } + + if ( $packaging ) { + $packaging_id = $packaging->get_id(); + } + + if ( ! isset( $packaging_data[ $country ][ "$packaging_id" ] ) ) { + $packaging_data[ $country ][ "$packaging_id" ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + ); + } + + $packaging_data[ $country ][ "$packaging_id" ]['count'] += 1; + $packaging_data[ $country ][ "$packaging_id" ]['weight_in_kg'] += wc_add_number_precision( (float) wc_get_weight( $packaging_weight, 'kg', $shipment->get_weight_unit() ), false ); + + $shipments_processed++; + } + + $this->args['processed'] = absint( $this->args['processed'] ) + $shipments_processed; + + update_option( $this->get_id() . '_tmp_result', $packaging_data, false ); + + return true; + } else { + return new \WP_Error( 'empty', _x( 'No shipments found.', 'shipments', 'woocommerce-germanized' ) ); + } + } + + /** + * @return Report + */ + public function complete() { + Package::log( sprintf( 'Completed called' ) ); + + $tmp_result = $this->get_temporary_result(); + $report = new Report( $this->get_id() ); + $count_total = 0; + $weight_total = 0.0; + $packaging_totals = array(); + + foreach ( $tmp_result as $country => $packaging_ids ) { + $country_count_total = 0; + $country_weight_total = 0.0; + + foreach ( $packaging_ids as $packaging_id => $totals ) { + $count_total += (int) $totals['count']; + $weight_total += (float) $totals['weight_in_kg']; + $country_count_total += (int) $totals['count']; + $country_weight_total += (float) $totals['weight_in_kg']; + + if ( ! isset( $packaging_totals[ "$packaging_id" ] ) ) { + $packaging_totals[ "$packaging_id" ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + ); + } + + $packaging_totals[ "$packaging_id" ]['count'] += (int) $totals['count']; + $packaging_totals[ "$packaging_id" ]['weight_in_kg'] += (float) $totals['weight_in_kg']; + + $report->set_packaging_count_by_country( $country, $packaging_id, (int) $totals['count'] ); + $report->set_packaging_weight_by_country( $country, $packaging_id, (float) wc_remove_number_precision( $totals['weight_in_kg'] ) ); + } + + $country_weight_total = (float) wc_remove_number_precision( $country_weight_total ); + + $report->set_total_packaging_count_by_country( $country, $country_count_total ); + $report->set_total_packaging_weight_by_country( $country, $country_weight_total ); + } + + foreach ( $packaging_totals as $packaging_id => $totals ) { + $packaging_weight_total = (float) wc_remove_number_precision( $totals['weight_in_kg'] ); + + $report->set_packaging_count( $packaging_id, $totals['count'] ); + $report->set_packaging_weight( $packaging_id, $packaging_weight_total ); + } + + $weight_total = (float) wc_remove_number_precision( $weight_total ); + + Package::log( sprintf( 'Completed packaging count: %d', $count_total ) ); + Package::log( sprintf( 'Completed packaging weight: %s', $weight_total ) ); + + $report->set_total_count( $count_total ); + $report->set_total_weight( $weight_total ); + $report->set_status( 'completed' ); + $report->set_version( Package::get_version() ); + $report->save(); + + return $report; + } + + protected function get_temporary_result() { + return (array) get_option( $this->get_id() . '_tmp_result', array() ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packaging/Report.php b/packages/woocommerce-germanized-shipments/src/Packaging/Report.php new file mode 100644 index 000000000..099b06851 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packaging/Report.php @@ -0,0 +1,422 @@ +set_id( $id ); + + if ( empty( $args ) ) { + $args = (array) get_option( $this->id . '_result', array() ); + } + + $args = wp_parse_args( + $args, + array( + 'packaging' => array(), + 'countries' => array(), + 'totals' => array(), + 'meta' => array(), + ) + ); + + $args['totals'] = wp_parse_args( + $args['totals'], + array( + 'weight_in_kg' => 0.0, + 'count' => 0, + ) + ); + + $args['meta'] = wp_parse_args( + $args['meta'], + array( + 'date_requested' => null, + 'status' => 'pending', + 'version' => '', + ) + ); + + $this->set_date_requested( $args['meta']['date_requested'] ); + $this->set_status( $args['meta']['status'] ); + $this->set_version( $args['meta']['version'] ); + + $this->args = $args; + } + + public function exists() { + return get_option( $this->id . '_result', false ); + } + + public function get_title() { + $title = ReportHelper::get_report_title( $this->get_id() ); + + if ( $this->get_date_requested() ) { + $title = $title . ' @ ' . $this->get_date_requested()->date_i18n(); + } + + return $title; + } + + public function get_url() { + return admin_url( 'admin.php?page=shipment-packaging-report&report=' . $this->get_id() ); + } + + public function get_delete_link() { + return add_query_arg( + array( + 'action' => 'wc_gzd_shipments_packaging_delete_report', + 'report_id' => $this->get_id(), + ), + wp_nonce_url( admin_url( 'admin-post.php' ), 'wc_gzd_shipments_packaging_delete_report' ) + ); + } + + public function get_refresh_link() { + return add_query_arg( + array( + 'action' => 'wc_gzd_shipments_packaging_refresh_report', + 'report_id' => $this->get_id(), + ), + wp_nonce_url( admin_url( 'admin-post.php' ), 'wc_gzd_shipments_packaging_refresh_report' ) + ); + } + + public function get_cancel_link() { + return add_query_arg( + array( + 'action' => 'wc_gzd_shipments_packaging_cancel_report', + 'report_id' => $this->get_id(), + ), + wp_nonce_url( admin_url( 'admin-post.php' ), 'wc_gzd_shipments_packaging_cancel_report' ) + ); + } + + public function get_type() { + return $this->type; + } + + public function set_type( $type ) { + $this->set_id_part( $type, 'type' ); + } + + public function set_id( $id ) { + $this->id = $id; + $data = ReportHelper::get_report_data( $this->id ); + $this->type = $data['type']; + $this->date_start = $data['date_start']; + $this->date_end = $data['date_end']; + } + + public function set_id_part( $value, $part = 'type' ) { + $data = ReportHelper::get_report_data( $this->id ); + $data[ $part ] = $value; + + $this->set_id( ReportHelper::get_report_id( $data ) ); + } + + public function get_id() { + return $this->id; + } + + public function get_date_start() { + return $this->date_start; + } + + public function set_date_start( $date ) { + $date = ReportHelper::string_to_datetime( $date ); + + $this->set_id_part( $date->format( 'Y-m-d' ), 'date_start' ); + } + + public function get_date_end() { + return $this->date_end; + } + + public function set_date_end( $date ) { + $date = ReportHelper::string_to_datetime( $date ); + + $this->set_id_part( $date->format( 'Y-m-d' ), 'date_end' ); + } + + public function get_status() { + return $this->args['meta']['status']; + } + + public function get_version() { + return $this->args['meta']['version']; + } + + public function set_status( $status ) { + $this->args['meta']['status'] = $status; + } + + public function set_version( $version ) { + $this->args['meta']['version'] = $version; + } + + public function get_date_requested() { + return is_null( $this->args['meta']['date_requested'] ) ? null : ReportHelper::string_to_datetime( $this->args['meta']['date_requested'] ); + } + + public function set_date_requested( $date ) { + if ( ! empty( $date ) ) { + $date = ReportHelper::string_to_datetime( $date ); + } + + $this->args['meta']['date_requested'] = is_a( $date, 'WC_DateTime' ) ? $date->date( 'Y-m-d' ) : null; + } + + /** + * @return int + */ + public function get_total_count() { + return (int) $this->args['totals']['count']; + } + + public function get_total_weight( $round = true, $unit = '' ) { + if ( '' === $unit ) { + $unit = wc_gzd_get_packaging_weight_unit(); + } + + $weight = wc_get_weight( $this->args['totals']['weight_in_kg'], $unit, 'kg' ); + + return $this->maybe_round( $weight, $round ); + } + + public function set_total_weight( $weight ) { + $this->args['totals']['weight_in_kg'] = wc_format_decimal( floatval( $weight ) ); + } + + public function set_total_count( $count ) { + $this->args['totals']['count'] = absint( $count ); + } + + public function get_packaging_ids() { + return array_keys( $this->args['packaging'] ); + } + + public function get_countries() { + return array_keys( $this->args['countries'] ); + } + + public function reset() { + $this->args['packaging'] = array(); + $this->args['countries'] = array(); + + $this->set_total_count( 0 ); + $this->set_total_weight( 0 ); + $this->set_date_requested( new \WC_DateTime() ); + $this->set_status( 'pending' ); + $this->set_version( Package::get_version() ); + + delete_option( $this->id . '_tmp_result' ); + } + + public function get_packaging_ids_by_country( $country ) { + $packaging_ids = array(); + + if ( array_key_exists( $country, $this->args['countries'] ) ) { + $packaging_ids = array_keys( $this->args['countries'][ $country ]['packaging'] ); + } + + return $packaging_ids; + } + + public function get_packaging_count( $packaging_id, $country = '' ) { + $count = 0; + + if ( '' === $country ) { + if ( isset( $this->args['packaging'][ "$packaging_id" ] ) ) { + $count = absint( $this->args['packaging'][ "$packaging_id" ]['count'] ); + } + } else { + if ( isset( $this->args['countries'][ $country ], $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ] ) ) { + $count = absint( $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ]['count'] ); + } + } + + return $count; + } + + public function get_packaging_weight( $packaging_id, $country = '', $round = true, $unit = '' ) { + $weight = 0.0; + + if ( '' === $country ) { + if ( isset( $this->args['packaging'][ "$packaging_id" ] ) ) { + $weight = $this->args['packaging'][ "$packaging_id" ]['weight_in_kg']; + } + } else { + if ( isset( $this->args['countries'][ $country ], $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ] ) ) { + $weight = $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ]['weight_in_kg']; + } + } + + $weight = wc_get_weight( $weight, $unit, 'kg' ); + + return $this->maybe_round( $weight, $round ); + } + + public function get_total_packaging_weight_by_country( $country, $round = true, $unit = '' ) { + $weight = 0.0; + + if ( isset( $this->args['countries'][ $country ] ) ) { + $weight = $this->args['countries'][ $country ]['weight_in_kg']; + } + + $weight = wc_get_weight( $weight, $unit, 'kg' ); + + return $this->maybe_round( $weight, $round ); + } + + public function get_total_packaging_count_by_country( $country ) { + $count = 0; + + if ( isset( $this->args['countries'][ $country ] ) ) { + $count = absint( $this->args['countries'][ $country ]['count'] ); + } + + return $count; + } + + public function set_packaging_count( $packaging_id, $count ) { + if ( ! isset( $this->args['packaging'][ "$packaging_id" ] ) ) { + $this->args['packaging'][ "$packaging_id" ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + ); + } + + $this->args['packaging'][ "$packaging_id" ]['count'] = absint( $count ); + } + + public function set_packaging_weight( $packaging_id, $weight ) { + if ( ! isset( $this->args['packaging'][ "$packaging_id" ] ) ) { + $this->args['packaging'][ "$packaging_id" ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + ); + } + + $this->args['packaging'][ "$packaging_id" ]['weight_in_kg'] = (float) wc_format_decimal( $weight ); + } + + public function set_packaging_count_by_country( $country, $packaging_id, $count ) { + if ( ! isset( $this->args['countries'][ $country ] ) ) { + $this->args['countries'][ $country ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + 'packaging' => array(), + ); + } + + if ( ! isset( $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ] ) ) { + $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + ); + } + + $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ]['count'] = absint( $count ); + } + + public function set_packaging_weight_by_country( $country, $packaging_id, $weight ) { + if ( ! isset( $this->args['countries'][ $country ] ) ) { + $this->args['countries'][ $country ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + 'packaging' => array(), + ); + } + + if ( ! isset( $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ] ) ) { + $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + ); + } + + $this->args['countries'][ $country ]['packaging'][ "$packaging_id" ]['weight_in_kg'] = (float) wc_format_decimal( $weight ); + } + + public function set_total_packaging_count_by_country( $country, $count ) { + if ( ! isset( $this->args['countries'][ $country ] ) ) { + $this->args['countries'][ $country ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + 'packaging' => array(), + ); + } + + $this->args['countries'][ $country ]['count'] = absint( $count ); + } + + public function set_total_packaging_weight_by_country( $country, $weight ) { + if ( ! isset( $this->args['countries'][ $country ] ) ) { + $this->args['countries'][ $country ] = array( + 'count' => 0, + 'weight_in_kg' => 0.0, + 'packaging' => array(), + ); + } + + $this->args['countries'][ $country ]['weight_in_kg'] = (float) wc_format_decimal( $weight ); + } + + protected function maybe_round( $total, $round = true ) { + $decimals = is_numeric( $round ) ? (int) $round : ''; + + return (float) wc_format_decimal( $total, $round ? $decimals : false ); + } + + public function save() { + update_option( $this->id . '_result', $this->args, false ); + + $reports_available = ReportHelper::get_report_ids(); + + if ( ! in_array( $this->get_id(), $reports_available[ $this->get_type() ], true ) ) { + // Add new report to start of the list + array_unshift( $reports_available[ $this->get_type() ], $this->get_id() ); + update_option( 'woocommerce_gzd_shipments_packaging_reports', $reports_available, false ); + } + + delete_option( $this->id . '_tmp_result' ); + + ReportHelper::clear_caches(); + + return $this->id; + } + + public function delete() { + delete_option( $this->id . '_result' ); + delete_option( $this->id . '_tmp_result' ); + + ReportQueue::maybe_stop_report( $this->get_id() ); + ReportHelper::remove_report( $this ); + + ReportHelper::clear_caches(); + + return true; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packaging/ReportHelper.php b/packages/woocommerce-germanized-shipments/src/Packaging/ReportHelper.php new file mode 100644 index 000000000..5eef695a2 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packaging/ReportHelper.php @@ -0,0 +1,516 @@ + array( + 'url' => $report->get_url(), + 'title' => _x( 'View', 'shipments-packaging-report', 'woocommerce-germanized' ), + ), + 'refresh' => array( + 'url' => $report->get_refresh_link(), + 'title' => _x( 'Refresh', 'shipments-packaging-report', 'woocommerce-germanized' ), + ), + 'delete' => array( + 'url' => $report->get_delete_link(), + 'title' => _x( 'Delete', 'shipments-packaging-report', 'woocommerce-germanized' ), + ), + ); + + if ( 'completed' !== $report->get_status() ) { + $actions['cancel'] = $actions['delete']; + $actions['cancel']['title'] = _x( 'Cancel', 'shipments-packaging-report', 'woocommerce-germanized' ); + + unset( $actions['view'] ); + unset( $actions['refresh'] ); + unset( $actions['delete'] ); + } + + return $actions; + } + + public static function render_report() { + if ( isset( $_GET['report'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $report_id = isset( $_GET['report'] ) ? wc_clean( wp_unslash( $_GET['report'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + + if ( ! $report_id ) { + return; + } + + if ( ! $report = self::get_report( $report_id ) ) { + return; + } + + $columns = array( + 'packaging' => _x( 'Packaging', 'shipments', 'woocommerce-germanized' ), + 'weight' => _x( 'Weight', 'shipments', 'woocommerce-germanized' ), + 'count' => _x( 'Count', 'shipments', 'woocommerce-germanized' ), + ); + + $actions = self::get_report_actions( $report ); + $countries = WC()->countries->get_countries(); + $packaging_ids = $report->get_packaging_ids(); + ?> +
+

get_title() ); ?>

+ $action ) : + if ( 'view' === $action_type ) { + continue; + } + ?> + + + + get_status() ) : ?> +

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() ) ); ?>)

+
+ + + + + + $column ) : ?> + + + + + + + + + + + + + +
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_countries() as $country ) : + ?> +

+

get_total_packaging_weight_by_country( $country ), wc_gzd_get_packaging_weight_unit() ) ); ?> (get_total_packaging_count_by_country( $country ) ) ); ?>)

+ + + + $column ) : ?> + + + + + + get_packaging_ids_by_country( $country ) as $packaging_id ) : + $packaging = is_numeric( $packaging_id ) ? wc_gzd_get_packaging( $packaging_id ) : false; + ?> + + + + + + + +
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'] ) ); ?>

+ +
+ get_status() ) { + $report->delete(); + } + } + } + } + + $running = array_values( $running ); + + update_option( 'woocommerce_gzd_shipments_packaging_reports_running', $running, false ); + ReportQueue::clear_cache(); + } + + public static function setup_recurring_actions() { + if ( $queue = ReportQueue::get_queue() ) { + // Schedule once per day at 2:00 + if ( null === $queue->get_next( 'woocommerce_gzd_shipments_daily_cleanup', array(), 'woocommerce_gzd_shipments' ) ) { + $timestamp = strtotime( 'tomorrow midnight' ); + $date = new \WC_DateTime(); + + $date->setTimestamp( $timestamp ); + $date->modify( '+2 hours' ); + + $queue->cancel_all( 'woocommerce_gzd_shipments_daily_cleanup', array(), 'woocommerce_gzd_shipments' ); + $queue->schedule_recurring( $date->getTimestamp(), DAY_IN_SECONDS, 'woocommerce_gzd_shipments_daily_cleanup', array(), 'woocommerce_gzd_shipments' ); + } + } + } + + public static function get_report_title( $id ) { + $args = self::get_report_data( $id ); + $title = _x( 'Report', 'shipments', 'woocommerce-germanized' ); + + if ( 'quarterly' === $args['type'] ) { + $date_start = $args['date_start']; + $quarter = 1; + $month_num = (int) $date_start->date_i18n( 'n' ); + + if ( 4 === $month_num ) { + $quarter = 2; + } elseif ( 7 === $month_num ) { + $quarter = 3; + } elseif ( 10 === $month_num ) { + $quarter = 4; + } + + $title = sprintf( _x( 'Q%1$s/%2$s', 'shipments', 'woocommerce-germanized' ), $quarter, $date_start->date_i18n( 'Y' ) ); + } elseif ( 'monthly' === $args['type'] ) { + $date_start = $args['date_start']; + $month_num = $date_start->date_i18n( 'm' ); + + $title = sprintf( _x( '%1$s/%2$s', 'shipments', 'woocommerce-germanized' ), $month_num, $date_start->date_i18n( 'Y' ) ); + } elseif ( 'yearly' === $args['type'] ) { + $date_start = $args['date_start']; + + $title = sprintf( _x( '%1$s', 'shipments', 'woocommerce-germanized' ), $date_start->date_i18n( 'Y' ) ); // phpcs:ignore WordPress.WP.I18n.NoEmptyStrings + } elseif ( 'custom' === $args['type'] ) { + $date_start = $args['date_start']; + $date_end = $args['date_end']; + + $title = sprintf( _x( '%1$s - %2$s', 'shipments', 'woocommerce-germanized' ), $date_start->date_i18n( 'Y-m-d' ), $date_end->date_i18n( 'Y-m-d' ) ); + } + + return $title; + } + + public static function get_report_id( $parts ) { + $parts = wp_parse_args( + $parts, + array( + 'type' => 'daily', + 'date_start' => date( 'Y-m-d' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date + 'date_end' => date( 'Y-m-d' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date + ) + ); + + if ( is_a( $parts['date_start'], 'WC_DateTime' ) ) { + $parts['date_start'] = $parts['date_start']->format( 'Y-m-d' ); + } + + if ( is_a( $parts['date_end'], 'WC_DateTime' ) ) { + $parts['date_end'] = $parts['date_end']->format( 'Y-m-d' ); + } + + return sanitize_key( 'woocommerce_gzd_shipments_packaging_' . $parts['type'] . '_report_' . $parts['date_start'] . '_' . $parts['date_end'] ); + } + + public static function get_available_report_types() { + $types = array( + 'quarterly' => _x( 'Quarterly', 'shipments', 'woocommerce-germanized' ), + 'yearly' => _x( 'Yearly', 'shipments', 'woocommerce-germanized' ), + 'monthly' => _x( 'Monthly', 'shipments', 'woocommerce-germanized' ), + 'custom' => _x( 'Custom', 'shipments', 'woocommerce-germanized' ), + ); + + return $types; + } + + public static function get_report_data( $id ) { + $clean_id = str_replace( 'packaging_', '', $id ); + $clean_id = str_replace( 'woocommerce_gzd_shipments_', '', $clean_id ); + $id_parts = explode( '_', $clean_id ); + + $data = array( + 'id' => $id, + 'type' => $id_parts[0], + 'date_start' => self::string_to_datetime( $id_parts[2] ), + 'date_end' => self::string_to_datetime( $id_parts[3] ), + ); + + return $data; + } + + public static function string_to_datetime( $time_string ) { + if ( is_string( $time_string ) && ! is_numeric( $time_string ) ) { + $time_string = strtotime( $time_string ); + } + + $date_time = $time_string; + + if ( is_numeric( $date_time ) ) { + $date_time = new \WC_DateTime( "@{$date_time}", new \DateTimeZone( 'UTC' ) ); + } + + if ( ! is_a( $date_time, 'WC_DateTime' ) ) { + return null; + } + + return $date_time; + } + + public static function clear_caches() { + delete_transient( 'woocommerce_gzd_shipments_packaging_report_counts' ); + wp_cache_delete( 'woocommerce_gzd_shipments_packaging_reports', 'options' ); + } + + public static function get_report_ids() { + $reports = (array) get_option( 'woocommerce_gzd_shipments_packaging_reports', array() ); + + foreach ( array_keys( self::get_available_report_types() ) as $type ) { + if ( ! array_key_exists( $type, $reports ) ) { + $reports[ $type ] = array(); + } + } + + return $reports; + } + + public static function get_report_status_title( $status ) { + if ( 'completed' === $status ) { + return _x( 'Completed', 'shipments-report-status', 'woocommerce-germanized' ); + } else { + return _x( 'Pending', 'shipments-report-status', 'woocommerce-germanized' ); + } + } + + /** + * @param array $args + * + * @return Report[] + */ + public static function get_reports( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'type' => '', + 'limit' => -1, + 'offset' => 0, + 'orderby' => 'date_start', + ) + ); + + $ids = self::get_report_ids(); + + if ( ! empty( $args['type'] ) ) { + $report_ids = array_key_exists( $args['type'], $ids ) ? $ids[ $args['type'] ] : array(); + } else { + $report_ids = array_merge( ...array_values( $ids ) ); + } + + $reports_sorted = array(); + + foreach ( $report_ids as $id ) { + $reports_sorted[] = self::get_report_data( $id ); + } + + if ( array_key_exists( $args['orderby'], array( 'date_start', 'date_end' ) ) ) { + usort( + $reports_sorted, + function( $a, $b ) use ( $args ) { + if ( $a[ $args['orderby'] ] === $b[ $args['orderby'] ] ) { + return 0; + } + + return $a[ $args['orderby'] ] < $b[ $args['orderby'] ] ? -1 : 1; + } + ); + } + + if ( -1 !== $args['limit'] ) { + $reports_sorted = array_slice( $reports_sorted, $args['offset'], $args['limit'] ); + } + + $reports = array(); + + foreach ( $reports_sorted as $data ) { + if ( $report = self::get_report( $data['id'] ) ) { + $reports[] = $report; + } + } + + return $reports; + } + + /** + * @param Report $report + */ + public static function remove_report( $report ) { + $reports_available = self::get_report_ids(); + + if ( in_array( $report->get_id(), $reports_available[ $report->get_type() ], true ) ) { + $reports_available[ $report->get_type() ] = array_diff( $reports_available[ $report->get_type() ], array( $report->get_id() ) ); + + update_option( 'woocommerce_gzd_shipments_packaging_reports', $reports_available, false ); + + /** + * Force non-cached option + */ + wp_cache_delete( 'woocommerce_gzd_shipments_packaging_reports', 'options' ); + } + } + + /** + * @param $id + * + * @return false|Report + */ + public static function get_report( $id ) { + $report = new Report( $id ); + + if ( $report->exists() ) { + return $report; + } + + return false; + } + + public static function delete_report() { + if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( isset( $_GET['_wpnonce'] ) ? wp_unslash( $_GET['_wpnonce'] ) : '', 'wc_gzd_shipments_packaging_delete_report' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + wp_die(); + } + + $report_id = isset( $_GET['report_id'] ) ? wc_clean( wp_unslash( $_GET['report_id'] ) ) : ''; + + if ( ! empty( $report_id ) && ( $report = self::get_report( $report_id ) ) ) { + $report->delete(); + + $referer = self::get_clean_referer(); + + /** + * Do not redirect deleted, refreshed reports back to report details page + */ + if ( strstr( $referer, '&report=' ) ) { + $referer = admin_url( 'admin.php?page=wc-settings&tab=germanized-shipments§ion=packaging' ); + } + + wp_safe_redirect( esc_url_raw( add_query_arg( array( 'report_deleted' => $report_id ), $referer ) ) ); + exit(); + } + + wp_safe_redirect( esc_url_raw( wp_get_referer() ) ); + exit(); + } + + protected static function get_clean_referer() { + $referer = wp_get_referer(); + + return remove_query_arg( array( 'report_created', 'report_deleted', 'report_restarted', 'report_cancelled' ), $referer ); + } + + public static function refresh_report() { + if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( isset( $_GET['_wpnonce'] ) ? wp_unslash( $_GET['_wpnonce'] ) : '', 'wc_gzd_shipments_packaging_refresh_report' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + wp_die(); + } + + $report_id = isset( $_GET['report_id'] ) ? wc_clean( wp_unslash( $_GET['report_id'] ) ) : ''; + + if ( ! empty( $report_id ) && ( $report = self::get_report( $report_id ) ) ) { + ReportQueue::start( $report->get_type(), $report->get_date_start(), $report->get_date_end() ); + + wp_safe_redirect( esc_url_raw( add_query_arg( array( 'report_restarted' => $report_id ), self::get_clean_referer() ) ) ); + exit(); + } + + wp_safe_redirect( esc_url_raw( wp_get_referer() ) ); + exit(); + } + + public static function cancel_report() { + if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( isset( $_GET['_wpnonce'] ) ? wp_unslash( $_GET['_wpnonce'] ) : '', 'wc_gzd_shipments_packaging_cancel_report' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + wp_die(); + } + + $report_id = isset( $_GET['report_id'] ) ? wc_clean( wp_unslash( $_GET['report_id'] ) ) : ''; + + if ( ! empty( $report_id ) && ReportQueue::is_running( $report_id ) ) { + ReportQueue::cancel( $report_id ); + + $referer = self::get_clean_referer(); + + /** + * Do not redirect deleted, refreshed reports back to report details page + */ + if ( strstr( $referer, '&report=' ) ) { + $referer = admin_url( 'admin.php?page=wc-settings&tab=germanized-shipments§ion=packaging' ); + } + + wp_safe_redirect( esc_url_raw( add_query_arg( array( 'report_cancelled' => $report_id ), $referer ) ) ); + exit(); + } + + wp_safe_redirect( esc_url_raw( wp_get_referer() ) ); + exit(); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packaging/ReportQueue.php b/packages/woocommerce-germanized-shipments/src/Packaging/ReportQueue.php new file mode 100644 index 000000000..047953647 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packaging/ReportQueue.php @@ -0,0 +1,337 @@ +diff( $args['end'] ); + + // Add version + $args['version'] = Package::get_version(); + + $generator = new AsyncReportGenerator( $type, $args ); + $queue_args = $generator->get_args(); + $queue = self::get_queue(); + + self::cancel( $generator->get_id() ); + + $report = $generator->start(); + + if ( is_a( $report, '\Vendidero\Germanized\Shipments\Packaging\Report' ) && $report->exists() ) { + Package::log( sprintf( 'Starting new %1$s', $report->get_title() ) ); + Package::log( sprintf( 'Default report arguments: %s', wc_print_r( $queue_args, true ) ) ); + + $queue->schedule_single( + time() + 10, + self::get_hook_name( $generator->get_id() ), + array( 'args' => $queue_args ), + 'woocommerce_gzd_shipments' + ); + + $running = self::get_reports_running(); + + if ( ! in_array( $generator->get_id(), $running, true ) ) { + $running[] = $generator->get_id(); + } + + update_option( 'woocommerce_gzd_shipments_packaging_reports_running', $running, false ); + self::clear_cache(); + + return $generator->get_id(); + } + + return false; + } + + public static function clear_cache() { + wp_cache_delete( 'woocommerce_gzd_shipments_packaging_reports_running', 'options' ); + } + + public static function get_queue_details( $report_id ) { + $details = array( + 'next_date' => null, + 'link' => admin_url( 'admin.php?page=wc-status&tab=action-scheduler&s=' . esc_attr( $report_id ) . '&status=pending' ), + 'shipment_count' => 0, + 'has_action' => false, + 'is_finished' => false, + 'action' => false, + ); + + if ( $queue = self::get_queue() ) { + if ( $next_date = $queue->get_next( self::get_hook_name( $report_id ) ) ) { + $details['next_date'] = $next_date; + } + + $search_args = array( + 'hook' => self::get_hook_name( $report_id ), + 'status' => \ActionScheduler_Store::STATUS_RUNNING, + 'order' => 'DESC', + 'per_page' => 1, + ); + + $results = $queue->search( $search_args ); + + /** + * Search for pending as fallback + */ + if ( empty( $results ) ) { + $search_args['status'] = \ActionScheduler_Store::STATUS_PENDING; + $results = $queue->search( $search_args ); + } + + /** + * Last resort: Search for completed (e.g. if no pending and no running are found - must have been completed) + */ + if ( empty( $results ) ) { + $search_args['status'] = \ActionScheduler_Store::STATUS_COMPLETE; + $results = $queue->search( $search_args ); + } + + if ( ! empty( $results ) ) { + $action = array_values( $results )[0]; + $args = $action->get_args(); + $processed = isset( $args['args']['processed'] ) ? (int) $args['args']['processed'] : 0; + + $details['shipment_count'] = absint( $processed ); + $details['has_action'] = true; + $details['action'] = $action; + $details['is_finished'] = $action->is_finished(); + } + } + + return $details; + } + + public static function get_batch_size() { + return apply_filters( 'woocommerce_gzd_shipments_packaging_report_batch_size', 25 ); + } + + public static function get_shipment_statuses() { + $statuses = array_keys( wc_gzd_get_shipment_statuses() ); + $statuses = array_diff( $statuses, array( 'gzd-draft', 'gzd-requested' ) ); + + return apply_filters( 'woocommerce_gzd_shipments_packaging_report_valid_statuses', $statuses ); + } + + /** + * @param $args + * + * @return \Vendidero\Germanized\Shipments\Shipment[] + */ + public static function query( $args ) { + $query_args = array( + 'date_created' => $args['start'] . '...' . $args['end'], + 'offset' => $args['offset'], + 'type' => $args['type'], + 'status' => $args['status'], + 'limit' => $args['limit'], + ); + + return wc_gzd_get_shipments( $query_args ); + } + + public static function cancel( $id ) { + $data = ReportHelper::get_report_data( $id ); + $generator = new AsyncReportGenerator( $data['type'], $data ); + $queue = self::get_queue(); + $running = self::get_reports_running(); + + if ( self::is_running( $id ) ) { + $running = array_diff( $running, array( $id ) ); + Package::log( sprintf( 'Cancelled %s', ReportHelper::get_report_title( $id ) ) ); + + update_option( 'woocommerce_gzd_shipments_packaging_reports_running', $running, false ); + self::clear_cache(); + $generator->delete(); + } + + /** + * Cancel outstanding events and queue new. + */ + $queue->cancel_all( self::get_hook_name( $id ) ); + } + + public static function get_queue() { + return function_exists( 'WC' ) ? WC()->queue() : false; + } + + public static function is_running( $id ) { + $running = self::get_reports_running(); + + if ( in_array( $id, $running, true ) && self::get_queue()->get_next( self::get_hook_name( $id ) ) ) { + return true; + } + + return false; + } + + public static function get_hook_name( $id ) { + if ( ! strstr( $id, 'woocommerce_gzd_shipments_' ) ) { + $id = 'woocommerce_gzd_shipments_' . $id; + } + + return $id; + } + + public static function next( $type, $args ) { + $generator = new AsyncReportGenerator( $type, $args ); + $result = $generator->next(); + $is_empty = false; + $queue = self::get_queue(); + + if ( is_wp_error( $result ) ) { + $is_empty = $result->get_error_message( 'empty' ); + } + + if ( ! $is_empty ) { + $new_args = $generator->get_args(); + + // Increase offset + $new_args['offset'] = (int) $new_args['offset'] + (int) $new_args['limit']; + + $queue->cancel_all( self::get_hook_name( $generator->get_id() ) ); + + Package::log( sprintf( 'Starting new queue: %s', wc_print_r( $new_args, true ) ) ); + + $queue->schedule_single( + time() + 10, + self::get_hook_name( $generator->get_id() ), + array( 'args' => $new_args ), + 'woocommerce_gzd_shipments' + ); + } else { + self::complete( $generator ); + } + } + + /** + * @param AsyncReportGenerator $generator + */ + public static function complete( $generator ) { + $queue = self::get_queue(); + $type = $generator->get_type(); + + /** + * Cancel outstanding events. + */ + $queue->cancel_all( self::get_hook_name( $generator->get_id() ) ); + + $report = $generator->complete(); + $status = 'failed'; + + if ( is_a( $report, '\Vendidero\Germanized\Shipments\Packaging\Report' ) && $report->exists() ) { + $status = 'completed'; + } + + Package::log( sprintf( 'Completed %1$s. Status: %2$s', $report->get_title(), $status ) ); + + self::maybe_stop_report( $report->get_id() ); + } + + public static function maybe_stop_report( $report_id ) { + $reports_running = self::get_reports_running(); + + if ( in_array( $report_id, $reports_running, true ) ) { + $reports_running = array_diff( $reports_running, array( $report_id ) ); + update_option( 'woocommerce_gzd_shipments_packaging_reports_running', $reports_running, false ); + + if ( $queue = self::get_queue() ) { + $queue->cancel_all( self::get_hook_name( $report_id ) ); + } + + /** + * Force non-cached running option + */ + wp_cache_delete( 'woocommerce_gzd_shipments_packaging_reports_running', 'options' ); + + return true; + } + + return false; + } + + public static function get_reports_running() { + return (array) get_option( 'woocommerce_gzd_shipments_packaging_reports_running', array() ); + } + + public static function get_timeframe( $type, $date = null, $date_end = null ) { + $date_start = null; + $date_end = is_null( $date_end ) ? null : $date_end; + $start_indicator = is_null( $date ) ? new \WC_DateTime() : $date; + + if ( ! is_a( $start_indicator, 'WC_DateTime' ) && is_numeric( $start_indicator ) ) { + $start_indicator = new \WC_DateTime( '@' . $start_indicator ); + } + + if ( ! is_null( $date_end ) && ! is_a( $date_end, 'WC_DateTime' ) && is_numeric( $date_end ) ) { + $date_end = new \WC_DateTime( '@' . $date_end ); + } + + if ( 'quarterly' === $type ) { + $month = $start_indicator->date( 'n' ); + $quarter = (int) ceil( $month / 3 ); + $start_month = 'Jan'; + $end_month = 'Mar'; + + if ( 2 === $quarter ) { + $start_month = 'Apr'; + $end_month = 'Jun'; + } elseif ( 3 === $quarter ) { + $start_month = 'Jul'; + $end_month = 'Sep'; + } elseif ( 4 === $quarter ) { + $start_month = 'Oct'; + $end_month = 'Dec'; + } + + $date_start = new \WC_DateTime( 'first day of ' . $start_month . ' ' . $start_indicator->format( 'Y' ) . ' midnight' ); + $date_end = new \WC_DateTime( 'last day of ' . $end_month . ' ' . $start_indicator->format( 'Y' ) . ' midnight' ); + } elseif ( 'monthly' === $type ) { + $month = $start_indicator->format( 'M' ); + + $date_start = new \WC_DateTime( 'first day of ' . $month . ' ' . $start_indicator->format( 'Y' ) . ' midnight' ); + $date_end = new \WC_DateTime( 'last day of ' . $month . ' ' . $start_indicator->format( 'Y' ) . ' midnight' ); + } elseif ( 'yearly' === $type ) { + $date_end = clone $start_indicator; + $date_start = clone $start_indicator; + + $date_end->modify( 'last day of dec ' . $start_indicator->format( 'Y' ) . ' midnight' ); + $date_start->modify( 'first day of jan ' . $start_indicator->format( 'Y' ) . ' midnight' ); + } else { + if ( is_null( $date_end ) ) { + $date_end = clone $start_indicator; + $date_end->modify( '-1 year' ); + } + + $date_start = clone $start_indicator; + } + + /** + * Always set start and end time to midnight + */ + if ( $date_start ) { + $date_start->setTime( 0, 0 ); + } + + if ( $date_end ) { + $date_end->setTime( 0, 0 ); + } + + return array( + 'start' => $date_start, + 'end' => $date_end, + ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/PackagingFactory.php b/packages/woocommerce-germanized-shipments/src/PackagingFactory.php new file mode 100644 index 000000000..4c48f8ec3 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/PackagingFactory.php @@ -0,0 +1,65 @@ +get_id(); + } elseif ( ! empty( $packaging->packaging_id ) ) { + return $packaging->packaging_id; + } else { + return false; + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packing/Helper.php b/packages/woocommerce-germanized-shipments/src/Packing/Helper.php new file mode 100644 index 000000000..24c0abbb2 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packing/Helper.php @@ -0,0 +1,31 @@ +get_id() ] = new PackagingBox( $packaging ); + } + } + + if ( $id ) { + return array_key_exists( $id, self::$packaging ) ? self::$packaging[ $id ] : false; + } + + return self::$packaging; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packing/OrderItem.php b/packages/woocommerce-germanized-shipments/src/Packing/OrderItem.php new file mode 100644 index 000000000..1dc130ee0 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packing/OrderItem.php @@ -0,0 +1,117 @@ +item = $item; + + if ( ! is_callable( array( $item, 'get_product' ) ) ) { + throw new \Exception( 'Invalid item' ); + } + + if ( $product = $this->item->get_product() ) { + $this->product = $product; + + $width = empty( $this->product->get_width() ) ? 0 : wc_format_decimal( $this->product->get_width() ); + $length = empty( $this->product->get_length() ) ? 0 : wc_format_decimal( $this->product->get_length() ); + $depth = empty( $this->product->get_height() ) ? 0 : wc_format_decimal( $this->product->get_height() ); + + $this->dimensions = array( + 'width' => (int) wc_get_dimension( $width, 'mm' ), + 'length' => (int) wc_get_dimension( $length, 'mm' ), + 'depth' => (int) wc_get_dimension( $depth, 'mm' ), + ); + + $weight = empty( $this->product->get_weight() ) ? 0 : wc_format_decimal( $this->product->get_weight() ); + $this->weight = (int) wc_get_weight( $weight, 'g' ); + } + + if ( ! $product ) { + throw new \Exception( 'Missing product' ); + } + } + + public function get_id() { + return $this->item->get_id(); + } + + /** + * @return \WC_Order_Item_Product + */ + public function get_order_item() { + return $this->item; + } + + /** + * Item SKU etc. + */ + public function getDescription(): string { + if ( $this->product->get_sku() ) { + return $this->product->get_sku(); + } + + return $this->item->get_id(); + } + + /** + * Item width in mm. + */ + public function getWidth(): int { + return $this->dimensions['width']; + } + + /** + * Item length in mm. + */ + public function getLength(): int { + return $this->dimensions['length']; + } + + /** + * Item depth in mm. + */ + public function getDepth(): int { + return $this->dimensions['depth']; + } + + /** + * Item weight in g. + */ + public function getWeight(): int { + return $this->weight; + } + + /** + * Does this item need to be kept flat / packed "this way up"? + */ + public function getKeepFlat(): bool { + return false; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packing/PackagingBox.php b/packages/woocommerce-germanized-shipments/src/Packing/PackagingBox.php new file mode 100644 index 000000000..6abe9c7bb --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packing/PackagingBox.php @@ -0,0 +1,147 @@ +packaging = $packaging; + + $width = empty( $this->packaging->get_width() ) ? 0 : wc_format_decimal( $this->packaging->get_width() ); + $length = empty( $this->packaging->get_length() ) ? 0 : wc_format_decimal( $this->packaging->get_length() ); + $depth = empty( $this->packaging->get_height() ) ? 0 : wc_format_decimal( $this->packaging->get_height() ); + + $this->dimensions = array( + 'width' => (int) floor( wc_get_dimension( $width, 'mm', wc_gzd_get_packaging_dimension_unit() ) ), + 'length' => (int) floor( wc_get_dimension( $length, 'mm', wc_gzd_get_packaging_dimension_unit() ) ), + 'depth' => (int) floor( wc_get_dimension( $depth, 'mm', wc_gzd_get_packaging_dimension_unit() ) ), + ); + + $weight = empty( $this->packaging->get_weight() ) ? 0 : wc_format_decimal( $this->packaging->get_weight() ); + $this->weight = (int) floor( wc_get_weight( $weight, 'g', wc_gzd_get_packaging_weight_unit() ) ); + + $max_content_weight = empty( $this->packaging->get_max_content_weight() ) ? 0 : wc_format_decimal( $this->packaging->get_max_content_weight() ); + $this->max_weight = (int) floor( wc_get_weight( $max_content_weight, 'g', wc_gzd_get_packaging_weight_unit() ) ); + + /** + * If no max weight was chosen - use 50kg as fallback + */ + if ( empty( $this->max_weight ) ) { + $this->max_weight = 50000; + } + } + + public function get_id() { + return $this->packaging->get_id(); + } + + /** + * Reference for box type (e.g. SKU or description). + */ + public function getReference(): string { + return (string) $this->packaging->get_title(); + } + + /** + * Outer width in mm. + */ + public function getOuterWidth(): int { + return $this->dimensions['width']; + } + + /** + * Outer length in mm. + */ + public function getOuterLength(): int { + return $this->dimensions['length']; + } + + /** + * Outer depth in mm. + */ + public function getOuterDepth(): int { + return $this->dimensions['depth']; + } + + /** + * Empty weight in g. + */ + public function getEmptyWeight(): int { + return $this->weight; + } + + /** + * Returns the threshold by which the inner dimension gets reduced + * in comparison to the outer dimension. + * + * @param string $type + * + * @return float + */ + public function get_inner_dimension_buffer( $value, $type = 'width' ) { + if ( apply_filters( 'woocommerce_gzd_packaging_inner_dimension_use_percentage_buffer', false, $type, $this ) ) { + $percentage_buffer = apply_filters( 'woocommerce_gzd_packaging_inner_dimension_percentage_buffer', 0.5, $type, $this ) / 100; + $value = $value - ( $value * $percentage_buffer ); + } else { + $fixed_buffer = apply_filters( 'woocommerce_gzd_packaging_inner_dimension_fixed_buffer_mm', 5, $type, $this ); + $value = $value - $fixed_buffer; + } + + return max( $value, 0 ); + } + + /** + * Inner width in mm. + */ + public function getInnerWidth(): int { + $width = $this->get_inner_dimension_buffer( $this->dimensions['width'], 'width' ); + + return $width; + } + + /** + * Inner length in mm. + */ + public function getInnerLength(): int { + $length = $this->get_inner_dimension_buffer( $this->dimensions['length'], 'length' ); + + return $length; + } + + /** + * Inner depth in mm. + */ + public function getInnerDepth(): int { + $depth = $this->get_inner_dimension_buffer( $this->dimensions['depth'], 'depth' ); + + return $depth; + } + + /** + * Max weight the packaging can hold in g. + */ + public function getMaxWeight(): int { + return $this->max_weight; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Packing/ShipmentItem.php b/packages/woocommerce-germanized-shipments/src/Packing/ShipmentItem.php new file mode 100644 index 000000000..e4a347676 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Packing/ShipmentItem.php @@ -0,0 +1,111 @@ +item = $item; + + if ( $shipment = $item->get_shipment() ) { + $dimension_unit = $shipment->get_dimension_unit(); + $weight_unit = $shipment->get_weight_unit(); + } else { + $dimension_unit = get_option( 'woocommerce_dimension_unit', 'cm' ); + $weight_unit = get_option( 'woocommerce_weight_unit', 'kg' ); + } + + $width = empty( $this->item->get_width() ) ? 0 : wc_format_decimal( $this->item->get_width() ); + $length = empty( $this->item->get_length() ) ? 0 : wc_format_decimal( $this->item->get_length() ); + $depth = empty( $this->item->get_height() ) ? 0 : wc_format_decimal( $this->item->get_height() ); + + $this->dimensions = array( + 'width' => (int) ceil( wc_get_dimension( $width, 'mm', $dimension_unit ) ), + 'length' => (int) ceil( wc_get_dimension( $length, 'mm', $dimension_unit ) ), + 'depth' => (int) ceil( wc_get_dimension( $depth, 'mm', $dimension_unit ) ), + ); + + $weight = empty( $this->item->get_weight() ) ? 0 : wc_format_decimal( $this->item->get_weight() ); + $this->weight = (int) ceil( wc_get_weight( $weight, 'g', $weight_unit ) ); + } + + /** + * @return \Vendidero\Germanized\Shipments\ShipmentItem + */ + public function get_shipment_item() { + return $this->item; + } + + public function get_id() { + return $this->item->get_id(); + } + + /** + * Item SKU etc. + */ + public function getDescription(): string { + if ( $this->item->get_sku() ) { + return $this->item->get_sku(); + } + + return $this->item->get_id(); + } + + /** + * Item width in mm. + */ + public function getWidth(): int { + return $this->dimensions['width']; + } + + /** + * Item length in mm. + */ + public function getLength(): int { + return $this->dimensions['length']; + } + + /** + * Item depth in mm. + */ + public function getDepth(): int { + return $this->dimensions['depth']; + } + + /** + * Item weight in g. + */ + public function getWeight(): int { + return $this->weight; + } + + /** + * Does this item need to be kept flat / packed "this way up"? + */ + public function getKeepFlat(): bool { + return false; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Product.php b/packages/woocommerce-germanized-shipments/src/Product.php new file mode 100644 index 000000000..d1e739d1c --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Product.php @@ -0,0 +1,130 @@ +product = $product; + } + + /** + * Returns the Woo WC_Product original object + * + * @return object|WC_Product + */ + public function get_product() { + return $this->product; + } + + protected function get_forced_parent_product() { + if ( $this->product->is_type( 'variation' ) ) { + if ( $parent = wc_get_product( $this->product->get_parent_id() ) ) { + return $parent; + } + } + + return $this->product; + } + + public function get_hs_code( $context = 'view' ) { + $legacy_data = $this->get_forced_parent_product()->get_meta( '_dhl_hs_code', true, $context ); + $data = $this->get_forced_parent_product()->get_meta( '_hs_code', true, $context ); + + if ( '' === $data && ! empty( $legacy_data ) ) { + $data = $legacy_data; + } + + return $data; + } + + public function get_manufacture_country( $context = 'view' ) { + $legacy_data = $this->get_forced_parent_product()->get_meta( '_dhl_manufacture_country', true, $context ); + $data = $this->get_forced_parent_product()->get_meta( '_manufacture_country', true, $context ); + + if ( '' === $data && ! empty( $legacy_data ) ) { + $data = $legacy_data; + } + + if ( '' === $data && 'view' === $context ) { + return wc_get_base_location()['country']; + } + + return $data; + } + + public function get_main_category() { + $ids = $this->get_forced_parent_product()->get_category_ids(); + $term_name = ''; + + if ( ! empty( $ids ) ) { + foreach ( $ids as $term_id ) { + $term = get_term( $term_id, 'product_cat' ); + + if ( empty( $term->slug ) ) { + continue; + } + + $term_name = $term->name; + break; + } + } + + return $term_name; + } + + public function set_hs_code( $code ) { + $this->product->update_meta_data( '_hs_code', $code ); + } + + public function set_manufacture_country( $country ) { + $this->product->update_meta_data( '_manufacture_country', substr( wc_strtoupper( $country ), 0, 2 ) ); + } + + /** + * 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->product, $method ) ) { + return call_user_func_array( array( $this->product, $method ), $args ); + } + + return false; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Rest/ShipmentsController.php b/packages/woocommerce-germanized-shipments/src/Rest/ShipmentsController.php new file mode 100644 index 000000000..d183cd932 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Rest/ShipmentsController.php @@ -0,0 +1,2057 @@ +namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'default' => false, + 'type' => 'boolean', + 'description' => _x( 'Whether to bypass trash and force deletion.', 'shipments', 'woocommerce-germanized' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)/label', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_label' ), + 'permission_callback' => array( $this, 'get_label_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_label' ), + 'permission_callback' => array( $this, 'create_label_permissions_check' ), + 'args' => array( + array( + 'description' => _x( 'Shipment label.', 'shipment', 'woocommerce-germanized' ), + 'context' => array( 'view', 'edit' ), + 'readonly' => false, + 'type' => 'object', + 'properties' => array( + 'type' => 'object', + 'properties' => array( + 'key' => array( + 'description' => _x( 'Label field key.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'value' => array( + 'description' => _x( 'Label field value.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'mixed', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + ), + ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_label' ), + 'permission_callback' => array( $this, 'delete_label_permissions_check' ), + 'args' => array( + 'force' => array( + 'default' => false, + 'type' => 'boolean', + 'description' => _x( 'Whether to bypass trash and force deletion.', 'shipments', 'woocommerce-germanized' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_label_schema' ), + ) + ); + } + + /** + * Get object. + * + * @param int|Shipment $id Object ID. + * @return Shipment Shipment object or WP_Error object. + */ + protected function get_object( $id ) { + return $this->get_shipment( $id ); + } + + /** + * Get object permalink. + * + * @param Shipment $shipment Object. + * @return string + */ + protected function get_permalink( $shipment ) { + return $shipment->get_edit_shipment_url(); + } + + private static function get_shipment_statuses() { + return array_map( array( 'Vendidero\Germanized\Shipments\Api', 'remove_status_prefix' ), array_keys( wc_gzd_get_shipment_statuses() ) ); + } + + /** + * Checks if a given request has access to get a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function get_item_permissions_check( $request ) { + if ( ! $this->check_permissions( 'shipment', 'read', $request['id'] ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_view', _x( 'Sorry, you are not allowed to view this resource.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * Retrieves a shipment by id. + * + * @param int $shipment_id + * + * @return Shipment|false + */ + private function get_shipment( $shipment_id ) { + $shipment = wc_gzd_get_shipment( $shipment_id ); + + return $shipment; + } + + /** + * Checks if a given request has access to get a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function get_items_permissions_check( $request ) { + if ( ! $this->check_permissions() ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_view', _x( 'Sorry, you cannot list resources.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + protected function check_permissions( $object_type = 'shipment', $context = 'read', $object_id = 0 ) { + if ( 'delete' === $context || 'edit' === $context ) { + $post_type_object = get_post_type_object( 'shop_order' ); + $capped = 'delete' === $context ? $post_type_object->cap->delete_posts : $post_type_object->cap->edit_posts; + $permission = current_user_can( $capped, $object_id ); + } else { + $permission = wc_rest_check_post_permissions( 'shop_order', $context ); + } + + return apply_filters( 'woocommerce_gzd_shipments_rest_check_permissions', $permission, $object_type, $context, $object_id ); + } + + /** + * Retrieves a collection of items. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * @since 4.7.0 + */ + public function get_items( $request ) { + $prepared_args = array( + 'limit' => $request['per_page'], + 'paginate' => true, + 'type' => $request['type'], + 'order_id' => $request['order_id'], + 'search' => $request['search'], + 'status' => $request['status'], + 'order' => $request['order'], + 'orderby' => $request['orderby'], + 'count_total' => true, + ); + + if ( ! empty( $prepared_args['search'] ) ) { + $prepared_args['search'] = '*' . $prepared_args['search'] . '*'; + } + + if ( ! empty( $request['offset'] ) ) { + $prepared_args['offset'] = $request['offset']; + } else { + $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['limit']; + } + + $objects = array(); + $query = new \Vendidero\Germanized\Shipments\ShipmentQuery( $prepared_args ); + $shipments = $query->get_shipments(); + + if ( ! empty( $shipments ) ) { + foreach ( $shipments as $shipment ) { + if ( ! $this->check_permissions( 'shipment', 'read', $shipment->get_id() ) ) { + continue; + } + + $objects[] = $this->prepare_object_for_response( $shipment, $request ); + } + } + + $page = (int) $request['page']; + $max_pages = $query->get_max_num_pages(); + + $response = rest_ensure_response( $objects ); + $response->header( 'X-WP-Total', $query->get_total() ); + $response->header( 'X-WP-TotalPages', (int) $max_pages ); + + $base = $this->rest_base; + $attrib_prefix = '(?P<'; + if ( strpos( $base, $attrib_prefix ) !== false ) { + $attrib_names = array(); + preg_match( '/\(\?P<[^>]+>.*\)/', $base, $attrib_names, PREG_OFFSET_CAPTURE ); + foreach ( $attrib_names as $attrib_name_match ) { + $beginning_offset = strlen( $attrib_prefix ); + $attrib_name_end = strpos( $attrib_name_match[0], '>', $attrib_name_match[1] ); + $attrib_name = substr( $attrib_name_match[0], $beginning_offset, $attrib_name_end - $beginning_offset ); + if ( isset( $request[ $attrib_name ] ) ) { + $base = str_replace( "(?P<$attrib_name>[\d]+)", $request[ $attrib_name ], $base ); + } + } + } + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ) ); + + if ( $page > 1 ) { + $prev_page = $page - 1; + if ( $prev_page > $max_pages ) { + $prev_page = $max_pages; + } + $prev_link = add_query_arg( 'page', $prev_page, $base ); + $response->link_header( 'prev', $prev_link ); + } + if ( $max_pages > $page ) { + $next_page = $page + 1; + $next_link = add_query_arg( 'page', $next_page, $base ); + $response->link_header( 'next', $next_link ); + } + + return $response; + } + + /** + * Checks if a given request has access to update a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function update_item_permissions_check( $request ) { + if ( ! $this->check_permissions( 'shipment', 'edit', $request['id'] ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_edit', _x( 'Sorry, you are not allowed to edit this resource.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * Checks if a given request has access to create a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function create_item_permissions_check( $request ) { + if ( ! $this->check_permissions( 'shipment', 'create' ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_view', _x( 'Sorry, you are not allowed to create resources.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * @param $request + * @param boolean $creating + * + * @return Shipment + * @throws \WC_REST_Exception + */ + protected function prepare_object_for_database( $request, $creating = false ) { + $id = isset( $request['id'] ) ? absint( $request['id'] ) : false; + + // Type is the most important part here because we need to be using the correct class and methods. + if ( isset( $request['type'] ) ) { + $shipment = ShipmentFactory::get_shipment( $id, $request['type'] ); + } elseif ( $id ) { + $shipment = wc_gzd_get_shipment( $id ); + } else { + $shipment = ShipmentFactory::get_shipment( false ); + } + + if ( ! $shipment ) { + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_id', _x( 'There was an error while creating the shipment.', 'shipments', 'woocommerce-germanized' ) ); + } + + if ( isset( $request['order_id'] ) ) { + $shipment->set_order_id( absint( wp_unslash( $request['order_id'] ) ) ); + } + + if ( $creating ) { + $order_shipment = $shipment->get_order_shipment(); + + if ( ! $order_shipment ) { + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_id', _x( 'This order does not exist.', 'shipments', 'woocommerce-germanized' ) ); + } + + if ( 'return' === $shipment->get_type() ) { + if ( ! $order_shipment->needs_return() ) { + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_id', _x( 'This order does need a return.', 'shipments', 'woocommerce-germanized' ) ); + } + } else { + if ( ! $order_shipment->needs_shipping() ) { + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_id', _x( 'This order does need shipping.', 'shipments', 'woocommerce-germanized' ) ); + } + } + + $shipment->sync(); + } + + if ( isset( $request['shipping_provider'] ) ) { + $provider = wc_clean( wp_unslash( $request['shipping_provider'] ) ); + + if ( $provider = wc_gzd_get_shipping_provider( $provider ) ) { + $shipment->set_shipping_provider( $provider ); + } + } + + if ( isset( $request['shipping_method'] ) ) { + $shipment->set_shipping_method( wc_clean( wp_unslash( $request['shipping_method'] ) ) ); + } + + if ( isset( $request['packaging_id'] ) ) { + $packaging_id = absint( wp_unslash( $request['packaging_id'] ) ); + + if ( $packaging = wc_gzd_get_packaging( $packaging_id ) ) { + $shipment->set_packaging_id( $packaging_id ); + } + } + + if ( isset( $request['packaging_weight'] ) ) { + $shipment->set_packaging_weight( wc_clean( wp_unslash( $request['packaging_weight'] ) ) ); + } + + if ( isset( $request['tracking_id'] ) ) { + $shipment->set_tracking_id( wc_clean( wp_unslash( $request['tracking_id'] ) ) ); + } + + if ( isset( $request['dimensions'] ) ) { + if ( isset( $request['dimensions']['length'] ) ) { + $shipment->set_length( wc_clean( wp_unslash( $request['dimensions']['length'] ) ) ); + } + if ( isset( $request['dimensions']['width'] ) ) { + $shipment->set_width( wc_clean( wp_unslash( $request['dimensions']['width'] ) ) ); + } + if ( isset( $request['dimensions']['height'] ) ) { + $shipment->set_height( wc_clean( wp_unslash( $request['dimensions']['height'] ) ) ); + } + } + + if ( isset( $request['dimension_unit'] ) ) { + $shipment->set_dimension_unit( wc_clean( wp_unslash( $request['dimension_unit'] ) ) ); + } + + if ( isset( $request['weight'] ) ) { + $shipment->set_weight( wc_clean( wp_unslash( $request['weight'] ) ) ); + } + + if ( isset( $request['weight_unit'] ) ) { + $shipment->set_weight_unit( wc_clean( wp_unslash( $request['weight_unit'] ) ) ); + } + + if ( isset( $request['address'] ) && is_array( $request['address'] ) ) { + $shipment->set_address( wc_clean( wp_unslash( $request['address'] ) ) ); + + if ( isset( $request['address']['country'] ) ) { + $shipment->set_country( wc_clean( wp_unslash( $request['address']['country'] ) ) ); + } + } + + if ( isset( $request['total'] ) ) { + $shipment->set_total( wc_clean( wp_unslash( $request['total'] ) ) ); + } + + if ( isset( $request['subtotal'] ) ) { + $shipment->set_subtotal( wc_clean( wp_unslash( $request['subtotal'] ) ) ); + } + + if ( isset( $request['additional_total'] ) ) { + $shipment->set_additional_total( wc_clean( wp_unslash( $request['additional_total'] ) ) ); + } + + if ( is_a( $shipment, 'Vendidero\Germanized\Shipments\ReturnShipment' ) ) { + if ( isset( $request['sender_address'] ) && is_array( $request['sender_address'] ) ) { + $shipment->set_sender_address( wc_clean( wp_unslash( $request['sender_address'] ) ) ); + } + + if ( isset( $request['is_customer_requested'] ) ) { + $shipment->set_is_customer_requested( wc_clean( wp_unslash( $request['is_customer_requested'] ) ) ); + } + } + + if ( ! empty( $request['date_created'] ) ) { + $date = rest_parse_date( wc_clean( wp_unslash( $request['date_created'] ) ) ); + + if ( $date ) { + $shipment->set_date_created( $date ); + } + } + + if ( ! empty( $request['date_created_gmt'] ) ) { + $date = rest_parse_date( wc_clean( wp_unslash( $request['date_created_gmt'] ) ), true ); + + if ( $date ) { + $shipment->set_date_created( $date ); + } + } + + if ( ! empty( $request['est_delivery_date'] ) ) { + $date = rest_parse_date( wc_clean( wp_unslash( $request['est_delivery_date'] ) ) ); + + if ( $date ) { + $shipment->set_est_delivery_date( $date ); + } + } + + if ( ! empty( $request['est_delivery_date_gmt'] ) ) { + $date = rest_parse_date( wc_clean( wp_unslash( $request['est_delivery_date_gmt'] ) ), true ); + + if ( $date ) { + $shipment->set_est_delivery_date( $date ); + } + } + + if ( ! empty( $request['date_sent'] ) ) { + $date = rest_parse_date( wc_clean( wp_unslash( $request['date_sent'] ) ) ); + + if ( $date ) { + $shipment->set_date_sent( $date ); + } + } + + if ( ! empty( $request['date_sent_gmt'] ) ) { + $date = rest_parse_date( wc_clean( wp_unslash( $request['date_sent_gmt'] ) ), true ); + + if ( $date ) { + $shipment->set_date_sent( $date ); + } + } + + if ( ! empty( $request['items'] ) && is_array( $request['items'] ) ) { + foreach ( $request['items'] as $item ) { + if ( is_array( $item ) ) { + if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) { + $shipment->remove_item( $item['id'] ); + } else { + $this->set_item( $shipment, $item ); + } + } + } + } elseif ( $creating ) { + $shipment->sync_items(); + } + + // Update the status at last + if ( isset( $request['status'] ) ) { + $status = str_replace( 'gzd-', '', wc_clean( wp_unslash( $request['status'] ) ) ); + + if ( in_array( $status, self::get_shipment_statuses(), true ) ) { + $shipment->set_status( $status ); + } + } + + if ( isset( $request['meta_data'] ) && is_array( $request['meta_data'] ) ) { + foreach ( $request['meta_data'] as $meta ) { + $meta = wc_clean( wp_unslash( $meta ) ); + + if ( isset( $meta['key'] ) ) { + $value = isset( $meta['value'] ) ? $meta['value'] : null; + $shipment->update_meta_data( $meta['key'], $value, isset( $meta['id'] ) ? $meta['id'] : '' ); + } + } + } + + if ( $shipment->get_item_count() <= 0 ) { + $shipment->delete( true ); + + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_id', _x( 'This shipment does not contain any items and was deleted.', 'shipments', 'woocommerce-germanized' ) ); + } + + /** + * Filters a shipment before it is inserted via the REST API. + * + * @param Shipment $shipment Shipment object. + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'woocommerce_gzd_rest_pre_insert_shipment_object', $shipment, $request ); + } + + /** + * Wrapper method to create/update order items. + * When updating, the item ID provided is checked to ensure it is associated + * with the order. + * + * @param Shipment $shipment order object. + * @param array $posted item provided in the request body. + * + * @throws \WC_REST_Exception If item ID is not associated with order. + */ + protected function set_item( $shipment, $posted ) { + if ( ! empty( $posted['id'] ) ) { + $action = 'update'; + } else { + $action = 'create'; + } + + $item = null; + + // Verify provided line item ID is associated with order. + if ( 'update' === $action ) { + $item = $shipment->get_item( absint( $posted['id'] ) ); + + if ( ! $item ) { + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_item_id', _x( 'Shipment item ID provided is not associated with shipment.', 'shipments', 'woocommerce-germanized' ), 400 ); + } + } + + if ( is_null( $item ) ) { + if ( 'return' === $shipment->get_type() ) { + $item = new \Vendidero\Germanized\Shipments\ShipmentReturnItem(); + } else { + $item = new \Vendidero\Germanized\Shipments\ShipmentItem(); + } + } + + if ( isset( $posted['order_item_id'] ) ) { + $item->set_order_item_id( absint( wp_unslash( $posted['order_item_id'] ) ) ); + } + + $item->set_shipment( $shipment ); + + /** + * Sync quantity first. + */ + if ( 'create' === $action ) { + $quantity = isset( $posted['quantity'] ) ? absint( wp_unslash( $posted['quantity'] ) ) : -1; + $quantity_left = 0; + + if ( $order_shipment = $shipment->get_order_shipment() ) { + if ( 'return' === $shipment->get_type() ) { + $quantity_left = $order_shipment->get_item_quantity_left_for_returning( $item->get_order_item_id() ); + } elseif ( $order_item = $item->get_order_item() ) { + $quantity_left = $order_shipment->get_item_quantity_left_for_shipping( $order_item ); + } + + if ( -1 !== $quantity ) { + if ( $quantity > $quantity_left ) { + $quantity = $quantity_left; + } + } else { + $quantity = $quantity_left; + } + } + + if ( $quantity <= 0 ) { + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_item_id', _x( 'This order item does not need shipping/returning.', 'shipments', 'woocommerce-germanized' ), 400 ); + } + + if ( $shipment->get_item_by_order_item_id( $item->get_order_item_id() ) ) { + throw new \WC_REST_Exception( 'woocommerce_gzd_rest_invalid_item_id', _x( 'The order item is already associated with another item.', 'shipments', 'woocommerce-germanized' ), 400 ); + } + + $item->sync( array( 'quantity' => $quantity ) ); + } else { + if ( $order_shipment = $shipment->get_order_shipment() ) { + $quantity = isset( $posted['quantity'] ) ? absint( wp_unslash( $posted['quantity'] ) ) : $item->get_quantity(); + $quantity_left = 0; + + if ( 'return' === $shipment->get_type() ) { + $quantity_left = $order_shipment->get_item_quantity_left_for_returning( + $item->get_order_item_id(), + array( + 'exclude_current_shipment' => true, + 'shipment_id' => $shipment->get_id(), + ) + ); + } elseif ( $order_item = $item->get_order_item() ) { + $quantity_left = $order_shipment->get_item_quantity_left_for_shipping( + $order_item, + array( + 'exclude_current_shipment' => true, + 'shipment_id' => $shipment->get_id(), + ) + ); + } + + if ( $quantity > $quantity_left ) { + $quantity = $quantity_left; + } + + if ( $quantity <= 0 ) { + $shipment->remove_item( $item->get_id() ); + return; + } + + $shipment->update_item_quantity( $item->get_id(), $quantity ); + } + } + + $props_to_set = array( + 'name', + 'product_id', + 'sku', + 'total', + 'subtotal', + 'weight', + 'hs_code', + 'manufacture_country', + 'return_reason_code', + ); + + foreach ( $props_to_set as $prop ) { + $setter = "set_{$prop}"; + + if ( isset( $posted[ $prop ] ) && is_callable( array( $item, $setter ) ) ) { + $item->{$setter}( wc_clean( wp_unslash( $posted[ $prop ] ) ) ); + } + } + + if ( isset( $posted['dimensions'] ) && is_array( $posted['dimensions'] ) ) { + if ( isset( $posted['dimensions']['length'] ) ) { + $item->set_length( wc_clean( wp_unslash( $posted['dimensions']['length'] ) ) ); + } + if ( isset( $posted['dimensions']['width'] ) ) { + $item->set_width( wc_clean( wp_unslash( $posted['dimensions']['width'] ) ) ); + } + if ( isset( $posted['dimensions']['height'] ) ) { + $item->set_height( wc_clean( wp_unslash( $posted['dimensions']['height'] ) ) ); + } + } + + if ( isset( $posted['attributes'] ) && is_array( $posted['attributes'] ) ) { + $attributes_to_save = array(); + + foreach ( $posted['attributes'] as $attribute ) { + $attribute = wc_clean( wp_unslash( $attribute ) ); + $attribute = wp_parse_args( + $attribute, + array( + 'key' => '', + 'value' => '', + 'label' => '', + 'order_item_meta_id' => 0, + ) + ); + + $attributes_to_save[] = array_intersect_key( $attribute, array_flip( array( 'key', 'value', 'label', 'order_item_meta_id' ) ) ); + } + + $item->set_attributes( $attributes_to_save ); + } + + if ( ! empty( $posted['meta_data'] ) && is_array( $posted['meta_data'] ) ) { + foreach ( $posted['meta_data'] as $meta ) { + $meta = wc_clean( wp_unslash( $meta ) ); + + if ( isset( $meta['key'] ) ) { + $value = isset( $meta['value'] ) ? $meta['value'] : null; + $item->update_meta_data( $meta['key'], $value, isset( $meta['id'] ) ? $meta['id'] : '' ); + } + } + } + + do_action( 'woocommerce_gzd_rest_set_shipment_item', $item, $posted ); + + // If creating the shipment, add the item to it. + if ( 'create' === $action ) { + $shipment->add_item( $item ); + } else { + $item->save(); + } + } + + /** + * Helper method to check if the resource ID associated with the provided item is null. + * Items can be deleted by setting the resource ID to null. + * + * @param array $item Item provided in the request body. + * @return bool True if the item resource ID is null, false otherwise. + */ + protected function item_is_null( $item ) { + $keys = array( 'order_item_id', 'name', 'product_id' ); + + foreach ( $keys as $key ) { + if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) { + return true; + } + } + + return false; + } + + /** + * Save an object data. + * + * @since 3.0.0 + * @param WP_REST_Request $request Full details about the request. + * @param bool $creating If is creating a new object. + * @return Shipment|WP_Error + */ + protected function save_object( $request, $creating = false ) { + try { + $object = $this->prepare_object_for_database( $request, $creating ); + + if ( is_wp_error( $object ) ) { + return $object; + } + + $object->save(); + + return $this->get_object( $object->get_id() ); + } catch ( \WC_Data_Exception $e ) { + return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() ); + } catch ( \WC_REST_Exception $e ) { + return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); + } + } + + /** + * Create a single item. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response + */ + public function create_item( $request ) { + if ( ! empty( $request['id'] ) ) { + /* translators: %s: post type */ + return new WP_Error( 'woocommerce_gzd_rest_shipment_exists', _x( 'Cannot create existing shipment.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 400 ) ); + } + + $object = $this->save_object( $request, true ); + + if ( is_wp_error( $object ) ) { + return $object; + } + + try { + $this->update_additional_fields_for_object( $object, $request ); + + /** + * Fires after a single object is created or updated via the REST API. + * + * @param Shipment $shipment Inserted object. + * @param WP_REST_Request $request Request object. + * @param boolean $creating True when creating object, false when updating. + */ + do_action( 'woocommerce_gzd_rest_insert_shipment_object', $object, $request, true ); + } catch ( \WC_Data_Exception $e ) { + $object->delete(); + return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() ); + } catch ( \WC_REST_Exception $e ) { + $object->delete(); + return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); + } + + $request->set_param( 'context', 'edit' ); + $response = $this->prepare_object_for_response( $object, $request ); + $response = rest_ensure_response( $response ); + $response->set_status( 201 ); + $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) ); + + return $response; + } + + /** + * Update a single post. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response + */ + public function update_item( $request ) { + $shipment = $this->get_object( (int) $request['id'] ); + + if ( ! $shipment || 0 === $shipment->get_id() ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_invalid_id', _x( 'Invalid ID.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 400 ) ); + } + + $shipment = $this->save_object( $request, false ); + + if ( is_wp_error( $shipment ) ) { + return $shipment; + } + + try { + $this->update_additional_fields_for_object( $shipment, $request ); + + /** + * Fires after a single shipment is created or updated via the REST API. + * + * @param Shipment $shipment Inserted object. + * @param WP_REST_Request $request Request object. + * @param boolean $creating True when creating object, false when updating. + */ + do_action( 'woocommerce_gzd_rest_insert_shipment_object', $shipment, $request, false ); + } catch ( \WC_Data_Exception $e ) { + return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() ); + } catch ( \WC_REST_Exception $e ) { + return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) ); + } + + $request->set_param( 'context', 'edit' ); + $response = $this->prepare_object_for_response( $shipment, $request ); + + return rest_ensure_response( $response ); + } + + /** + * Prepares the object for the REST response. + * + * @since 3.0.0 + * @param Shipment $shipment Object data. + * @param WP_REST_Request $request Request object. + * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + */ + protected function prepare_object_for_response( $shipment, $request ) { + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $this->request = $request; + $data = self::prepare_shipment( $shipment, $context ); + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + $response = rest_ensure_response( $data ); + + $response->add_links( $this->prepare_links( $shipment, $request ) ); + + /** + * Filter the shipment data for a response. + * + * @param WP_REST_Response $response The response object. + * @param Shipment $shipment Object data. + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'woocommerce_gzd_rest_prepare_shipment_object', $response, $shipment, $request ); + } + + /** + * Prepare links for the request. + * + * @param Shipment $shipment Object data. + * @param WP_REST_Request $request Request object. + * @return array Links for the given post. + */ + protected function prepare_links( $shipment, $request ) { + $links = array( + 'self' => array( + 'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $shipment->get_id() ) ), + ), + 'collection' => array( + 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ), + ), + ); + + return $links; + } + + /** + * Get a single item. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_Error|WP_REST_Response + */ + public function get_item( $request ) { + $object = $this->get_object( (int) $request['id'] ); + + if ( ! $object || 0 === $object->get_id() ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_invalid_id', _x( 'Invalid ID.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 404 ) ); + } + + $data = $this->prepare_object_for_response( $object, $request ); + $response = rest_ensure_response( $data ); + + return $response; + } + + /** + * Checks if a given request has access to delete a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function delete_item_permissions_check( $request ) { + if ( ! $this->check_permissions( 'shipment', 'delete', $request['id'] ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_delete', _x( 'Sorry, you are not allowed to delete this resource.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * Deletes one item from the collection. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * @since 4.7.0 + */ + public function delete_item( $request ) { + $force = (bool) $request['force']; + $shipment = $this->get_shipment( (int) $request['id'] ); + + if ( ! $shipment ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_invalid_id', _x( 'Invalid ID.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 404 ) ); + } + + if ( ! $shipment->delete( $force ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_delete', _x( 'The shipment cannot be deleted.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 500 ) ); + } + + return rest_ensure_response( self::prepare_shipment( $shipment ) ); + } + + /** + * Checks if a given request has access to get a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function get_label_permissions_check( $request ) { + if ( ! $this->check_permissions( 'shipment_label', 'read', $request['id'] ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_view', _x( 'Sorry, you are not allowed to view this resource.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * Retrieves one item from the collection. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * @since 4.7.0 + */ + public function get_label( $request ) { + $shipment = $this->get_shipment( (int) $request['id'] ); + + if ( ! $shipment ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_invalid_id', _x( 'Invalid ID.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 404 ) ); + } + + $label = $shipment->get_label(); + + if ( ! $label ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_invalid_id', _x( 'Invalid ID.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 404 ) ); + } + + $label_data = self::prepare_label( $label ); + + return rest_ensure_response( $label_data ); + } + + public function delete_label( $request ) { + $force = (bool) $request['force']; + $shipment = $this->get_shipment( (int) $request['id'] ); + + if ( ! $shipment ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_invalid_id', _x( 'Invalid ID.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 404 ) ); + } + + $label = $shipment->get_label(); + + if ( ! $label || ! $label->delete( $force ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_delete', _x( 'The label cannot be deleted.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 500 ) ); + } + + return rest_ensure_response( self::prepare_label( $label ) ); + } + + public function create_label( $request ) { + $shipment = $this->get_shipment( (int) $request['id'] ); + + if ( ! $shipment ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_invalid_id', _x( 'Invalid ID.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 404 ) ); + } + + if ( ! $shipment->supports_label() || ! $shipment->needs_label() ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_label_exists', _x( 'Label already exists, please delete first.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 404 ) ); + } + + $request->set_param( 'context', 'edit' ); + + $args = wc_clean( wp_unslash( $request['args'] ) ); + $args = empty( $args ) ? false : $args; + $result = $shipment->create_label( $args ); + + if ( is_wp_error( $result ) ) { + $result = wc_gzd_get_shipment_error( $result ); + } + + if ( is_wp_error( $result ) && ! $result->is_soft_error() ) { + $message = implode( ' | ', $result->get_error_messages() ); + + return new WP_Error( 'woocommerce_gzd_rest_shipment_label_create', $message, array( 'status' => 500 ) ); + } + + $label = $shipment->get_label(); + + if ( ! $label ) { + return new WP_Error( 'woocommerce_gzd_rest_shipment_label_create', _x( 'There was an error creating the label.', 'shipments', 'woocommerce-germanized' ), array( 'status' => 500 ) ); + } + + $response = self::prepare_label( $label, $request ); + $response = rest_ensure_response( $response ); + + $response->set_status( 201 ); + $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d/label', $this->namespace, $this->rest_base, $label->get_shipment_id() ) ) ); + + return $response; + } + + /** + * Checks if a given request has access to create a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function create_label_permissions_check( $request ) { + if ( ! $this->check_permissions( 'shipment_label', 'create' ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_delete', _x( 'Sorry, you are not allowed to create resources.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * Checks if a given request has access to delete a specific item. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. + * @since 4.7.0 + */ + public function delete_label_permissions_check( $request ) { + if ( ! $this->check_permissions( 'shipment_label', 'delete', $request['id'] ) ) { + return new WP_Error( 'woocommerce_gzd_rest_cannot_delete', _x( 'Sorry, you are not allowed to delete this resource.', 'shipments', 'woocommerce-germanized' ), array( 'status' => rest_authorization_required_code() ) ); + } + + return true; + } + + /** + * Retrieves the item's schema, conforming to JSON Schema. + * + * @return array Item schema data. + * @since 4.7.0 + */ + public function get_item_schema() { + return $this->add_additional_fields_schema( self::get_single_item_schema() ); + } + + /** + * @param Shipment $shipment + * @param string $context + * @param bool|int $dp + * + * @return array + */ + public static function prepare_shipment( $shipment, $context = 'view', $dp = false ) { + $item_data = array(); + + foreach ( $shipment->get_items() as $item ) { + $item_data[] = array( + 'id' => $item->get_id(), + 'name' => $item->get_name( $context ), + 'order_item_id' => $item->get_order_item_id( $context ), + 'product_id' => $item->get_product_id( $context ), + 'sku' => $item->get_sku( $context ), + 'quantity' => $item->get_quantity( $context ), + 'total' => wc_format_decimal( $item->get_total( $context ), $dp ), + 'subtotal' => wc_format_decimal( $item->get_subtotal( $context ), $dp ), + 'weight' => wc_format_decimal( $item->get_weight( $context ), $dp ), + 'dimensions' => array( + 'length' => wc_format_decimal( $item->get_length( $context ), $dp ), + 'width' => wc_format_decimal( $item->get_width( $context ), $dp ), + 'height' => wc_format_decimal( $item->get_height( $context ), $dp ), + ), + 'hs_code' => $item->get_hs_code( $context ), + 'manufacture_country' => $item->get_manufacture_country( $context ), + 'attributes' => $item->get_attributes( $context ), + 'meta_data' => $item->get_meta_data(), + ); + } + + return array( + 'id' => $shipment->get_id(), + 'shipment_number' => $shipment->get_shipment_number(), + 'date_created' => wc_rest_prepare_date_response( $shipment->get_date_created( $context ), false ), + 'date_created_gmt' => wc_rest_prepare_date_response( $shipment->get_date_created( $context ) ), + 'date_sent' => wc_rest_prepare_date_response( $shipment->get_date_sent( $context ), false ), + 'date_sent_gmt' => wc_rest_prepare_date_response( $shipment->get_date_sent( $context ) ), + 'est_delivery_date' => wc_rest_prepare_date_response( $shipment->get_est_delivery_date( $context ), false ), + 'est_delivery_date_gmt' => wc_rest_prepare_date_response( $shipment->get_est_delivery_date( $context ) ), + 'total' => wc_format_decimal( $shipment->get_total( $context ), $dp ), + 'subtotal' => wc_format_decimal( $shipment->get_subtotal( $context ), $dp ), + 'additional_total' => wc_format_decimal( $shipment->get_additional_total( $context ), $dp ), + 'order_id' => $shipment->get_order_id( $context ), + 'order_number' => $shipment->get_order_number(), + 'weight' => wc_format_decimal( $shipment->get_weight( $context ), $dp ), + 'content_weight' => wc_format_decimal( $shipment->get_content_weight(), $dp ), + 'weight_unit' => $shipment->get_weight_unit( $context ), + 'packaging_id' => $shipment->get_packaging_id( $context ), + 'packaging_weight' => $shipment->get_packaging_weight( $context ), + 'status' => $shipment->get_status( $context ), + 'tracking_id' => $shipment->get_tracking_id( $context ), + 'tracking_url' => $shipment->get_tracking_url(), + 'shipping_provider' => $shipment->get_shipping_provider( $context ), + 'content_dimensions' => array( + 'length' => wc_format_decimal( $shipment->get_content_length(), $dp ), + 'width' => wc_format_decimal( $shipment->get_content_width(), $dp ), + 'height' => wc_format_decimal( $shipment->get_content_height(), $dp ), + ), + 'dimensions' => array( + 'length' => wc_format_decimal( $shipment->get_length( $context ), $dp ), + 'width' => wc_format_decimal( $shipment->get_width( $context ), $dp ), + 'height' => wc_format_decimal( $shipment->get_height( $context ), $dp ), + ), + 'package_dimensions' => array( + 'length' => wc_format_decimal( $shipment->get_package_length(), $dp ), + 'width' => wc_format_decimal( $shipment->get_package_width(), $dp ), + 'height' => wc_format_decimal( $shipment->get_package_height(), $dp ), + ), + 'dimension_unit' => $shipment->get_dimension_unit( $context ), + 'address' => $shipment->get_address( $context ), + 'sender_address' => 'return' === $shipment->get_type() ? $shipment->get_sender_address( $context ) : array(), + 'is_customer_requested' => 'return' === $shipment->get_type() ? $shipment->get_is_customer_requested( $context ) : false, + 'items' => $item_data, + 'meta_data' => $shipment->get_meta_data(), + ); + } + + /** + * @param Label $label + * + * @return + */ + private static function get_label_file( $label, $file_type = '' ) { + $result = array( + 'file' => '', + 'filename' => $label->get_filename( $file_type ), + 'path' => $label->get_path( 'view', $file_type ), + 'type' => $file_type, + ); + + if ( $file = $label->get_file( $file_type ) ) { + try { + $content = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents + $result['file'] = chunk_split( base64_encode( $content ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + } catch ( \Exception $ex ) { + $result['file'] = ''; + } + } + + return $result; + } + + /** + * @param Label $label + * @param string $context + * @param bool|int $dp + * + * @return array + */ + public static function prepare_label( $label, $context = 'view', $dp = false ) { + $label_data = array( + 'id' => $label->get_id(), + 'date_created' => wc_rest_prepare_date_response( $label->get_date_created( $context ), false ), + 'date_created_gmt' => wc_rest_prepare_date_response( $label->get_date_created( $context ) ), + 'weight' => wc_format_decimal( $label->get_weight( $context ), $dp ), + 'net_weight' => wc_format_decimal( $label->get_net_weight( $context ), $dp ), + 'dimensions' => array( + 'length' => wc_format_decimal( $label->get_length( $context ), $dp ), + 'width' => wc_format_decimal( $label->get_width( $context ), $dp ), + 'height' => wc_format_decimal( $label->get_height( $context ), $dp ), + ), + 'shipment_id' => $label->get_shipment_id( $context ), + 'parent_id' => $label->get_parent_id( $context ), + 'product_id' => $label->get_product_id( $context ), + 'number' => $label->get_number( $context ), + 'type' => $label->get_type(), + 'shipping_provider' => $label->get_shipping_provider( $context ), + 'created_via' => $label->get_created_via( $context ), + 'services' => $label->get_services( $context ), + 'additional_file_types' => array(), + 'files' => array( self::get_label_file( $label ) ), + ); + + foreach ( $label->get_additional_file_types() as $file_type ) { + if ( 'default' === $file_type ) { + continue; + } + + $label_file = self::get_label_file( $label, $file_type ); + + if ( ! empty( $label_file['file'] ) ) { + $label_data['files'][] = $label_file; + $label_data['additional_file_types'][] = $file_type; + } + } + + return $label_data; + } + + /** + * Get the query params for collections. + * + * @return array + */ + public function get_collection_params() { + $params = parent::get_collection_params(); + + $params['context']['default'] = 'view'; + + $params['offset'] = array( + 'description' => _x( 'Offset the result set by a specific number of items.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order'] = array( + 'default' => 'desc', + 'description' => _x( 'Order sort attribute ascending or descending.', 'shipments', 'woocommerce-germanized' ), + 'enum' => array( 'asc', 'desc' ), + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['orderby'] = array( + 'default' => 'date_created', + 'description' => _x( 'Sort collection by object attribute.', 'shipments', 'woocommerce-germanized' ), + 'enum' => array( + 'country', + 'status', + 'tracking_id', + 'date_created', + 'order_id', + 'weight', + ), + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['order_id'] = array( + 'description' => _x( 'Limit result set to shipments belonging to a certain order id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'sanitize_callback' => 'absint', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['status'] = array( + 'description' => _x( 'Limit result set to shipments having a certain status.', 'shipments', 'woocommerce-germanized' ), + 'enum' => self::get_shipment_statuses(), + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + $params['type'] = array( + 'description' => _x( 'Limit result set to shipments of a certain type.', 'shipments', 'woocommerce-germanized' ), + 'default' => 'simple', + 'enum' => wc_gzd_get_shipment_types(), + 'sanitize_callback' => 'sanitize_key', + 'type' => 'string', + 'validate_callback' => 'rest_validate_request_arg', + ); + return $params; + } + + /** + * Get the schema of a single shipment + * + * @return array + */ + public static function get_single_item_schema() { + $weight_unit = get_option( 'woocommerce_weight_unit' ); + $dimension_unit = get_option( 'woocommerce_dimension_unit' ); + + return array( + 'description' => _x( 'Single shipment.', 'shipment', 'woocommerce-germanized' ), + 'context' => array( 'view', 'edit' ), + 'readonly' => false, + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => _x( 'Shipment ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'shipment_number' => array( + 'description' => _x( 'Shipment number.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'order_id' => array( + 'description' => _x( 'Shipment order id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'order_number' => array( + 'description' => _x( 'Shipment order number.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'status' => array( + 'description' => _x( 'Shipment status.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'enum' => self::get_shipment_statuses(), + ), + 'tracking_id' => array( + 'description' => _x( 'Shipment tracking id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'tracking_url' => array( + 'description' => _x( 'Shipment tracking url.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'shipping_provider' => array( + 'description' => _x( 'Shipment shipping provider.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'date_created' => array( + 'description' => _x( "The date the shipment was created, in the site's timezone.", 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_created_gmt' => array( + 'description' => _x( 'The date the shipment was created, as GMT.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_sent' => array( + 'description' => _x( "The date the shipment was sent, in the site's timezone.", 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_sent_gmt' => array( + 'description' => _x( 'The date the shipment was sent, as GMT.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'est_delivery_date' => array( + 'description' => _x( "The estimated delivery date of the shipment, in the site's timezone.", 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'est_delivery_date_gmt' => array( + 'description' => _x( 'The estimated delivery date of the shipment, as GMT.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'type' => array( + 'description' => _x( 'Shipment type, e.g. simple or return.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'enum' => wc_gzd_get_shipment_types(), + ), + 'is_customer_requested' => array( + 'description' => _x( 'Return shipment is requested by customer.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit' ), + ), + 'sender_address' => array( + 'description' => _x( 'Return sender address.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'properties' => array( + 'first_name' => array( + 'description' => _x( 'First name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'last_name' => array( + 'description' => _x( 'Last name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'company' => array( + 'description' => _x( 'Company name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'address_1' => array( + 'description' => _x( 'Address line 1', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'address_2' => array( + 'description' => _x( 'Address line 2', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'city' => array( + 'description' => _x( 'City name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'state' => array( + 'description' => _x( 'ISO code or name of the state, province or district.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'postcode' => array( + 'description' => _x( 'Postal code.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'country' => array( + 'description' => _x( 'Country code in ISO 3166-1 alpha-2 format.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'customs_reference_number' => array( + 'description' => _x( 'Customs reference number.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + 'weight' => array( + 'description' => _x( 'Shipment weight.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'content_weight' => array( + 'description' => _x( 'Shipment content weight.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'content_dimensions' => array( + 'description' => _x( 'Shipment content dimensions.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => array( + 'length' => array( + 'description' => _x( 'Shipment content length.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'width' => array( + 'description' => _x( 'Shipment content width.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'height' => array( + 'description' => _x( 'Shipment content height.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ), + 'weight_unit' => array( + 'description' => _x( 'Shipment weight unit.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'default' => $weight_unit, + ), + 'packaging_id' => array( + 'description' => _x( 'Shipment packaging id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), + 'packaging_weight' => array( + 'description' => _x( 'Shipment packaging weight.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'total' => array( + 'description' => _x( 'Shipment total.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'subtotal' => array( + 'description' => _x( 'Shipment subtotal.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'additional_total' => array( + 'description' => _x( 'Shipment additional total.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'version' => array( + 'description' => _x( 'Shipment version.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'shipping_method' => array( + 'description' => _x( 'Shipment shipping method.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'dimensions' => array( + 'description' => _x( 'Shipment dimensions.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'properties' => array( + 'length' => array( + 'description' => _x( 'Shipment length.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'width' => array( + 'description' => _x( 'Shipment width.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'height' => array( + 'description' => _x( 'Shipment height.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + 'package_dimensions' => array( + 'description' => _x( 'Shipment package dimensions.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'properties' => array( + 'length' => array( + 'description' => _x( 'Shipment package length.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + ), + 'width' => array( + 'description' => _x( 'Shipment package width.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + ), + 'height' => array( + 'description' => _x( 'Shipment package height.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + ), + ), + ), + 'dimension_unit' => array( + 'description' => _x( 'Shipment dimension unit.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'default' => $dimension_unit, + ), + 'address' => array( + 'description' => _x( 'Shipping address.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'properties' => array( + 'first_name' => array( + 'description' => _x( 'First name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'last_name' => array( + 'description' => _x( 'Last name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'company' => array( + 'description' => _x( 'Company name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'address_1' => array( + 'description' => _x( 'Address line 1', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'address_2' => array( + 'description' => _x( 'Address line 2', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'city' => array( + 'description' => _x( 'City name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'state' => array( + 'description' => _x( 'ISO code or name of the state, province or district.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'postcode' => array( + 'description' => _x( 'Postal code.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'country' => array( + 'description' => _x( 'Country code in ISO 3166-1 alpha-2 format.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'customs_reference_number' => array( + 'description' => _x( 'Customs reference number.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + 'meta_data' => array( + 'description' => _x( 'Meta data.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => _x( 'Meta ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'key' => array( + 'description' => _x( 'Meta key.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'value' => array( + 'description' => _x( 'Meta value.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'mixed', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + ), + 'items' => array( + 'description' => _x( 'Shipment items.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => _x( 'Item ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => _x( 'Item name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'mixed', + 'context' => array( 'view', 'edit' ), + ), + 'order_item_id' => array( + 'description' => _x( 'Order Item ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), + 'product_id' => array( + 'description' => _x( 'Product ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'mixed', + 'context' => array( 'view', 'edit' ), + ), + 'quantity' => array( + 'description' => _x( 'Quantity.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), + 'weight' => array( + 'description' => _x( 'Item weight.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'sku' => array( + 'description' => _x( 'Item SKU.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'total' => array( + 'description' => _x( 'Item total.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'subtotal' => array( + 'description' => _x( 'Item subtotal.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'hs_code' => array( + 'description' => _x( 'Item HS Code (customs).', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'manufacture_country' => array( + 'description' => _x( 'Item country of manufacture in ISO 3166-1 alpha-2 format.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'dimensions' => array( + 'description' => _x( 'Item dimensions.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'properties' => array( + 'length' => array( + 'description' => _x( 'Item length.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'width' => array( + 'description' => _x( 'Item width.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'height' => array( + 'description' => _x( 'Item height.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + 'attributes' => array( + 'description' => _x( 'Item attributes.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'properties' => array( + 'key' => array( + 'description' => _x( 'Attribute key.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'value' => array( + 'description' => _x( 'Attribute value.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'label' => array( + 'description' => _x( 'Attribute label.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'order_item_meta_id' => array( + 'description' => _x( 'Order item meta id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + 'meta_data' => array( + 'description' => _x( 'Shipment item meta data.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => _x( 'Meta ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'key' => array( + 'description' => _x( 'Meta key.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'value' => array( + 'description' => _x( 'Meta value.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'mixed', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + ), + ), + ), + ), + ), + ); + } + + public function get_public_item_label_schema() { + return array( + 'description' => _x( 'Shipment label.', 'shipment', 'woocommerce-germanized' ), + 'context' => array( 'view', 'edit' ), + 'readonly' => false, + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => _x( 'Label ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_created' => array( + 'description' => _x( "The date the label was created, in the site's timezone.", 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'date_created_gmt' => array( + 'description' => _x( 'The date the label was created, as GMT.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'shipment_id' => array( + 'description' => _x( 'Shipment id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'parent_id' => array( + 'description' => _x( 'Parent id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + ), + 'product_id' => array( + 'description' => _x( 'Label product id.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'number' => array( + 'description' => _x( 'Label number.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'shipping_provider' => array( + 'description' => _x( 'Shipping provider.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'weight' => array( + 'description' => _x( 'Weight.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'net_weight' => array( + 'description' => _x( 'Net weight.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'created_via' => array( + 'description' => _x( 'Created via.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'is_trackable' => array( + 'description' => _x( 'Is trackable?', 'shipments', 'woocommerce-germanized' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'additional_file_types' => array( + 'description' => _x( 'Additional file types', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'type' => 'string', + ), + ), + 'files' => array( + 'description' => _x( 'Label file data.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'type' => 'object', + 'properties' => array( + 'path' => array( + 'description' => _x( 'File path.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'filename' => array( + 'description' => _x( 'File name.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'file' => array( + 'description' => _x( 'The file data (base64 encoded).', 'shipments', 'woocommerce-germanized' ), + 'type' => 'binary', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'type' => array( + 'description' => _x( 'File type.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ), + ), + 'type' => array( + 'description' => _x( 'Label type, e.g. simple or return.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'dimensions' => array( + 'description' => _x( 'Label dimensions.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'object', + 'context' => array( 'view', 'edit' ), + 'properties' => array( + 'length' => array( + 'description' => _x( 'Label length.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'width' => array( + 'description' => _x( 'Label width.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'height' => array( + 'description' => _x( 'Label height.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + 'services' => array( + 'description' => _x( 'Label services.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'items' => array( + 'type' => 'string', + ), + ), + 'meta_data' => array( + 'description' => _x( 'Label meta data.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => _x( 'Meta ID.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'key' => array( + 'description' => _x( 'Meta key.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'value' => array( + 'description' => _x( 'Meta value.', 'shipments', 'woocommerce-germanized' ), + 'type' => 'mixed', + 'context' => array( 'view', 'edit' ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ReturnReason.php b/packages/woocommerce-germanized-shipments/src/ReturnReason.php new file mode 100644 index 000000000..2b89c904c --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ReturnReason.php @@ -0,0 +1,46 @@ + '', + 'reason' => '', + 'order' => 0, + ) + ); + + $this->args = $args; + } + + public function get_reason() { + return $this->args['reason']; + } + + public function get_code() { + return $this->args['code']; + } + + public function get_name() { + return $this->get_code(); + } + + public function get_order() { + return absint( $this->args['order'] ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ReturnShipment.php b/packages/woocommerce-germanized-shipments/src/ReturnShipment.php new file mode 100644 index 000000000..64ad411fe --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ReturnShipment.php @@ -0,0 +1,478 @@ + 0, + 'is_customer_requested' => false, + 'sender_address' => array(), + ); + + /** + * Returns the shipment type. + * + * @return string + */ + public function get_type() { + return 'return'; + } + + /** + * Returns the order id belonging to the shipment. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return integer + */ + public function get_order_id( $context = 'view' ) { + return $this->get_prop( 'order_id', $context ); + } + + /** + * Returns whether the current return was requested by a customer or not. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return boolean + */ + public function get_is_customer_requested( $context = 'view' ) { + return $this->get_prop( 'is_customer_requested', $context ); + } + + public function is_customer_requested() { + return $this->get_is_customer_requested(); + } + + public function confirm_customer_request() { + if ( $this->is_customer_requested() && $this->has_status( 'requested' ) ) { + + $this->set_status( 'processing' ); + + if ( $this->save() ) { + /** + * Action that fires after a return request has been confirmed to the customer. + * + * @param integer $shipment_id The return shipment id. + * @param ReturnShipment $shipment The return shipment object. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_return_shipment_customer_confirmed', $this->get_id(), $this ); + + return true; + } else { + return false; + } + } + + return false; + } + + /** + * Returns the address of the sender e.g. customer. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string[] + */ + public function get_sender_address( $context = 'view' ) { + return $this->get_prop( 'sender_address', $context ); + } + + /** + * Set shipment order id. + * + * @param string $order_id The order id. + */ + public function set_order_id( $order_id ) { + // Reset order object + $this->order = null; + + $this->set_prop( 'order_id', absint( $order_id ) ); + } + + /** + * Set if the current return was requested by the customer or not. + * + * @param string $is_requested Whether or not it is requested by the customer. + */ + public function set_is_customer_requested( $is_requested ) { + $this->set_prop( 'is_customer_requested', wc_string_to_bool( $is_requested ) ); + } + + /** + * Set shipment order. + * + * @param Order $order_shipment The order shipment. + */ + public function set_order_shipment( &$order_shipment ) { + $this->order_shipment = $order_shipment; + } + + /** + * Returns shipment order. + * + * @return Order|null The order shipment. + */ + public function get_order_shipment() { + if ( is_null( $this->order_shipment ) ) { + $order = $this->get_order(); + $this->order_shipment = ( $order ? wc_gzd_get_shipment_order( $order ) : false ); + } + + return $this->order_shipment; + } + + /** + * Returns the shippable item count. + * + * @return int + */ + public function get_shippable_item_count() { + + if ( $order_shipment = $this->get_order_shipment() ) { + return $order_shipment->get_returnable_item_count(); + } + + return 0; + } + + /** + * Tries to fetch the order for the current shipment. + * + * @return bool|WC_Order|null + */ + public function get_order() { + if ( is_null( $this->order ) ) { + $this->order = ( $this->get_order_id() > 0 ? wc_get_order( $this->get_order_id() ) : false ); + } + + return $this->order; + } + + /** + * Returns available shipment methods by checking the corresponding order. + * + * @return string[] + */ + public function get_available_shipping_methods() { + $methods = array(); + $methods[ $this->get_shipping_method() ] = ''; + + return $methods; + } + + /** + * Returns a sender address prop. + * + * @param string $prop + * @param string $context + * + * @return null|string + */ + protected function get_sender_address_prop( $prop, $context = 'view' ) { + $value = null; + + if ( isset( $this->changes['sender_address'][ $prop ] ) || isset( $this->data['sender_address'][ $prop ] ) ) { + $value = isset( $this->changes['sender_address'][ $prop ] ) ? $this->changes['sender_address'][ $prop ] : $this->data['sender_address'][ $prop ]; + + if ( 'view' === $context ) { + /** + * Filter to adjust a Shipment's return sender address property e.g. first_name. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. `$prop` refers to the actual address property e.g. first_name. + * + * Example hook name: woocommerce_gzd_return_shipment_get_sender_address_first_name + * + * @param string $value The address property value. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $value = apply_filters( "{$this->get_hook_prefix()}sender_address_{$prop}", $value, $this ); + } + } + + return $value; + } + + /** + * Set sender address. + * + * @param string[] $address The address props. + */ + public function set_sender_address( $address ) { + $this->set_prop( 'sender_address', empty( $address ) ? array() : (array) $address ); + } + + /** + * Syncs the return shipment with the corresponding parent shipment. + * + * @param array $args + * + * @return bool + */ + public function sync( $args = array() ) { + try { + if ( ! $order_shipment = $this->get_order_shipment() ) { + throw new Exception( _x( 'Invalid shipment order', 'shipments', 'woocommerce-germanized' ) ); + } + + $return_address = wc_gzd_get_shipment_return_address( $order_shipment ); + $order = $order_shipment->get_order(); + + /** + * Make sure that manually adjusted providers are not overridden by syncing. + */ + $default_provider = $order_shipment->get_default_return_shipping_provider(); + $provider = $this->get_shipping_provider( 'edit' ); + $sender_address_data = array_merge( + ( $order->has_shipping_address() ? $order->get_address( 'shipping' ) : $order->get_address( 'billing' ) ), + array( + 'email' => $order->get_billing_email(), + 'phone' => $order->get_billing_phone(), + ) + ); + + // Prefer shipping phone in case exists + if ( is_callable( array( $order, 'get_shipping_phone' ) ) && $order->get_shipping_phone() ) { + $sender_address_data['phone'] = $order->get_shipping_phone(); + } + + $args = wp_parse_args( + $args, + array( + 'order_id' => $order->get_id(), + 'country' => $return_address['country'], + 'shipping_method' => wc_gzd_get_shipment_order_shipping_method_id( $order ), + 'shipping_provider' => ( ! empty( $provider ) ) ? $provider : $default_provider, + 'address' => $return_address, + 'sender_address' => $sender_address_data, + 'weight' => $this->get_weight( 'edit' ), + 'length' => $this->get_length( 'edit' ), + 'width' => $this->get_width( 'edit' ), + 'height' => $this->get_height( 'edit' ), + ) + ); + + /** + * Filter to allow adjusting the return shipment props synced from the corresponding order. + * + * @param mixed $args The properties in key => value pairs. + * @param ReturnShipment $shipment The shipment object. + * @param Order $order_shipment The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $args = apply_filters( 'woocommerce_gzd_return_shipment_sync_props', $args, $this, $order_shipment ); + + $this->set_props( $args ); + + /** + * Action that fires after a return shipment has been synced. Syncing is used to + * keep the shipment in sync with the corresponding parent shipment. + * + * @param ReturnShipment $shipment The return shipment object. + * @param Order $order_shipment The shipment order object. + * @param array $args Array containing properties in key => value pairs to be updated. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_return_shipment_synced', $this, $order_shipment, $args ); + + } catch ( Exception $e ) { + return false; + } + + return true; + } + + /** + * Keeps items in sync with the parent shipment items. + * Limits quantities and removes non-existent items. + * + * @param array $args + * + * @return bool + */ + public function sync_items( $args = array() ) { + try { + + if ( ! $order_shipment = $this->get_order_shipment() ) { + throw new Exception( _x( 'Invalid shipment order', 'shipments', 'woocommerce-germanized' ) ); + } + + $order = $order_shipment->get_order(); + + $args = wp_parse_args( + $args, + array( + 'items' => array(), + ) + ); + + $available_items = $order_shipment->get_available_items_for_return( + array( + 'shipment_id' => $this->get_id(), + 'exclude_current_shipment' => true, + ) + ); + + foreach ( $available_items as $order_item_id => $item_data ) { + + if ( $item = $order_shipment->get_simple_shipment_item( $order_item_id ) ) { + $quantity = $item_data['max_quantity']; + $return_reason_code = ''; + + if ( ! empty( $args['items'] ) ) { + if ( isset( $args['items'][ $order_item_id ] ) ) { + + if ( is_array( $args['items'][ $order_item_id ] ) ) { + $default_item_data = wp_parse_args( + $args['items'][ $order_item_id ], + array( + 'quantity' => 1, + 'return_reason_code' => '', + ) + ); + } else { + $default_item_data = array( + 'quantity' => absint( $args['items'][ $order_item_id ] ), + 'return_reason_code' => '', + ); + } + + $new_quantity = $default_item_data['quantity']; + $return_reason_code = $default_item_data['return_reason_code']; + + if ( $new_quantity < $quantity ) { + $quantity = $new_quantity; + } + } else { + continue; + } + } + + if ( $quantity <= 0 ) { + continue; + } + + $sync_data = array( + 'quantity' => $quantity, + ); + + if ( ! empty( $return_reason_code ) ) { + $sync_data['return_reason_code'] = $return_reason_code; + } + + if ( ! $shipment_item = $this->get_item_by_order_item_id( $order_item_id ) ) { + $shipment_item = wc_gzd_create_return_shipment_item( $this, $item, $sync_data ); + + $this->add_item( $shipment_item ); + } else { + $shipment_item->sync( $sync_data ); + } + } + } + + foreach ( $this->get_items() as $item ) { + + // Remove non-existent items + if ( ! $shipment_item = $order_shipment->get_simple_shipment_item( $item->get_order_item_id() ) ) { + $this->remove_item( $item->get_id() ); + } + } + + // Sync packaging + $this->sync_packaging(); + + /** + * Action that fires after items of a shipment have been synced. + * + * @param SimpleShipment $shipment The shipment object. + * @param Order $order_shipment The shipment order object. + * @param array $args Array containing additional data e.g. items. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_return_shipment_items_synced', $this, $order_shipment, $args ); + + } catch ( Exception $e ) { + return false; + } + + return true; + } + + /** + * Returns whether the Shipment needs additional items or not. + * + * @param bool|integer[] $available_items + * + * @return bool + */ + public function needs_items( $available_items = false ) { + + if ( ! $available_items && ( $order_shipment = $this->get_order_shipment() ) ) { + $available_items = array_keys( $order_shipment->get_available_items_for_return() ); + } + + return ( $this->is_editable() && ! $this->contains_order_item( $available_items ) ); + } + + /** + * Returns the edit shipment URL. + * + * @return mixed|string|void + */ + public function get_edit_shipment_url() { + /** + * Filter to adjust the edit Shipment admin URL. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_edit_url + * + * @param string $url The URL. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}edit_url", get_admin_url( null, 'post.php?post=' . $this->get_order_id() . '&action=edit&shipment_id=' . $this->get_id() ), $this ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Shipment.php b/packages/woocommerce-germanized-shipments/src/Shipment.php new file mode 100644 index 000000000..1e8aca751 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Shipment.php @@ -0,0 +1,2820 @@ + null, + 'date_sent' => null, + 'status' => '', + 'weight' => '', + 'width' => '', + 'height' => '', + 'length' => '', + 'packaging_weight' => '', + 'weight_unit' => '', + 'dimension_unit' => '', + 'country' => '', + 'address' => array(), + 'tracking_id' => '', + 'shipping_provider' => '', + 'shipping_method' => '', + 'total' => 0, + 'subtotal' => 0, + 'additional_total' => 0, + 'est_delivery_date' => null, + 'packaging_id' => 0, + 'version' => '', + ); + + /** + * Get the shipment if ID is passed, otherwise the shipment is new and empty. + * This class should NOT be instantiated, but the `wc_gzd_get_shipment` function should be used. + * + * @param int|object|Shipment $shipment Shipment to read. + */ + public function __construct( $data = 0 ) { + parent::__construct( $data ); + + if ( $data instanceof Shipment ) { + $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 ''; + } + + /** + * Merge changes with data and clear. + * Overrides WC_Data::apply_changes. + * + * @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(); + } + + /** + * @return bool|Order + */ + public function get_order_shipment() { + return false; + } + + public function set_order_shipment( &$order_shipment ) {} + + /** + * Return item count (quantities summed up). + * + * @return int + */ + public function get_item_count() { + $items = $this->get_items(); + $quantity = 0; + + foreach ( $items as $item ) { + $quantity += $item->get_quantity(); + } + + return $quantity; + } + + /** + * Prefix for action and filter hooks on data. + * + * @return string + */ + protected function get_hook_prefix() { + return $this->get_general_hook_prefix() . 'get_'; + } + + /** + * Prefix for action and filter hooks on data. + * + * @return string + */ + protected function get_general_hook_prefix() { + $shipment_prefix = 'simple' === $this->get_type() ? '' : $this->get_type() . '_'; + + return "woocommerce_gzd_{$shipment_prefix}shipment_"; + } + + public function is_shipping_domestic() { + return Package::is_shipping_domestic( + $this->get_country(), + array( + 'sender_country' => $this->get_sender_country(), + 'sender_postcode' => $this->get_sender_postcode(), + 'postcode' => $this->get_postcode(), + ) + ); + } + + /** + * Returns true in case the shipment is being shipped inner EU, e.g. + * from a base country inside of the EU to another country inside the EU. + * + * @return bool + */ + public function is_shipping_inner_eu() { + if ( Package::is_shipping_inner_eu_country( + $this->get_country(), + array( + 'sender_country' => $this->get_sender_country(), + 'sender_postcode' => $this->get_sender_postcode(), + 'postcode' => $this->get_postcode(), + ) + ) ) { + return true; + } + + return false; + } + + public function is_shipping_international() { + if ( $this->is_shipping_domestic() || $this->is_shipping_inner_eu() ) { + return false; + } + + return true; + } + + /** + * Return the shipment statuses without gzd- internal prefix. + * + * @param string $context View or edit context. + * @return string + */ + public function get_status( $context = 'view' ) { + $status = $this->get_prop( 'status', $context ); + + if ( empty( $status ) && 'view' === $context ) { + + /** + * Filters the default Shipment status used as fallback. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_default_shipment_status + * + * @param string $status Default fallback status. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $status = apply_filters( "{$this->get_hook_prefix()}}default_shipment_status", 'draft' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + } + + return $status; + } + + /** + * Checks whether the shipment has a specific status or not. + * + * @param string|string[] $status The status to be checked against. + * @return boolean + */ + public function has_status( $status ) { + /** + * Filter to decide whether a Shipment has a certain status or not. + * + * @param boolean $has_status Whether the Shipment has a status or not. + * @param Shipment $this The shipment object. + * @param string $status The status to be checked against. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status ); + } + + /** + * Return the date this shipment was created. + * + * @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 ); + } + + /** + * Return the date this shipment was sent. + * + * @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_sent( $context = 'view' ) { + return $this->get_prop( 'date_sent', $context ); + } + + /** + * Returns the shipment method. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_shipping_method( $context = 'view' ) { + return $this->get_prop( 'shipping_method', $context ); + } + + public function get_shipping_method_instance() { + $method_id = $this->get_shipping_method(); + + if ( is_null( $this->shipping_method_instance ) && ! empty( $method_id ) ) { + $this->shipping_method_instance = wc_gzd_get_shipping_provider_method( $this->get_shipping_method() ); + } + + return is_null( $this->shipping_method_instance ) ? false : $this->shipping_method_instance; + } + + /** + * Returns the shipment weight. In case view context was chosen and weight is not yet set, returns the content weight. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_weight( $context = 'view' ) { + $weight = $this->get_prop( 'weight', $context ); + + if ( 'view' === $context && '' === $weight ) { + return $this->get_content_weight(); + } + + return $weight; + } + + public function get_total_weight() { + $weight = $this->get_weight() + $this->get_packaging_weight(); + + return $weight; + } + + public function get_packaging_weight( $context = 'view' ) { + $weight = $this->get_prop( 'packaging_weight', $context ); + + if ( 'view' === $context && '' === $weight ) { + $weight = wc_format_decimal( 0 ); + + if ( $packaging = $this->get_packaging() ) { + if ( ! empty( $packaging->get_weight() ) ) { + $weight = wc_get_weight( $packaging->get_weight(), $this->get_weight_unit(), wc_gzd_get_packaging_weight_unit() ); + } + } + } + + return $weight; + } + + public function get_items_to_pack() { + if ( ! Package::is_packing_supported() ) { + return $this->get_items(); + } else { + if ( is_null( $this->items_to_pack ) ) { + $this->items_to_pack = array(); + + foreach ( $this->get_items() as $item ) { + for ( $i = 0; $i < $item->get_quantity(); $i++ ) { // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed + $box_item = new Packing\ShipmentItem( $item ); + $this->items_to_pack[] = $box_item; + } + } + } + + return apply_filters( "{$this->get_hook_prefix()}items_to_pack", $this->items_to_pack, $this ); + } + } + + /** + * Returns the shipment weight unit. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_weight_unit( $context = 'view' ) { + $unit = $this->get_prop( 'weight_unit', $context ); + + if ( 'view' === $context && '' === $unit ) { + return get_option( 'woocommerce_weight_unit' ); + } + + return $unit; + } + + /** + * Returns the shipment dimension unit. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_dimension_unit( $context = 'view' ) { + $unit = $this->get_prop( 'dimension_unit', $context ); + + if ( 'view' === $context && '' === $unit ) { + return get_option( 'woocommerce_dimension_unit' ); + } + + return $unit; + } + + /** + * Returns the shipment length. In case view context was chosen and length is not yet set, returns the content length. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_length( $context = 'view' ) { + $length = $this->get_prop( 'length', $context ); + + if ( 'view' === $context && '' === $length ) { + return $this->get_content_length(); + } + + return $length; + } + + public function get_package_length() { + $length = $this->get_length(); + + // Older versions did not sync dimensions with packaging dimensions + if ( '' === $this->get_version() ) { + if ( $packaging = $this->get_packaging() ) { + $length = wc_get_dimension( $packaging->get_length(), $this->get_dimension_unit(), wc_gzd_get_packaging_dimension_unit() ); + } + } + + return $length; + } + + public function has_packaging() { + return ( $this->get_packaging_id() > 0 && $this->get_packaging() ) ? true : false; + } + + /** + * Returns the shipment width. In case view context was chosen and width is not yet set, returns the content width. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_width( $context = 'view' ) { + $width = $this->get_prop( 'width', $context ); + + if ( 'view' === $context && '' === $width ) { + return $this->get_content_width(); + } + + return $width; + } + + public function get_package_width() { + $width = $this->get_width(); + + if ( '' === $this->get_version() ) { + if ( $packaging = $this->get_packaging() ) { + $width = wc_get_dimension( $packaging->get_width(), $this->get_dimension_unit(), wc_gzd_get_packaging_dimension_unit() ); + } + } + + return $width; + } + + /** + * Returns the shipment height. In case view context was chosen and height is not yet set, returns the content height. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_height( $context = 'view' ) { + $height = $this->get_prop( 'height', $context ); + + if ( 'view' === $context && '' === $height ) { + return $this->get_content_height(); + } + + return $height; + } + + public function get_package_height() { + $height = $this->get_height(); + + if ( '' === $this->get_version() ) { + if ( $packaging = $this->get_packaging() ) { + $height = wc_get_dimension( $packaging->get_height(), $this->get_dimension_unit(), wc_gzd_get_packaging_dimension_unit() ); + } + } + + return $height; + } + + public function has_dimensions() { + $width = $this->get_width(); + $length = $this->get_length(); + $height = $this->get_height(); + + return ( ! empty( $width ) && ! empty( $length ) && ! empty( $height ) ); + } + + /** + * Returns the calculated weights for included items. + * + * @return float[] + */ + public function get_item_weights() { + if ( is_null( $this->weights ) ) { + $this->weights = array(); + + foreach ( $this->get_items() as $item ) { + $this->weights[ $item->get_id() ] = ( ( $item->get_weight() === '' ? 0 : $item->get_weight() ) * $item->get_quantity() ); + } + + if ( empty( $this->weights ) ) { + $this->weights = array( 0 ); + } + } + + return $this->weights; + } + + /** + * Returns the calculated lengths for included items. + * + * @return float[] + */ + public function get_item_lengths() { + if ( is_null( $this->lengths ) ) { + $this->lengths = array(); + + foreach ( $this->get_items() as $item ) { + $this->lengths[ $item->get_id() ] = $item->get_length() === '' ? 0 : $item->get_length(); + } + + if ( empty( $this->lengths ) ) { + $this->lengths = array( 0 ); + } + } + + return $this->lengths; + } + + public function get_item_volumes() { + if ( is_null( $this->volumes ) ) { + $this->volumes = array(); + + foreach ( $this->get_items() as $item ) { + $dimensions = $item->get_dimensions(); + $volume = ( '' !== $dimensions['length'] ? (float) $dimensions['length'] : 0 ) * ( '' !== $dimensions['width'] ? (float) $dimensions['width'] : 0 ) * ( '' !== $dimensions['height'] ? (float) $dimensions['height'] : 0 ); + + $this->volumes[ $item->get_id() ] = $volume * (float) $item->get_quantity(); + } + + if ( empty( $this->volumes ) ) { + $this->volumes = array( 0 ); + } + } + + return $this->volumes; + } + + /** + * Returns the calculated widths for included items. + * + * @return float[] + */ + public function get_item_widths() { + if ( is_null( $this->widths ) ) { + $this->widths = array(); + + foreach ( $this->get_items() as $item ) { + $this->widths[ $item->get_id() ] = $item->get_width() === '' ? 0 : $item->get_width(); + } + + if ( empty( $this->widths ) ) { + $this->widths = array( 0 ); + } + } + + return $this->widths; + } + + /** + * Returns the calculated heights for included items. + * + * @return float[] + */ + public function get_item_heights() { + if ( is_null( $this->heights ) ) { + $this->heights = array(); + + foreach ( $this->get_items() as $item ) { + $this->heights[ $item->get_id() ] = ( $item->get_height() === '' ? 0 : $item->get_height() ) * $item->get_quantity(); + } + + if ( empty( $this->heights ) ) { + $this->heights = array( 0 ); + } + } + + return $this->heights; + } + + /** + * Returns the calculated weight for included items. + * + * @return float + */ + public function get_content_weight() { + return wc_format_decimal( array_sum( $this->get_item_weights() ) ); + } + + public function get_content_dimensions() { + return array( + 'length' => $this->get_content_length(), + 'width' => $this->get_content_width(), + 'height' => $this->get_content_height(), + ); + } + + /** + * Returns the calculated length for included items. + * + * @return float + */ + public function get_content_length() { + $default = max( $this->get_item_lengths() ); + + return wc_format_decimal( $default, false, true ); + } + + /** + * Returns the calculated width for included items. + * + * @return float + */ + public function get_content_width() { + $default = max( $this->get_item_widths() ); + + return wc_format_decimal( $default, false, true ); + } + + /** + * Returns the calculated volume for included items. + * + * @return float + */ + public function get_content_volume() { + $default = array_sum( $this->get_item_volumes() ); + + return wc_format_decimal( $default, false, true ); + } + + /** + * Returns the calculated height for included items. + * + * @return float + */ + public function get_content_height() { + $default_height = array_sum( $this->get_item_heights() ); + + return wc_format_decimal( $default_height, false, true ); + } + + /** + * Returns the shipping address properties. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string[] + */ + public function get_address( $context = 'view' ) { + return $this->get_prop( 'address', $context ); + } + + /** + * Returns the shipment total. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return float + */ + public function get_total( $context = 'view' ) { + return $this->get_prop( 'total', $context ); + } + + /** + * Returns the shipment total. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return float + */ + public function get_subtotal( $context = 'view' ) { + $subtotal = $this->get_prop( 'subtotal', $context ); + + if ( 'view' === $context && empty( $subtotal ) ) { + $subtotal = $this->get_total(); + } + + return $subtotal; + } + + /** + * Returns the additional total amount containing shipping and fee costs. + * Only one of the shipments related to an order should include additional total. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return float + */ + public function get_additional_total( $context = 'view' ) { + return $this->get_prop( 'additional_total', $context ); + } + + public function has_tracking() { + $has_tracking = true; + + if ( ! $this->has_tracking_instruction() && ! $this->get_tracking_url() ) { + $has_tracking = false; + } + + /** + * Check whether the label supports tracking or not + */ + if ( $this->has_label() && ( $label = $this->get_label() ) ) { + if ( ! $label->is_trackable() ) { + $has_tracking = false; + } + } + + return apply_filters( "{$this->get_general_hook_prefix()}has_tracking", $has_tracking, $this ); + } + + /** + * Returns the shipment tracking id. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_tracking_id( $context = 'view' ) { + return $this->get_prop( 'tracking_id', $context ); + } + + /** + * Returns the shipment tracking URL. + * + * @return string + */ + public function get_tracking_url() { + $tracking_url = ''; + + if ( $provider = $this->get_shipping_provider_instance() ) { + $tracking_url = $provider->get_tracking_url( $this ); + } + + /** + * Filter to adjust a Shipment's tracking URL. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_tracking_url + * + * @param string $tracking_url The tracking URL. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}tracking_url", $tracking_url, $this ); + } + + /** + * Returns the shipment tracking instruction. + * + * @return string + */ + public function get_tracking_instruction( $plain = false ) { + $instruction = ''; + + if ( $provider = $this->get_shipping_provider_instance() ) { + $instruction = $provider->get_tracking_desc( $this, $plain ); + } + + /** + * Filter to adjust a Shipment's tracking instruction. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_tracking_instruction + * + * @param string $instruction The tracking instruction. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}tracking_instruction", $instruction, $this ); + } + + /** + * Returns whether the current shipment has tracking instructions available or not. + * + * @return boolean + */ + public function has_tracking_instruction() { + $instruction = $this->get_tracking_instruction( true ); + + return ( ! empty( $instruction ) ) ? true : false; + } + + /** + * Returns the shipment shipping provider. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_shipping_provider( $context = 'view' ) { + return $this->get_prop( 'shipping_provider', $context ); + } + + public function get_shipping_provider_title() { + if ( $provider = $this->get_shipping_provider_instance() ) { + return $provider->get_title(); + } + + return ''; + } + + public function get_shipping_provider_instance() { + $provider = $this->get_shipping_provider(); + + if ( ! empty( $provider ) ) { + return wc_gzd_get_shipping_provider( $provider ); + } + + return false; + } + + /** + * Returns the formatted shipping address. + * + * @param string $empty_content Content to show if no address is present. + * @return string + */ + public function get_formatted_address( $empty_content = '' ) { + $address = WC()->countries->get_formatted_address( $this->get_address() ); + + return $address ? $address : $empty_content; + } + + /** + * Get a formatted shipping address for the order. + * + * @return string + */ + public function get_address_map_url( $address ) { + // Remove name and company before generate the Google Maps URL. + unset( $address['first_name'], $address['last_name'], $address['company'], $address['email'], $address['phone'], $address['title'] ); + + /** + * Filter to adjust a Shipment's address parts used for constructing the Google maps URL. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_address_map_url_parts + * + * @param string[] $address The address parts used. + * @param Shipment $this The shipment object. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + $address = apply_filters( "{$this->get_hook_prefix()}address_map_url_parts", $address, $this ); + $address = array_filter( $address ); + + /** + * Filter to adjust a Shipment's address Google maps URL. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_address_map_url + * + * @param string $url The address url. + * @param Shipment $this The shipment object. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}address_map_url", 'https://maps.google.com/maps?&q=' . rawurlencode( implode( ', ', $address ) ) . '&z=16', $this ); + } + + /** + * Returns the shipment address phone number. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_phone( $context = 'view' ) { + return $this->get_address_prop( 'phone', $context ); + } + + /** + * Returns the shipment address email. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_email( $context = 'view' ) { + return $this->get_address_prop( 'email', $context ); + } + + /** + * Returns the shipment address first line. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_address_1( $context = 'view' ) { + return $this->get_address_prop( 'address_1', $context ); + } + + /** + * Returns the shipment address second line. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_address_2( $context = 'view' ) { + return $this->get_address_prop( 'address_2', $context ); + } + + /** + * Returns the shipment address street number by splitting the address. + * + * @param string $type The address type e.g. address_1 or address_2. + * + * @return string + */ + public function get_address_street_number( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_$type"}() ); + + /** + * Filter to adjust the shipment address street number. + * + * @param string $number The shipment address street number. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_get_shipment_address_street_number', $split['number'], $this ); + } + + /** + * Returns the shipment address street without number by splitting the address. + * + * @param string $type The address type e.g. address_1 or address_2. + * + * @return string + */ + public function get_address_street( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_$type"}() ); + + /** + * Filter to adjust the shipment address street. + * + * @param string $street The shipment address street without street number. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_get_shipment_address_street', $split['street'], $this ); + } + + public function get_address_street_addition( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_$type"}() ); + + /** + * Filter to adjust the shipment address street addition. + * + * @param string $addition The shipment address street addition e.g. EG14. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_get_shipment_address_street_addition', $split['addition'], $this ); + } + + public function get_address_street_addition_2( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_$type"}() ); + + /** + * Filter to adjust the shipment address street addition. + * + * @param string $addition The shipment address street addition e.g. EG14. + * @param Shipment $shipment The shipment object. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_get_shipment_address_street_addition_2', $split['addition_2'], $this ); + } + + /** + * Returns the shipment address company. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_company( $context = 'view' ) { + return $this->get_address_prop( 'company', $context ); + } + + /** + * Returns the shipment address first name. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_first_name( $context = 'view' ) { + return $this->get_address_prop( 'first_name', $context ); + } + + /** + * Returns the shipment address last name. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_last_name( $context = 'view' ) { + return $this->get_address_prop( 'last_name', $context ); + } + + /** + * Returns the shipment address formatted full name. + * + * @return string + */ + public function get_formatted_full_name() { + return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce-germanized' ), $this->get_first_name(), $this->get_last_name() ); + } + + /** + * Returns the shipment address postcode. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_postcode( $context = 'view' ) { + return $this->get_address_prop( 'postcode', $context ); + } + + /** + * Returns the shipment address city. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_city( $context = 'view' ) { + return $this->get_address_prop( 'city', $context ); + } + + /** + * Returns the shipment address state. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_state( $context = 'view' ) { + return $this->get_address_prop( 'state', $context ); + } + + public function get_formatted_state() { + if ( '' === $this->get_state() || '' === $this->get_country() ) { + return ''; + } + + return wc_gzd_get_formatted_state( $this->get_state(), $this->get_country() ); + } + + /** + * Returns the shipment address country. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_country( $context = 'view' ) { + return $this->get_address_prop( 'country', $context ) ? $this->get_address_prop( 'country', $context ) : ''; + } + + /** + * Returns the shipment address customs reference number. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_customs_reference_number( $context = 'view' ) { + return $this->get_address_prop( 'customs_reference_number', $context ) ? $this->get_address_prop( 'customs_reference_number', $context ) : ''; + } + + /** + * Returns a sender address prop by checking the corresponding provider and falling back to + * global sender address setting data. + * + * @param string $prop + * @param string $context + * + * @return null|string + */ + protected function get_sender_address_prop( $prop, $context = 'view' ) { + $value = null; + + if ( $provider = $this->get_shipping_provider_instance() ) { + $getter = "get_shipper_{$prop}"; + + if ( is_callable( array( $provider, $getter ) ) ) { + $value = $provider->$getter( $context ); + } + } else { + $key = "woocommerce_gzd_shipments_shipper_address_{$prop}"; + $value = get_option( $key, '' ); + } + + if ( 'view' === $context ) { + /** + * Filter to adjust a shipment's sender address property e.g. first_name. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. `$prop` refers to the actual address property e.g. first_name. + * + * Example hook name: woocommerce_gzd_shipment_get_sender_address_first_name + * + * @param string $value The address property value. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $value = apply_filters( "{$this->get_hook_prefix()}sender_address_{$prop}", $value, $this ); + } + + return $value; + } + + /** + * Returns the formatted sender address. + * + * @param string $empty_content Content to show if no address is present. + * @return string + */ + public function get_formatted_sender_address( $empty_content = '' ) { + $address = WC()->countries->get_formatted_address( $this->get_sender_address() ); + + return $address ? $address : $empty_content; + } + + /** + * Returns the address of the sender e.g. customer. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string[] + */ + public function get_sender_address( $context = 'view' ) { + return apply_filters( + "{$this->get_hook_prefix()}sender_address", + array( + 'company' => $this->get_sender_company( $context ), + 'first_name' => $this->get_sender_first_name( $context ), + 'last_name' => $this->get_sender_last_name( $context ), + 'address_1' => $this->get_sender_address_1( $context ), + 'address_2' => $this->get_sender_address_2( $context ), + 'postcode' => $this->get_sender_postcode( $context ), + 'city' => $this->get_sender_city( $context ), + 'country' => $this->get_sender_country( $context ), + 'state' => $this->get_sender_state( $context ), + ), + $this + ); + } + + /** + * Returns the sender address phone number. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_phone( $context = 'view' ) { + return $this->get_sender_address_prop( 'phone', $context ); + } + + /** + * Returns the sender address email. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_email( $context = 'view' ) { + return $this->get_sender_address_prop( 'email', $context ); + } + + /** + * 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 ); + } + + /** + * Returns the sender address second line. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_address_2( $context = 'view' ) { + return $this->get_sender_address_prop( 'address_2', $context ); + } + + /** + * Returns the sender address street number by splitting the address. + * + * @param string $type The address type e.g. address_1 or address_2. + * + * @return string + */ + public function get_sender_address_street_number( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_sender_$type"}() ); + + return $split['number']; + } + + /** + * Returns the sender address street without number by splitting the address. + * + * @param string $type The address type e.g. address_1 or address_2. + * + * @return string + */ + public function get_sender_address_street( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_sender_$type"}() ); + + return $split['street']; + } + + /** + * Returns the sender address street addition by splitting the address. + * + * @param string $type The address type e.g. address_1 or address_2. + * + * @return string + */ + public function get_sender_address_street_addition( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_sender_$type"}() ); + + return $split['addition']; + } + + public function get_sender_address_street_addition_2( $type = 'address_1' ) { + $split = wc_gzd_split_shipment_street( $this->{"get_sender_$type"}() ); + + return $split['addition_2']; + } + + /** + * Returns the sender address company. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_company( $context = 'view' ) { + return $this->get_sender_address_prop( 'company', $context ); + } + + /** + * Returns the sender address first name. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_first_name( $context = 'view' ) { + return $this->get_sender_address_prop( 'first_name', $context ); + } + + /** + * Returns the shipment address last name. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_last_name( $context = 'view' ) { + return $this->get_sender_address_prop( 'last_name', $context ); + } + + /** + * Returns the sender address formatted full name. + * + * @return string + */ + public function get_formatted_sender_full_name() { + return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce-germanized' ), $this->get_sender_first_name(), $this->get_sender_last_name() ); + } + + /** + * Returns the sender address postcode. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_postcode( $context = 'view' ) { + return $this->get_sender_address_prop( 'postcode', $context ); + } + + /** + * Returns the sender address city. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_city( $context = 'view' ) { + return $this->get_sender_address_prop( 'city', $context ); + } + + /** + * Returns the sender address state. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_state( $context = 'view' ) { + return $this->get_sender_address_prop( 'state', $context ); + } + + /** + * Returns the sender address customs reference number. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_customs_reference_number( $context = 'view' ) { + return $this->get_sender_address_prop( 'customs_reference_number', $context ) ? $this->get_sender_address_prop( 'customs_reference_number', $context ) : ''; + } + + /** + * Returns the sender address customs reference number. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_customs_uk_vat_id( $context = 'view' ) { + return $this->get_sender_address_prop( 'customs_uk_vat_id', $context ) ? $this->get_sender_address_prop( 'customs_uk_vat_id', $context ) : ''; + } + + public function get_formatted_sender_state() { + if ( '' === $this->get_sender_state() || '' === $this->get_sender_country() ) { + return ''; + } + + return wc_gzd_get_formatted_state( $this->get_sender_state(), $this->get_sender_country() ); + } + + /** + * Returns the sender address country. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return string + */ + public function get_sender_country( $context = 'view' ) { + return $this->get_sender_address_prop( 'country', $context ) ? $this->get_sender_address_prop( 'country', $context ) : ''; + } + + /** + * Return the date this shipment is estimated to be delivered. + * + * @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_est_delivery_date( $context = 'view' ) { + return $this->get_prop( 'est_delivery_date', $context ); + } + + /** + * Decides whether the shipment is sent to an external pickup or not. + * + * @param string[]|string $types + * + * @return boolean + */ + public function send_to_external_pickup( $types = array() ) { + $types = is_array( $types ) ? $types : array( $types ); + + /** + * Filter to decide whether a Shipment is to be sent to a external pickup location + * e.g. packstation. + * + * @param boolean $external True if the Shipment goes to a pickup location. + * @param array $types Array containing the types to be checked against, or empty. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_send_to_external_pickup', false, $types, $this ); + } + + /** + * Returns an address prop. + * + * @param string $prop + * @param string $context + * + * @return null|string + */ + protected function get_address_prop( $prop, $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 Shipment's shipping address property e.g. first_name. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. `$prop` refers to the actual address property e.g. first_name. + * + * Example hook name: woocommerce_gzd_shipment_get_address_first_name + * + * @param string $value The address property value. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $value = apply_filters( "{$this->get_hook_prefix()}address_{$prop}", $value, $this ); + } + } + + return $value; + } + + /** + * Returns dimensions. + * + * @return string|array + */ + public function get_dimensions( $context = 'view' ) { + return array( + 'length' => $this->get_length( $context ), + 'width' => $this->get_width( $context ), + 'height' => $this->get_height( $context ), + ); + } + + /** + * Returns dimensions. + * + * @return string|array + */ + public function get_package_dimensions() { + return array( + 'length' => $this->get_package_length(), + 'width' => $this->get_package_width(), + 'height' => $this->get_package_height(), + ); + } + + public function get_formatted_dimensions() { + return wc_gzd_format_shipment_dimensions( $this->get_dimensions(), $this->get_dimension_unit() ); + } + + /** + * Returns whether the shipment is editable or not. + * + * @return boolean + */ + public function is_editable() { + /** + * Filter to dedice whether the current Shipment is still editable or not. + * + * @param boolean $is_editable Whether the Shipment is editable or not. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_is_editable', $this->has_status( wc_gzd_get_shipment_editable_statuses() ), $this ); + } + + /** + * Returns the shipment number. + * + * @return string + */ + public function get_shipment_number() { + /** + * Filter to adjust a Shipment's number. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_shipment_number + * + * @param string $number The shipment number. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return (string) apply_filters( "{$this->get_hook_prefix()}shipment_number", $this->get_id(), $this ); + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set shipment status. + * + * @param string $new_status Status to change the shipment to. No internal gzd- prefix is required. + * @param boolean $manual_update Whether it is a manual status update or not. + * @return array details of change + */ + public function set_status( $new_status, $manual_update = false ) { + $old_status = $this->get_status(); + $new_status = 'gzd-' === substr( $new_status, 0, 4 ) ? substr( $new_status, 4 ) : $new_status; + + $this->set_prop( 'status', $new_status ); + + $result = array( + 'from' => $old_status, + 'to' => $new_status, + ); + + if ( true === $this->object_read && ! empty( $result['from'] ) && $result['from'] !== $result['to'] ) { + $this->status_transition = array( + 'from' => ! empty( $this->status_transition['from'] ) ? $this->status_transition['from'] : $result['from'], + 'to' => $result['to'], + 'manual' => (bool) $manual_update, + ); + + if ( $manual_update ) { + /** + * Action that fires after a shipment status has been updated manually. + * + * @param integer $shipment_id The shipment id. + * @param string $status The new shipment status. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_edit_status', $this->get_id(), $result['to'] ); + } + + $this->maybe_set_date_sent(); + } + + return $result; + } + + public function is_shipped() { + $is_shipped = $this->has_status( wc_gzd_get_shipment_sent_statuses() ); + + return apply_filters( $this->get_hook_prefix() . 'is_shipped', $is_shipped, $this ); + } + + /** + * Maybe set date sent. + * + * Sets the date sent variable when transitioning to the shipped shipment status. + * Date sent is set once in this manner - only when it is not already set. + */ + public function maybe_set_date_sent() { + // This logic only runs if the date_sent prop has not been set yet. + if ( ! $this->get_date_sent( 'edit' ) ) { + if ( $this->is_shipped() ) { + // If payment complete status is reached, set paid now. + $this->set_date_sent( time() ); + } + } + } + + /** + * Updates status of shipment immediately. + * + * @uses Shipment::set_status() + * + * @param string $new_status Status to change the shipment to. No internal gzd- prefix is required. + * @param bool $manual Is this a manual order status change? + * @return bool + */ + public function update_status( $new_status, $manual = false ) { + if ( ! $this->get_id() ) { + return false; + } + + try { + $this->set_status( $new_status, $manual ); + $this->save(); + } catch ( Exception $e ) { + $logger = wc_get_logger(); + $logger->error( + sprintf( 'Error updating status for shipment #%d', $this->get_id() ), + array( + 'shipment' => $this, + 'error' => $e, + ) + ); + return false; + } + return true; + } + + /** + * Set the date this shipment was created. + * + * @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 ); + } + + /** + * Set the date this shipment was sent. + * + * @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_sent( $date = null ) { + $this->set_date_prop( 'date_sent', $date ); + } + + /** + * Set shipment weight in kg. + * + * @param string $weight The weight. + */ + public function set_weight( $weight ) { + $this->set_prop( 'weight', '' === $weight ? '' : wc_format_decimal( $weight ) ); + } + + public function set_packaging_weight( $weight ) { + $this->set_prop( 'packaging_weight', '' === $weight ? '' : wc_format_decimal( $weight ) ); + } + + /** + * Set shipment total weight. + * + * @param string $weight The weight. + */ + public function set_total_weight( $weight ) { + $this->set_prop( 'total_weight', '' === $weight ? '' : wc_format_decimal( $weight ) ); + } + + /** + * Set shipment width. + * + * @param string $width The width. + */ + public function set_width( $width ) { + $this->set_prop( 'width', '' === $width ? '' : wc_format_decimal( $width ) ); + } + + public function set_weight_unit( $unit ) { + $this->set_prop( 'weight_unit', $unit ); + } + + public function set_dimension_unit( $unit ) { + $this->set_prop( 'dimension_unit', $unit ); + } + + /** + * Set shipment length. + * + * @param string $length The length. + */ + public function set_length( $length ) { + $this->set_prop( 'length', '' === $length ? '' : wc_format_decimal( $length ) ); + } + + /** + * Set shipment height. + * + * @param string $height The height. + */ + public function set_height( $height ) { + $this->set_prop( 'height', '' === $height ? '' : wc_format_decimal( $height ) ); + } + + /** + * Set shipment address. + * + * @param string[] $address The address props. + */ + public function set_address( $address ) { + $this->set_prop( 'address', empty( $address ) ? array() : (array) $address ); + } + + /** + * Set shipment shipping method. + * + * @param string $method The shipping method. + */ + public function set_shipping_method( $method ) { + $this->shipping_method_instance = null; + + $this->set_prop( 'shipping_method', $method ); + } + + /** + * Set shipment version. + * + * @param string $version The version. + */ + public function set_version( $version ) { + $this->set_prop( 'version', $version ); + } + + /** + * Set the date this shipment will be delivered. + * + * @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_est_delivery_date( $date = null ) { + $this->set_date_prop( 'est_delivery_date', $date ); + } + + /** + * Set shipment total. + * + * @param float|string $value The shipment total. + */ + public function set_total( $value ) { + $value = wc_format_decimal( $value ); + + if ( ! is_numeric( $value ) ) { + $value = 0; + } + + $this->set_prop( 'total', $value ); + } + + /** + * Set shipment total. + * + * @param float|string $value The shipment total. + */ + public function set_subtotal( $value ) { + $value = wc_format_decimal( $value ); + + if ( ! is_numeric( $value ) ) { + $value = 0; + } + + $this->set_prop( 'subtotal', $value ); + } + + /** + * Set shipment additional total. + * + * @param float|string $value The shipment total. + */ + public function set_additional_total( $value ) { + $value = wc_format_decimal( $value ); + + if ( ! is_numeric( $value ) ) { + $value = 0; + } + + $this->set_prop( 'additional_total', $value ); + } + + /** + * Set shipment shipping country. + * + * @param string $country The country in ISO format. + */ + public function set_country( $country ) { + $this->set_address_prop( 'country', $country ); + } + + /** + * Update a specific address prop. + * + * @param $prop + * @param $value + */ + protected function set_address_prop( $prop, $value ) { + $address = $this->get_address(); + $address[ $prop ] = $value; + + $this->set_address( $address ); + } + + /** + * Set shipment tracking id. + * + * @param string $tracking_id The trakcing id. + */ + public function set_tracking_id( $tracking_id ) { + $this->set_prop( 'tracking_id', $tracking_id ); + } + + /** + * Set shipment shipping provider. + * + * @param string $provider The shipping provider. + */ + public function set_shipping_provider( $provider ) { + $this->set_prop( 'shipping_provider', wc_gzd_get_shipping_provider_slug( $provider ) ); + } + + /** + * Set packaging id. + * + * @param integer $packaging_id The packaging id. + */ + public function set_packaging_id( $packaging_id ) { + $this->set_prop( 'packaging_id', absint( $packaging_id ) ); + + $this->packaging = null; + } + + public function sync_packaging() { + $available_packaging = $this->get_selectable_packaging(); + $default_packaging = $this->get_default_packaging(); + $packaging_id = $this->get_packaging_id( 'edit' ); + + if ( ! empty( $packaging_id ) ) { + $exists = false; + + foreach ( $available_packaging as $packaging ) { + if ( (int) $packaging_id === (int) $packaging->get_id() ) { + $exists = true; + break; + } + } + + if ( ! $exists && $default_packaging ) { + $this->set_packaging_id( $default_packaging->get_id() ); + } + } elseif ( empty( $packaging_id ) && $default_packaging ) { + $this->set_packaging_id( $default_packaging->get_id() ); + } + } + + public function update_packaging() { + if ( $packaging = $this->get_packaging() ) { + $packaging_dimension = wc_gzd_get_packaging_dimension_unit(); + + $props = array( + 'width' => wc_get_dimension( $packaging->get_width( 'edit' ), $this->get_dimension_unit(), $packaging_dimension ), + 'length' => wc_get_dimension( $packaging->get_length( 'edit' ), $this->get_dimension_unit(), $packaging_dimension ), + 'height' => wc_get_dimension( $packaging->get_height( 'edit' ), $this->get_dimension_unit(), $packaging_dimension ), + 'packaging_weight' => wc_get_weight( $packaging->get_weight( 'edit' ), $this->get_weight_unit(), wc_gzd_get_packaging_weight_unit() ), + ); + + $this->set_props( $props ); + } else { + $props = array( 'packaging_weight' => '' ); + $changes = $this->get_changes(); + + /** + * Maybe reset dimensions in case they've not been explicitly set + */ + if ( array_key_exists( 'packaging_id', $changes ) ) { + foreach ( array( 'length', 'width', 'height' ) as $dim_prop ) { + if ( ! array_key_exists( $dim_prop, $changes ) ) { + $props = array_merge( $props, array( $dim_prop => '' ) ); + } + } + } + + // Reset + $this->set_props( $props ); + } + + return true; + } + + /** + * Return an array of items within this shipment. + * + * @return ShipmentItem[] + */ + public function get_items() { + $items = array(); + + if ( is_null( $this->items ) ) { + $this->items = array_filter( $this->data_store->read_items( $this ) ); + + $items = (array) $this->items; + } else { + $items = (array) $this->items; + } + + /** + * Filter to adjust items belonging to a Shipment. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_items + * + * @param string $number The shipment number. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}items", $items, $this ); + } + + /** + * Get's the URL to edit the shipment in the backend. + * + * @return string + */ + abstract public function get_edit_shipment_url(); + + public function get_view_shipment_url() { + /** + * Filter to adjust the URL being used to access the view shipment page on the customer account page. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_view_shipment_url + * + * @param string $url The URL pointing to the view page. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}_view_shipment_url", wc_get_endpoint_url( 'view-shipment', $this->get_id(), wc_get_page_permalink( 'myaccount' ) ), $this ); + } + + /** + * Get an item object. + * + * @param int $item_id ID of item to get. + * + * @return ShipmentItem|false + */ + public function get_item( $item_id ) { + $items = $this->get_items(); + + if ( isset( $items[ $item_id ] ) ) { + return $items[ $item_id ]; + } + + return false; + } + + /** + * Remove item from the shipment. + * + * @param int $item_id Item ID to delete. + * + * @return false|void + */ + public function remove_item( $item_id ) { + $item = $this->get_item( $item_id ); + + // Unset and remove later. + $this->items_to_delete[] = $item; + + unset( $this->items[ $item->get_id() ] ); + + $this->reset_content_data(); + $this->calculate_totals(); + $this->sync_packaging(); + } + + public function update_item_quantity( $item_id, $quantity = 1 ) { + if ( $item = $this->get_item( $item_id ) ) { + $item->set_quantity( $quantity ); + + if ( array_key_exists( 'quantity', $item->get_changes() ) ) { + $this->sync_packaging(); + } + + return true; + } + + return false; + } + + /** + * Adds a shipment item to this shipment. The shipment item will not persist until save. + * + * @since 3.0.0 + * @param ShipmentItem $item Shipment item object. + * + * @return false|void + */ + public function add_item( $item ) { + // Make sure that items are loaded + $items = $this->get_items(); + + // Set parent. + $item->set_shipment_id( $this->get_id() ); + + // Append new row with generated temporary ID. + $item_id = $item->get_id(); + + if ( $item_id ) { + $this->items[ $item_id ] = $item; + } else { + $this->items[ 'new:' . count( $this->items ) ] = $item; + } + + $this->items_to_pack = null; + + $this->reset_content_data(); + $this->calculate_totals(); + $this->sync_packaging(); + } + + /** + * Reset item content data. + */ + protected function reset_content_data() { + $this->weights = null; + $this->lengths = null; + $this->widths = null; + $this->heights = null; + $this->volumes = null; + } + + /** + * Handle the status transition. + */ + protected function status_transition() { + $status_transition = $this->status_transition; + + // Reset status transition variable. + $this->status_transition = false; + + if ( $status_transition ) { + try { + /** + * Action that fires before a shipment status transition happens. + * + * @param integer $shipment_id The shipment id. + * @param Shipment $shipment The shipment object. + * @param array $status_transition The status transition data. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_before_status_change', $this->get_id(), $this, $this->status_transition ); + + $status_to = $status_transition['to']; + $status_hook_prefix = 'woocommerce_gzd_' . ( 'simple' === $this->get_type() ? '' : $this->get_type() . '_' ) . 'shipment_status'; + + /** + * Action that indicates shipment status change to a specific status. + * + * The dynamic portion of the hook name, `$status_hook_prefix` constructs a unique prefix + * based on the shipment type. `$status_to` refers to the new shipment status. + * + * Example hook name: `woocommerce_gzd_return_shipment_status_processing` + * + * @param integer $shipment_id The shipment id. + * @param Shipment $shipment The shipment object. + * + * @see wc_gzd_get_shipment_statuses() + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$status_hook_prefix}_$status_to", $this->get_id(), $this ); + + if ( ! empty( $status_transition['from'] ) ) { + $status_from = $status_transition['from']; + + /** + * Action that indicates shipment status change from a specific status to a specific status. + * + * The dynamic portion of the hook name, `$status_hook_prefix` constructs a unique prefix + * based on the shipment type. `$status_from` refers to the old shipment status. + * `$status_to` refers to the new status. + * + * Example hook name: `woocommerce_gzd_return_shipment_status_processing_to_shipped` + * + * @param integer $shipment_id The shipment id. + * @param Shipment $shipment The shipment object. + * + * @see wc_gzd_get_shipment_statuses() + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$status_hook_prefix}_{$status_from}_to_{$status_to}", $this->get_id(), $this ); + + /** + * Action that indicates shipment status change. + * + * @param integer $shipment_id The shipment id. + * @param string $status_from The old shipment status. + * @param string $status_to The new shipment status. + * @param Shipment $shipment The shipment object. + * + * @see wc_gzd_get_shipment_statuses() + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_status_changed', $this->get_id(), $status_from, $status_to, $this ); + } + } catch ( Exception $e ) { + $logger = wc_get_logger(); + $logger->error( + sprintf( 'Status transition of shipment #%d errored!', $this->get_id() ), + array( + 'shipment' => $this, + 'error' => $e, + ) + ); + } + } + } + + /** + * Remove all items from the shipment. + */ + public function remove_items() { + $this->data_store->delete_items( $this ); + $this->items = array(); + + $this->items_to_pack = null; + + $this->reset_content_data(); + $this->calculate_totals(); + $this->sync_packaging(); + } + + /** + * Save all items which are part of this shipment. + */ + protected function save_items() { + $items_changed = false; + + foreach ( $this->items_to_delete as $item ) { + $item->delete(); + $items_changed = true; + } + + $this->items_to_delete = array(); + + foreach ( $this->get_items() as $item_key => $item ) { + $item->set_shipment_id( $this->get_id() ); + + $item_id = $item->save(); + + // If ID changed (new item saved to DB)... + if ( $item_id !== $item_key ) { + $this->items[ $item_id ] = $item; + + unset( $this->items[ $item_key ] ); + + $items_changed = true; + } + } + } + + /** + * Finds an ShipmentItem based on an order item id. + * + * @param integer $order_item_id + * + * @return bool|ShipmentItem + */ + public function get_item_by_order_item_id( $order_item_id ) { + $items = $this->get_items(); + + foreach ( $items as $item ) { + if ( $item->get_order_item_id() === (int) $order_item_id ) { + return $item; + } + } + + return false; + } + + /** + * Returns version. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return integer + */ + public function get_version( $context = 'view' ) { + return $this->get_prop( 'version', $context ); + } + + /** + * Returns the packaging id belonging to the shipment. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return integer + */ + public function get_packaging_id( $context = 'view' ) { + return $this->get_prop( 'packaging_id', $context ); + } + + public function get_packaging() { + if ( is_null( $this->packaging ) && $this->get_packaging_id() > 0 ) { + if ( $packaging = wc_gzd_get_packaging( $this->get_packaging_id() ) ) { + // Do only allow load packaging if it does really exist in DB. + if ( $packaging->get_id() > 0 ) { + $this->packaging = $packaging; + } + } + } + + return $this->packaging; + } + + /** + * Returns a list of available (fitting) packaging options for the current shipment + * + * @return Packaging[] + */ + public function get_available_packaging() { + $packaging_store = \WC_Data_Store::load( 'packaging' ); + + return apply_filters( "{$this->get_hook_prefix()}available_packaging", $packaging_store->find_available_packaging_for_shipment( $this ), $this ); + } + + /** + * Returns a list of user-selectable packaging options for the current shipment. + * + * @return Packaging[] + */ + public function get_selectable_packaging() { + return apply_filters( "{$this->get_hook_prefix()}selectable_packaging", $this->get_available_packaging(), $this ); + } + + public function get_default_packaging() { + $packaging_store = \WC_Data_Store::load( 'packaging' ); + $default_packaging = $packaging_store->find_best_match_for_shipment( $this ); + + if ( ! $default_packaging ) { + $setting = Package::get_setting( 'default_packaging' ); + + if ( ! empty( $setting ) && wc_gzd_get_packaging( $setting ) ) { + $default_packaging = wc_gzd_get_packaging( $setting ); + } + } + + return apply_filters( "{$this->get_hook_prefix()}default_packaging_id", $default_packaging, $this ); + } + + /** + * Tries to fetch the order for the current shipment. + * + * @return bool|WC_Order|null + */ + abstract public function get_order(); + + abstract public function get_order_id(); + + /** + * Returns the formatted order number. + * + * @return string + */ + public function get_order_number() { + if ( $order = $this->get_order() ) { + return $order->get_order_number(); + } + + return $this->get_order_id(); + } + + /** + * Returns whether the Shipment contains an order item or not. + * + * @param integer|integer[] $item_id + * + * @return boolean + */ + public function contains_order_item( $item_id ) { + + if ( ! is_array( $item_id ) ) { + $item_id = array( $item_id ); + } + + $new_items = $item_id; + + foreach ( $item_id as $key => $order_item_id ) { + + if ( is_a( $order_item_id, 'WC_Order_Item' ) ) { + $order_item_id = $order_item_id->get_id(); + $item_id[ $key ] = $order_item_id; + } + + if ( $this->get_item_by_order_item_id( $order_item_id ) ) { + unset( $new_items[ $key ] ); + } + } + + $contains = empty( $new_items ) ? true : false; + + /** + * Filter to adjust whether a Shipment contains a specific order item or not. + * + * @param boolean $contains Whether the Shipment contains the order item or not. + * @param integer[] $order_item_id The order item id(s). + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipment_contains_order_item', $contains, $item_id, $this ); + } + + public function get_shippable_item_count() { + return 0; + } + + /** + * Finds an ShipmentItem based on an item parent id. + * + * @param integer $item_parent_id + * + * @return bool|ShipmentItem + */ + public function get_item_by_item_parent_id( $item_parent_id ) { + $items = $this->get_items(); + + foreach ( $items as $item ) { + if ( $item->get_parent_id() === $item_parent_id ) { + return $item; + } + } + + return false; + } + + public function needs_items( $available_items = false ) { + return false; + } + + public function sync( $args = array() ) { + return false; + } + + public function sync_items( $args = array() ) { + return false; + } + + /** + * Returns a label + * + * @return boolean|ShipmentLabel|ShipmentReturnLabel + */ + public function get_label() { + $label = false; + $prefix = ''; + + if ( $provider = $this->get_shipping_provider_instance() ) { + $prefix = $provider->get_name() . '_'; + $label = $provider->get_label( $this ); + } + + /** + * Filter for shipping providers to retrieve the `ShipmentLabel` corresponding to a certain shipment. + * + * 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` + * + * @param boolean|ShipmentLabel $label The label instance. + * @param Shipment $shipment The current shipment instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}{$prefix}label", $label, $this ); + } + + /** + * Output label admin fields. + */ + public function get_label_settings_html() { + $hook_prefix = $this->get_general_hook_prefix(); + $html = ''; + + if ( $provider = $this->get_shipping_provider_instance() ) { + $hook_prefix = $hook_prefix . '_' . $provider->get_name(); + $html = $provider->get_label_fields_html( $this ); + } + + /** + * Action for shipping providers to output available admin settings while creating a label. + * + * The dynamic portion of this hook, `$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_print_dhl_label_admin_fields` + * + * @param Shipment $shipment The current shipment instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$hook_prefix}label_settings_html", $html, $this ); + } + + /** + * @param $props + * + * @return true|ShipmentError + */ + public function create_label( $props = false ) { + $hook_prefix = $this->get_general_hook_prefix(); + $provider_name = ''; + $error = new ShipmentError(); + + /** + * Sanitize props + */ + if ( is_array( $props ) ) { + foreach ( $props as $key => $value ) { + $props[ $key ] = wc_clean( wp_unslash( $value ) ); + } + } + + if ( $provider = $this->get_shipping_provider_instance() ) { + $provider_name = $provider->get_name(); + $result = $provider->create_label( $this, $props ); + + if ( is_wp_error( $result ) ) { + $error = wc_gzd_get_shipment_error( $result ); + + if ( ! $error->is_soft_error() ) { + return $error; + } + } + } else { + /** + * Action for shipping providers to create the `ShipmentLabel` corresponding to a certain shipment. + * + * The dynamic portion of this hook, `$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_create_dhl_label` + * + * @param array|false $props Array containing props extracted from post data (if created manually). + * @param WP_Error $error An WP_Error instance useful for returning errors while creating the label. + * @param Shipment $shipment The current shipment instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$hook_prefix}create_{$provider_name}label", $props, $error, $this ); + + if ( wc_gzd_shipment_wp_error_has_errors( $error ) && ! $error->is_soft_error() ) { + return $error; + } + } + + if ( $label = $this->get_label() ) { + $this->set_tracking_id( $label->get_number() ); + + /** + * Action for shipping providers to adjust the shipment before updating it after a label has + * been successfully generated. + * + * The dynamic portion of this hook, `$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_created_dhl_label` + * + * @param Shipment $shipment The current shipment instance. + * @param array $props Array containing props extracted from post data (if created manually) and sanitized via `wc_clean`. + * @param array $raw_data Raw post data unsanitized. + * + * @since 3.1.2 + * @package Vendidero/Germanized/Shipments + */ + do_action( "{$hook_prefix}created_{$provider_name}label", $this, $props ); + + do_action( "{$hook_prefix}created_label", $this, $props ); + + $this->save(); + } + + if ( wc_gzd_shipment_wp_error_has_errors( $error ) ) { + return $error; + } + + return true; + } + + public function delete_label( $force = false ) { + if ( $this->supports_label() && ( $label = $this->get_label() ) ) { + $label->delete( $force ); + $this->set_tracking_id( '' ); + $this->save(); + + return true; + } + + return false; + } + + /** + * Whether or not the current shipments supports labels or not. + * + * @return bool + */ + public function supports_label() { + if ( $provider = $this->get_shipping_provider_instance() ) { + if ( $provider->supports_labels( $this->get_type(), $this ) ) { + return true; + } + } + + return false; + } + + /** + * Whether or not the current shipments needs a label or not. + * + * @return bool + */ + public function needs_label( $check_status = true ) { + $needs_label = true; + $provider = $this->get_shipping_provider(); + $hook_prefix = $this->get_general_hook_prefix(); + + if ( ! empty( $provider ) ) { + $provider = $provider . '_'; + } + + if ( $this->has_label() ) { + $needs_label = false; + } + + if ( ! $this->supports_label() ) { + $needs_label = false; + } + + if ( $shipping_provider = $this->get_shipping_provider_instance() ) { + if ( ! $shipping_provider->is_activated() ) { + $needs_label = false; + } + } + + // If shipment is already delivered + if ( $check_status && $this->is_shipped() ) { + $needs_label = false; + } + + /** + * Filter for shipping providers to decide whether the shipment needs a label or not. + * + * The dynamic portion of this hook, `$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_needs_dhl_label` + * + * @param boolean $needs_label Whether or not the shipment needs a label. + * @param boolean $check_status Whether or not checking the shipment status is needed. + * @param Shipment $shipment The current shipment instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$hook_prefix}needs_{$provider}label", $needs_label, $check_status, $this ); + } + + /** + * Whether or not the current shipment has a valid label or not. + * + * @return bool + */ + public function has_label() { + $label = $this->get_label(); + + if ( $label && is_a( $label, '\Vendidero\Germanized\Shipments\Interfaces\ShipmentLabel' ) ) { + if ( 'return' === $label->get_type() ) { + if ( ! is_a( $label, '\Vendidero\Germanized\Shipments\Interfaces\ShipmentReturnLabel' ) ) { + return false; + } + } + + return true; + } else { + return false; + } + } + + public function get_label_download_url( $args = array() ) { + $download_url = ''; + $provider = $this->get_shipping_provider(); + + if ( $label = $this->get_label() ) { + $download_url = $label->get_download_url( $args ); + } + + /** + * 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 Shipment $shipment The current shipment instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}{$provider}label_download_url", $download_url, $this ); + } + + public function add_note( $note, $added_by_user = false ) { + if ( $order = $this->get_order() ) { + if ( is_callable( array( $order, 'add_order_note' ) ) ) { + $order->add_order_note( $note, 0, $added_by_user ); + } + } + } + + /** + * Calculate totals based on contained items. + */ + protected function calculate_totals() { + $total = 0; + $subtotal = 0; + + foreach ( $this->get_items() as $item ) { + $total += round( $item->get_total(), wc_get_price_decimals() ); + $subtotal += round( $item->get_subtotal(), wc_get_price_decimals() ); + } + + $this->set_total( $total ); + + if ( empty( $subtotal ) ) { + $subtotal = $total; + } + + $this->set_subtotal( $subtotal ); + } + + public function delete( $force_delete = false ) { + $this->delete_label( $force_delete ); + + return parent::delete( $force_delete ); + } + + /** + * Save data to the database. + * + * @return integer shipment id + */ + public function save() { + try { + $this->calculate_totals(); + $is_new = false; + + if ( array_key_exists( 'packaging_id', $this->get_changes() ) || $this->is_editable() ) { + $this->update_packaging(); + } + + if ( $this->data_store ) { + // Trigger action before saving to the DB. Allows you to adjust object props before save. + do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); + + if ( $this->get_id() ) { + $this->data_store->update( $this ); + } else { + $this->data_store->create( $this ); + $is_new = true; + } + } + + $this->save_items(); + + /** + * Trigger action after saving shipment to the DB. + * + * @param Shipment $shipment The shipment object being saved. + * @param WC_Data_Store_WP $data_store THe data store persisting the data. + */ + do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store ); + + $hook_postfix = ''; + + if ( 'simple' !== $this->get_type() ) { + $hook_postfix = $this->get_type() . '_'; + } + + /** + * Trigger action after saving shipment to the DB. + * + * The dynamic portion of this hook, `$hook_postfix` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_after_save + * + * @param Shipment $shipment The shipment object being saved. + * @param boolean $is_new Indicator to determine whether this is a new shipment or not. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( "woocommerce_gzd_{$hook_postfix}shipment_after_save", $this, $is_new ); + + $this->status_transition(); + $this->reset_content_data(); + + } catch ( Exception $e ) { + /** + * This is a tweak to prevent the WooCommerce PayPal Payments Plugin compatibility script + * from breaking our code in case an error occurs while transmitting tracking data to PayPal. + * This tweak should only be included as long as the bug persists. + * @TODO Check whether the issue persists in next release cycles + * + * @see https://github.com/woocommerce/woocommerce-paypal-payments/issues/1020 + */ + if ( is_a( $e, 'WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException' ) || is_a( $e, 'WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException' ) ) { + $this->status_transition(); + $this->reset_content_data(); + } else { + $logger = wc_get_logger(); + $logger->error( + sprintf( 'Error saving shipment #%d', $this->get_id() ), + array( + 'shipment' => $this, + 'error' => $e, + ) + ); + } + } + + return $this->get_id(); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ShipmentError.php b/packages/woocommerce-germanized-shipments/src/ShipmentError.php new file mode 100644 index 000000000..da87055f4 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShipmentError.php @@ -0,0 +1,88 @@ +set_is_soft_error(); + } + + public function add( $code, $message, $data = '' ) { + parent::add( $code, $message, $data ); + + $this->set_is_soft_error(); + } + + public function get_error_messages_by_type() { + $errors = $this->get_error_messages(); + $soft = $this->get_soft_error_messages(); + + return array( + 'error' => array_diff( $errors, $soft ), + 'soft' => $soft, + ); + } + + public function get_soft_error_messages( $code = '' ) { + // Return all messages if no code specified. + if ( empty( $code ) ) { + $all_messages = array(); + foreach ( (array) $this->errors as $code => $messages ) { + $data = $this->get_error_data( $code ); + + if ( is_string( $data ) && 'soft' === $data ) { + $all_messages = array_merge( $all_messages, $messages ); + } + } + + return $all_messages; + } + + if ( isset( $this->errors[ $code ] ) ) { + $data = $this->get_error_data( $code ); + + if ( is_string( $data ) && 'soft' === $data ) { + return $this->errors[ $code ]; + } + + return array(); + } else { + return array(); + } + } + + public function add_soft_error( $code, $message ) { + parent::add( $code, $message, 'soft' ); + } + + public function is_soft_error() { + return $this->is_soft_error; + } + + protected function set_is_soft_error() { + $is_soft_error = true; + + foreach ( $this->get_error_codes() as $code ) { + $error_data = $this->get_error_data( $code ); + + if ( ! is_string( $error_data ) || 'soft' !== $error_data ) { + $is_soft_error = false; + break; + } + } + + $this->is_soft_error = $is_soft_error; + } + + public static function from_wp_error( \WP_Error $from ) { + $error = new self(); + $error->merge_from( $from ); + + return $error; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ShipmentFactory.php b/packages/woocommerce-germanized-shipments/src/ShipmentFactory.php new file mode 100644 index 000000000..f6595c8e9 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShipmentFactory.php @@ -0,0 +1,88 @@ +get_shipment_type( $shipment_id ); + + /** + * Shipment type cannot be found, seems to not exist. + */ + if ( empty( $shipment_type ) ) { + return false; + } + + $shipment_type_data = wc_gzd_get_shipment_type_data( $shipment_type ); + } else { + $shipment_type_data = wc_gzd_get_shipment_type_data( $shipment_type ); + } + + if ( $shipment_type_data ) { + $classname = $shipment_type_data['class_name']; + } else { + $classname = false; + } + + /** + * Filter to adjust the classname used to construct a Shipment. + * + * @param string $clasname The classname to be used. + * @param integer $shipment_id The shipment id. + * @param string $shipment_type The shipment type. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $classname = apply_filters( 'woocommerce_gzd_shipment_class', $classname, $shipment_id, $shipment_type ); + + if ( ! class_exists( $classname ) ) { + return false; + } + + try { + return new $classname( $shipment_id ); + } catch ( Exception $e ) { + wc_caught_exception( $e, __FUNCTION__, array( $shipment_id, $shipment_type ) ); + return false; + } + } + + public static function get_shipment_id( $shipment ) { + if ( is_numeric( $shipment ) ) { + return $shipment; + } elseif ( $shipment instanceof Shipment ) { + return $shipment->get_id(); + } elseif ( ! empty( $shipment->shipment_id ) ) { + return $shipment->shipment_id; + } else { + return false; + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ShipmentItem.php b/packages/woocommerce-germanized-shipments/src/ShipmentItem.php new file mode 100644 index 000000000..4b3fe3de3 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShipmentItem.php @@ -0,0 +1,605 @@ + 0, + 'order_item_id' => 0, + 'parent_id' => 0, + 'quantity' => 1, + 'product_id' => 0, + 'weight' => '', + 'width' => '', + 'length' => '', + 'height' => '', + 'sku' => '', + 'name' => '', + 'total' => 0, + 'subtotal' => 0, + 'hs_code' => '', + 'manufacture_country' => '', + 'attributes' => array(), + ); + + /** + * Stores meta in cache for future reads. + * A group must be set to to enable caching. + * + * @var string + */ + protected $cache_group = 'shipment-items'; + + /** + * Meta type. This should match up with + * the types available at https://developer.wordpress.org/reference/functions/add_metadata/. + * WP defines 'post', 'user', 'comment', and 'term'. + * + * @var string + */ + protected $meta_type = 'shipment_item'; + + /** + * This is the name of this object type. + * + * @var string + */ + protected $object_type = 'shipment_item'; + + /** + * Constructor. + * + * @param int|object|array $item ID to load from the DB, or WC_Order_Item object. + */ + public function __construct( $item = 0 ) { + parent::__construct( $item ); + + if ( $item instanceof ShipmentItem ) { + $this->set_id( $item->get_id() ); + } elseif ( is_numeric( $item ) && $item > 0 ) { + $this->set_id( $item ); + } else { + $this->set_object_read( true ); + } + + $this->data_store = WC_Data_Store::load( 'shipment-item' ); + + // 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 ); + } + } + + /** + * Merge changes with data and clear. + * Overrides WC_Data::apply_changes. + * array_replace_recursive does not work well for order items because it merges taxes 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(); + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + */ + + public function get_type() { + return 'simple'; + } + + /** + * Get order ID this meta belongs to. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int + */ + public function get_shipment_id( $context = 'view' ) { + return $this->get_prop( 'shipment_id', $context ); + } + + /** + * Get order ID this meta belongs to. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int + */ + public function get_order_item_id( $context = 'view' ) { + return $this->get_prop( 'order_item_id', $context ); + } + + /** + * Get order ID this meta belongs to. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int + */ + public function get_product_id( $context = 'view' ) { + return $this->get_prop( 'product_id', $context ); + } + + /** + * Get item parent id. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int + */ + public function get_parent_id( $context = 'view' ) { + return $this->get_prop( 'parent_id', $context ); + } + + /** + * Get order ID this meta belongs to. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int + */ + public function get_total( $context = 'view' ) { + return $this->get_prop( 'total', $context ); + } + + /** + * Get order ID this meta belongs to. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return int + */ + public function get_subtotal( $context = 'view' ) { + $subtotal = $this->get_prop( 'subtotal', $context ); + + if ( 'view' === $context && empty( $subtotal ) ) { + $subtotal = $this->get_total(); + } + + return $subtotal; + } + + /** + * Get quantity. + * + * @return int + */ + public function get_sku( $context = 'view' ) { + return $this->get_prop( 'sku', $context ); + } + + /** + * Get quantity. + * + * @return int + */ + public function get_quantity( $context = 'view' ) { + return $this->get_prop( 'quantity', $context ); + } + + /** + * Get weight. + * + * @return string + */ + public function get_weight( $context = 'view' ) { + return $this->get_prop( 'weight', $context ); + } + + /** + * Get width. + * + * @return string + */ + public function get_width( $context = 'view' ) { + return $this->get_prop( 'width', $context ); + } + + /** + * Get length. + * + * @return string + */ + public function get_length( $context = 'view' ) { + return $this->get_prop( 'length', $context ); + } + + /** + * Get height. + * + * @return string + */ + public function get_height( $context = 'view' ) { + return $this->get_prop( 'height', $context ); + } + + public function get_name( $context = 'view' ) { + $name = $this->get_prop( 'name', $context ); + + if ( 'view' === $context && empty( $name ) && ( $item = $this->get_order_item() ) ) { + $name = $item->get_name(); + } + + return $name; + } + + public function get_hs_code( $context = 'view' ) { + $legacy = $this->get_meta( '_dhl_hs_code', $context ); + $prop = $this->get_prop( 'hs_code', $context ); + + if ( '' === $prop && ! empty( $legacy ) ) { + $prop = $legacy; + } + + return $prop; + } + + public function get_manufacture_country( $context = 'view' ) { + $legacy = $this->get_meta( '_dhl_manufacture_country', $context ); + $prop = $this->get_prop( 'manufacture_country', $context ); + + if ( '' === $prop && ! empty( $legacy ) ) { + $prop = $legacy; + } + + return $prop; + } + + /** + * Get attributes. + * + * @return string[] + */ + public function get_attributes( $context = 'view' ) { + return $this->get_prop( 'attributes', $context ); + } + + public function has_attributes() { + $attributes = $this->get_attributes(); + + return ! empty( $attributes ); + } + + /** + * Get parent order object. + * + * @return SimpleShipment|ReturnShipment|Shipment + */ + public function get_shipment() { + if ( is_null( $this->shipment ) && 0 < $this->get_shipment_id() ) { + $this->shipment = wc_gzd_get_shipment( $this->get_shipment_id() ); + } + + $shipment = ( $this->shipment ) ? $this->shipment : false; + + return $shipment; + } + + /** + * Sets the linked shipment instance. + * + * @param Shipment $shipment + */ + public function set_shipment( &$shipment ) { + $this->shipment = $shipment; + } + + /** + * Syncs an item with either it's parent item or the corresponding order item. + * + * @param array $args + */ + public function sync( $args = array() ) { + $item = false; + + if ( $shipment = $this->get_shipment() ) { + if ( 'return' === $shipment->get_type() ) { + if ( $shipment = $this->get_shipment() ) { + if ( $order_shipment = $shipment->get_order_shipment() ) { + $item = $order_shipment->get_simple_shipment_item( $this->get_order_item_id() ); + } + } + } else { + $item = $this->get_order_item(); + } + } + + if ( is_a( $item, '\Vendidero\Germanized\Shipments\ShipmentItem' ) ) { + + $default_data = $item->get_data(); + + unset( $default_data['id'] ); + unset( $default_data['shipment_id'] ); + + $default_data['parent_id'] = $item->get_id(); + $args = wp_parse_args( $args, $default_data ); + + } elseif ( is_a( $item, 'WC_Order_Item' ) ) { + + if ( is_callable( array( $item, 'get_variation_id' ) ) && is_callable( array( $item, 'get_product_id' ) ) ) { + $this->set_product_id( $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id() ); + } elseif ( is_callable( array( $item, 'get_product_id' ) ) ) { + $this->set_product_id( $item->get_product_id() ); + } + + $args = wp_parse_args( + $args, + array( + 'quantity' => 1, + ) + ); + + $product = $this->get_product(); + $s_product = wc_gzd_shipments_get_product( $product ); + + /** + * Calculate the order item total per unit to make sure it is independent from + * shipment item quantity. + */ + $tax_total = is_callable( array( $item, 'get_total_tax' ) ) ? ( (float) $item->get_total_tax() / $item->get_quantity() ) * $args['quantity'] : 0; + $total = is_callable( array( $item, 'get_total' ) ) ? ( (float) $item->get_total() / $item->get_quantity() ) * $args['quantity'] : 0; + $subtotal = is_callable( array( $item, 'get_subtotal' ) ) ? (float) $item->get_subtotal() / $item->get_quantity() * $args['quantity'] : 0; + $tax_subtotal = is_callable( array( $item, 'get_subtotal_tax' ) ) ? (float) $item->get_subtotal_tax() / $item->get_quantity() * $args['quantity'] : 0; + $meta = $item->get_formatted_meta_data( apply_filters( "{$this->get_hook_prefix()}hide_meta_prefix", '_', $this ), apply_filters( "{$this->get_hook_prefix()}include_all_meta", false, $this ) ); + $attributes = array(); + + foreach ( $meta as $meta_id => $entry ) { + $attributes[] = array( + 'key' => $entry->key, + 'value' => str_replace( array( '

', '

' ), '', $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() : '', + '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_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..4c2eae436 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShipmentQuery.php @@ -0,0 +1,586 @@ + array_keys( wc_gzd_get_shipment_statuses() ), + 'limit' => 10, + 'order_id' => '', + 'parent_id' => '', + 'product_ids' => '', + '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'] = 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['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'] ) ) { + $this->query_where .= $wpdb->prepare( ' AND shipment_order_id = %d', $this->args['order_id'] ); + } + + // 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 + } + + // 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/ShippingProvider/Auto.php b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Auto.php new file mode 100644 index 000000000..6439fe820 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Auto.php @@ -0,0 +1,570 @@ + '', + 'label_minimum_shipment_weight' => '', + 'label_auto_enable' => false, + 'label_auto_shipment_status' => 'gzd-processing', + 'label_return_auto_enable' => false, + 'label_return_auto_shipment_status' => 'gzd-processing', + 'label_auto_shipment_status_shipped' => false, + ); + + public function get_label_default_shipment_weight( $context = 'view' ) { + $weight = $this->get_prop( 'label_default_shipment_weight', $context ); + + if ( 'view' === $context && '' === $weight ) { + $weight = $this->get_default_label_default_shipment_weight(); + } + + return $weight; + } + + protected function get_default_label_default_shipment_weight() { + return 0; + } + + public function get_label_minimum_shipment_weight( $context = 'view' ) { + $weight = $this->get_prop( 'label_minimum_shipment_weight', $context ); + + if ( 'view' === $context && '' === $weight ) { + $weight = $this->get_default_label_minimum_shipment_weight(); + } + + return $weight; + } + + protected function get_default_label_minimum_shipment_weight() { + return 0.5; + } + + /** + * @param false|Shipment $shipment + * + * @return boolean + */ + public function automatically_generate_label( $shipment = false ) { + $setting_key = 'label_auto_enable'; + + if ( $shipment ) { + if ( 'return' === $shipment->get_type() ) { + $setting_key = 'label_return_auto_enable'; + } + + return wc_string_to_bool( $this->get_shipment_setting( $shipment, $setting_key, false ) ); + } else { + return wc_string_to_bool( $this->get_setting( $setting_key, false ) ); + } + } + + /** + * @param false|Shipment $shipment + * + * @return string + */ + public function get_label_automation_shipment_status( $shipment = false ) { + $setting_key = 'label_auto_shipment_status'; + + if ( $shipment ) { + if ( 'return' === $shipment->get_type() ) { + $setting_key = 'label_return_auto_shipment_status'; + } + + return $this->get_shipment_setting( $shipment, $setting_key, 'gzd-processing' ); + } else { + return $this->get_setting( $setting_key, 'gzd-processing' ); + } + } + + public function automatically_set_shipment_status_shipped( $shipment = false ) { + $setting_key = 'label_auto_shipment_status_shipped'; + + if ( $shipment ) { + return wc_string_to_bool( $this->get_shipment_setting( $shipment, $setting_key, false ) ); + } else { + return wc_string_to_bool( $this->get_setting( $setting_key, false ) ); + } + } + + public function get_label_auto_enable( $context = 'view' ) { + return $this->get_prop( 'label_auto_enable', $context ); + } + + public function get_label_auto_shipment_status_shipped( $context = 'view' ) { + return $this->get_prop( 'label_auto_shipment_status_shipped', $context ); + } + + public function get_label_auto_shipment_status( $context = 'view' ) { + return $this->get_prop( 'label_auto_shipment_status', $context ); + } + + public function automatically_generate_return_label() { + return $this->get_label_return_auto_enable(); + } + + public function get_label_return_auto_enable( $context = 'view' ) { + return $this->get_prop( 'label_return_auto_enable', $context ); + } + + public function get_label_return_auto_shipment_status( $context = 'view' ) { + return $this->get_prop( 'label_return_auto_shipment_status', $context ); + } + + public function is_sandbox() { + return false; + } + + public function set_label_default_shipment_weight( $weight ) { + $this->set_prop( 'label_default_shipment_weight', ( '' === $weight ? '' : wc_format_decimal( $weight ) ) ); + } + + public function set_label_minimum_shipment_weight( $weight ) { + $this->set_prop( 'label_minimum_shipment_weight', ( '' === $weight ? '' : wc_format_decimal( $weight ) ) ); + } + + public function set_label_auto_enable( $enable ) { + $this->set_prop( 'label_auto_enable', wc_string_to_bool( $enable ) ); + } + + public function set_label_auto_shipment_status_shipped( $enable ) { + $this->set_prop( 'label_auto_shipment_status_shipped', wc_string_to_bool( $enable ) ); + } + + public function set_label_auto_shipment_status( $status ) { + $this->set_prop( 'label_auto_shipment_status', $status ); + } + + public function set_label_return_auto_enable( $enable ) { + $this->set_prop( 'label_return_auto_enable', wc_string_to_bool( $enable ) ); + } + + public function set_label_return_auto_shipment_status( $status ) { + $this->set_prop( 'label_return_auto_shipment_status', $status ); + } + + public function get_label_classname( $type ) { + return '\Vendidero\Germanized\Shipments\Labels\Simple'; + } + + /** + * Whether or not this instance is a manual integration. + * Manual integrations are constructed dynamically from DB and do not support + * automatic shipment handling, e.g. label creation. + * + * @return bool + */ + public function is_manual_integration() { + return false; + } + + /** + * Whether or not this instance supports a certain label type. + * + * @param string $label_type The label type e.g. simple or return. + * + * @return bool + */ + public function supports_labels( $label_type, $shipment = false ) { + return true; + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + * + * @return mixed|void + */ + public function get_label( $shipment ) { + $type = wc_gzd_get_label_type_by_shipment( $shipment ); + $label = wc_gzd_get_label_by_shipment( $shipment, $type ); + + return apply_filters( "{$this->get_hook_prefix()}label", $label, $shipment, $this ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + public function get_label_fields_html( $shipment ) { + /** + * Setup local variables + */ + $settings = $this->get_label_fields( $shipment ); + $provider = $this; + + if ( is_wp_error( $settings ) ) { + $error = $settings; + + ob_start(); + include Package::get_path() . '/includes/admin/views/label/html-shipment-label-backbone-error.php'; + $html = ob_get_clean(); + } else { + ob_start(); + include Package::get_path() . '/includes/admin/views/label/html-shipment-label-backbone-form.php'; + $html = ob_get_clean(); + } + + return apply_filters( "{$this->get_hook_prefix()}label_fields_html", $html, $shipment, $this ); + } + + protected function get_automation_settings( $for_shipping_method = false ) { + $settings = array( + array( + 'title' => _x( 'Automation', 'shipments', 'woocommerce-germanized' ), + 'allow_override' => true, + 'type' => 'title', + 'id' => 'shipping_provider_label_auto_options', + ), + ); + + $shipment_statuses = array_diff_key( wc_gzd_get_shipment_statuses(), array_fill_keys( array( 'gzd-draft', 'gzd-delivered', 'gzd-returned', 'gzd-requested' ), '' ) ); + + $settings = array_merge( + $settings, + array( + array( + 'title' => _x( 'Labels', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Automatically create labels for shipments.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'label_auto_enable', + 'type' => 'gzd_toggle', + 'value' => wc_bool_to_string( $this->get_setting( 'label_auto_enable' ) ), + ), + + array( + 'title' => _x( 'Status', 'shipments', 'woocommerce-germanized' ), + 'type' => 'select', + 'id' => 'label_auto_shipment_status', + 'desc' => '
' . _x( 'Choose a shipment status which should trigger generation of a label.', 'shipments', 'woocommerce-germanized' ) . ' ' . ( 'yes' === Package::get_setting( 'auto_enable' ) ? sprintf( _x( 'Your current default shipment status is: %s.', 'shipments', 'woocommerce-germanized' ), wc_gzd_get_shipment_status_name( Package::get_setting( 'auto_default_status' ) ) ) : '' ) . '
', + 'options' => $shipment_statuses, + 'class' => 'wc-enhanced-select', + 'custom_attributes' => array( 'data-show_if_label_auto_enable' => '' ), + 'value' => $this->get_setting( 'label_auto_shipment_status' ), + ), + + array( + 'title' => _x( 'Shipment Status', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Mark shipment as shipped after label has been created successfully.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'label_auto_shipment_status_shipped', + 'type' => 'gzd_toggle', + 'value' => wc_bool_to_string( $this->get_setting( 'label_auto_shipment_status_shipped' ) ), + ), + ) + ); + + if ( $this->supports_labels( 'return' ) ) { + $settings = array_merge( + $settings, + array( + array( + 'title' => _x( 'Returns', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Automatically create labels for returns.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'label_return_auto_enable', + 'type' => 'gzd_toggle', + 'value' => wc_bool_to_string( $this->get_setting( 'label_return_auto_enable' ) ), + ), + + array( + 'title' => _x( 'Status', 'shipments', 'woocommerce-germanized' ), + 'type' => 'select', + 'id' => 'label_return_auto_shipment_status', + 'desc' => '
' . _x( 'Choose a shipment status which should trigger generation of a return label.', 'shipments', 'woocommerce-germanized' ) . '
', + 'options' => $shipment_statuses, + 'class' => 'wc-enhanced-select', + 'custom_attributes' => array( 'data-show_if_label_return_auto_enable' => '' ), + 'value' => $this->get_setting( 'label_return_auto_shipment_status' ), + ), + ) + ); + } + + $settings = array_merge( + $settings, + array( + array( + 'type' => 'sectionend', + 'id' => 'shipping_provider_label_auto_options', + ), + ) + ); + + return $settings; + } + + public function get_settings_help_pointers( $section = '' ) { + return array(); + } + + protected function get_label_settings( $for_shipping_method = false ) { + $settings = array( + array( + 'title' => '', + 'type' => 'title', + 'id' => 'shipping_provider_label_options', + ), + + array( + 'title' => _x( 'Default content weight (kg)', 'shipments', 'woocommerce-germanized' ), + 'type' => 'text', + 'desc' => _x( 'Choose a default shipment content weight to be used for labels if no weight has been applied to the shipment.', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => true, + 'id' => 'label_default_shipment_weight', + 'css' => 'max-width: 60px;', + 'class' => 'wc_input_decimal', + 'default' => $this->get_default_label_default_shipment_weight(), + 'value' => wc_format_localized_decimal( $this->get_setting( 'label_default_shipment_weight' ) ), + ), + + array( + 'title' => _x( 'Minimum weight (kg)', 'shipments', 'woocommerce-germanized' ), + 'type' => 'text', + 'desc' => _x( 'Choose a minimum weight to be used for labels e.g. to prevent low shipment weight errors.', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => true, + 'id' => 'label_minimum_shipment_weight', + 'css' => 'max-width: 60px;', + 'class' => 'wc_input_decimal', + 'default' => $this->get_default_label_minimum_shipment_weight(), + 'value' => wc_format_localized_decimal( $this->get_setting( 'label_minimum_shipment_weight' ) ), + ), + + array( + 'type' => 'sectionend', + 'id' => 'shipping_provider_label_options', + ), + ); + + return $settings; + } + + protected function get_available_base_countries() { + $countries = array(); + + if ( function_exists( 'WC' ) && WC()->countries ) { + $countries = WC()->countries->get_countries(); + } + + return $countries; + } + + public function get_setting_sections() { + $sections = array( + '' => _x( 'General', 'shipments', 'woocommerce-germanized' ), + 'label' => _x( 'Labels', 'shipments', 'woocommerce-germanized' ), + 'automation' => _x( 'Automation', 'shipments', 'woocommerce-germanized' ), + ); + + $sections = array_replace_recursive( $sections, parent::get_setting_sections() ); + + return $sections; + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + public function get_label_fields( $shipment ) { + if ( 'return' === $shipment->get_type() ) { + return $this->get_return_label_fields( $shipment ); + } else { + return $this->get_simple_label_fields( $shipment ); + } + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + protected function get_simple_label_fields( $shipment ) { + $default = $this->get_default_label_product( $shipment ); + $available = $this->get_available_label_products( $shipment ); + + $settings = array( + array( + 'id' => 'product_id', + 'label' => sprintf( _x( '%s Product', 'shipments', 'woocommerce-germanized' ), $this->get_title() ), + 'description' => '', + 'options' => $this->get_available_label_products( $shipment ), + 'value' => $default && array_key_exists( $default, $available ) ? $default : '', + 'type' => 'select', + ), + ); + + return $settings; + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + protected function get_return_label_fields( $shipment ) { + return $this->get_simple_label_fields( $shipment ); + } + + /** + * @param Shipment $shipment + * @param $props + * + * @return ShipmentError|mixed + */ + protected function validate_label_request( $shipment, $props ) { + return $props; + } + + /** + * @param Shipment $shipment + * + * @return array + */ + protected function get_default_label_props( $shipment ) { + $default = array( + 'shipping_provider' => $this->get_name(), + 'weight' => wc_gzd_get_shipment_label_weight( $shipment ), + 'net_weight' => wc_gzd_get_shipment_label_weight( $shipment, true ), + 'shipment_id' => $shipment->get_id(), + 'services' => array(), + 'product_id' => $this->get_default_label_product( $shipment ), + ); + + $dimensions = wc_gzd_get_shipment_label_dimensions( $shipment ); + $default = array_merge( $default, $dimensions ); + + return $default; + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + * @param mixed $props + * + * @return ShipmentError|true + */ + public function create_label( $shipment, $props = false ) { + /** + * In case props is false this indicates an automatic (non-manual) request. + */ + if ( false === $props ) { + $props = $this->get_default_label_props( $shipment ); + } elseif ( is_array( $props ) ) { + $fields = $this->get_label_fields( $shipment ); + + /** + * By default checkbox fields won't be transmitted via POST data. + * In case the values does not exist within props, assume not checked. + */ + foreach ( $fields as $field ) { + if ( ! isset( $field['value'] ) ) { + continue; + } + + if ( 'checkbox' === $field['type'] && ! isset( $props[ $field['id'] ] ) ) { + // Exclude array fields from default checkbox handling + if ( isset( $field['name'] ) && strstr( $field['name'], '[]' ) ) { + continue; + } + + $props[ $field['id'] ] = 'no'; + } elseif ( 'multiselect' === $field['type'] ) { + if ( isset( $props[ $field['id'] ] ) ) { + $props[ $field['id'] ] = (array) $props[ $field['id'] ]; + } + } + } + + /** + * Merge with default data. That needs to be done after manually + * parsing checkboxes as missing data would be overridden with defaults. + */ + $props = wp_parse_args( $props, $this->get_default_label_props( $shipment ) ); + + foreach ( $props as $key => $value ) { + if ( substr( $key, 0, strlen( 'service_' ) ) === 'service_' ) { + $new_key = substr( $key, ( strlen( 'service_' ) ) ); + + if ( wc_string_to_bool( $value ) && in_array( $new_key, $this->get_available_label_services( $shipment ), true ) ) { + if ( ! in_array( $new_key, $props['services'], true ) ) { + $props['services'][] = $new_key; + } + unset( $props[ $key ] ); + } else { + if ( ( $service_key = array_search( $new_key, $props['services'], true ) ) !== false ) { + unset( $props['services'][ $service_key ] ); + } + unset( $props[ $key ] ); + } + } + } + } + + $props = $this->validate_label_request( $shipment, $props ); + + if ( is_wp_error( $props ) ) { + return $props; + } + + if ( isset( $props['services'] ) ) { + $props['services'] = array_unique( $props['services'] ); + } + + $label = Factory::get_label( 0, $this->get_name(), $shipment->get_type() ); + + if ( $label ) { + foreach ( $props as $key => $value ) { + $setter = "set_{$key}"; + + if ( is_callable( array( $label, $setter ) ) ) { + $label->{$setter}( $value ); + } else { + $label->update_meta_data( $key, $value ); + } + } + + $label->set_shipment( $shipment ); + + /** + * Fetch the label via API and store as file + */ + $result = $label->fetch(); + + if ( is_wp_error( $result ) ) { + $result = wc_gzd_get_shipment_error( $result ); + + if ( ! $result->is_soft_error() ) { + return $result; + } + } + + do_action( "{$this->get_general_hook_prefix()}created_label", $label, $this ); + $label_id = $label->save(); + + return is_wp_error( $result ) && $result->is_soft_error() ? $result : $label_id; + } + + return new ShipmentError( 'label-error', _x( 'Error while creating the label.', 'shipments', 'woocommerce-germanized' ) ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + public function get_available_label_services( $shipment ) { + return array(); + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + abstract public function get_available_label_products( $shipment ); + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + abstract public function get_default_label_product( $shipment ); +} diff --git a/packages/woocommerce-germanized-shipments/src/ShippingProvider/Helper.php b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Helper.php new file mode 100644 index 000000000..948ac1687 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Helper.php @@ -0,0 +1,217 @@ +get_shipping_provider_class_names(); + + if ( ! is_object( $provider ) ) { + if ( ! class_exists( $provider ) ) { + return false; + } + + $provider = new $provider(); + } else { + $classname = '\Vendidero\Germanized\Shipments\ShippingProvider\Simple'; + + if ( array_key_exists( $provider->shipping_provider_name, $classes ) ) { + $classname = $classes[ $provider->shipping_provider_name ]; + } + + $classname = apply_filters( 'woocommerce_gzd_shipping_provider_class_name', $classname, $provider->shipping_provider_name, $provider ); + + if ( ! class_exists( $classname ) ) { + $classname = '\Vendidero\Germanized\Shipments\ShippingProvider\Simple'; + } + + $provider = new $classname( $provider ); + } + + if ( ! $provider || ! is_a( $provider, '\Vendidero\Germanized\Shipments\Interfaces\ShippingProvider' ) ) { + return false; + } + + if ( is_null( $this->shipping_providers ) ) { + $this->shipping_providers = array(); + } + + $this->shipping_providers[ $provider->get_name() ] = $provider; + } + + /** + * Shipping providers register themselves by returning their main class name through the woocommerce_gzd_shipping_provider_integrations filter. + * + * @return array + */ + public function get_shipping_provider_class_names() { + $class_names = array(); + + /** + * This filter may be used to register additional shipping providers + * by adding a unique name as key and the classname to be loaded as value of the array. + * + * @param array $shipping_providers The shipping provider array + * + * @since 1.0.5 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( 'woocommerce_gzd_shipping_provider_class_names', $class_names ); + } + + public function is_shipping_provider_activated( $name ) { + /** + * Make sure that the plugin has initialised, e.g. during installs of shipping provider + */ + if ( ! did_action( 'woocommerce_gzd_shipments_init' ) ) { + Package::init(); + } + + return WC_Data_Store::load( 'shipping-provider' )->is_activated( $name ); + } + + /** + * Loads all shipping providers which are hooked in. + * + * @return ShippingProvider[] + */ + public function load_shipping_providers() { + if ( ! did_action( 'plugins_loaded' ) || doing_action( 'plugins_loaded' ) ) { + wc_doing_it_wrong( __FUNCTION__, _x( 'Loading shipping providers should only be triggered after the plugins_loaded action has fully been executed', 'shipments', 'woocommerce-germanized' ), '2.2.3' ); + return array(); + } + + $this->shipping_providers = array(); + + $shipping_providers = WC_Data_Store::load( 'shipping-provider' )->get_shipping_providers(); + $registered_providers = $this->get_shipping_provider_class_names(); + + foreach ( $registered_providers as $k => $provider ) { + if ( ! array_key_exists( $k, $shipping_providers ) ) { + $shipping_providers[ $k ] = $provider; + } + } + + // For the settings in the backend, and for non-shipping zone methods, we still need to load any registered classes here. + foreach ( $shipping_providers as $provider_name => $provider_class ) { + $this->register_shipping_provider( $provider_class ); + } + + /** + * This hook fires as soon as shipping providers are loaded. + * Additional shipping provider may be registered manually afterwards. + * + * @param Helper $providers The shipping providers instance + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_load_shipping_providers', $this ); + + // Return loaded methods. + return $this->get_shipping_providers(); + } + + /** + * Returns all registered shipping providers for usage. + * + * @return Simple|Auto|ShippingProvider[] + */ + public function get_shipping_providers() { + if ( is_null( $this->shipping_providers ) ) { + $this->load_shipping_providers(); + } + + if ( is_null( $this->shipping_providers ) ) { + return array(); + } + + return $this->shipping_providers; + } + + /** + * @param $name + * + * @return false|Simple|Auto|ShippingProvider + */ + public function get_shipping_provider( $name ) { + $providers = $this->get_shipping_providers(); + + return ( array_key_exists( $name, $providers ) ? $providers[ $name ] : false ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ShippingProvider/Method.php b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Method.php new file mode 100644 index 000000000..4155abc8b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Method.php @@ -0,0 +1,413 @@ +method = $method; + $this->init(); + } else { + $this->is_placeholder = true; + $this->init_placeholder( $method ); + } + } + + protected function init_placeholder( $id ) { + if ( is_a( $id, 'WC_Shipping_Rate' ) ) { + $instance_id = $id->get_instance_id(); + $id = $id->get_id(); + + if ( strpos( $id, ':' ) === false ) { + $id = $id . ':' . $instance_id; + } + } elseif ( is_a( $id, 'WC_Shipping_Method' ) ) { + $instance_id = $id->get_instance_id(); + $id = $id->id; + + if ( strpos( $id, ':' ) === false ) { + $id = $id . ':' . $instance_id; + } + } + + if ( ! is_numeric( $id ) ) { + $expl = explode( ':', $id ); + $instance_id = ( ( ! empty( $expl ) && count( $expl ) > 1 ) ? $expl[1] : 0 ); + $id = ( ( ! empty( $expl ) && count( $expl ) > 1 ) ? $expl[0] : $id ); + } else { + $instance_id = $id; + } + + $this->placeholder_id = $id; + $this->placeholder_instance_id = $instance_id; + + $this->instance_form_fields = Package::get_method_settings(); + } + + public function get_fallback_setting_value( $setting_key ) { + $setting_key = $this->maybe_prefix_key( $setting_key ); + $setting_value = ''; + + /** + * In case the setting belongs to the current shipping provider + * lets allow overriding the fallback setting with data from the provider. + */ + if ( ( $provider = $this->get_provider_instance() ) && $this->setting_belongs_to_provider( $setting_key ) ) { + $setting_value = $provider->get_setting( $setting_key ); + } + + if ( is_null( $setting_value ) ) { + $setting_value = Package::get_setting( $setting_key, null ); + } + + /** + * Convert booleans to string options + */ + if ( is_bool( $setting_value ) ) { + $setting_value = wc_bool_to_string( $setting_value ); + } + + return apply_filters( "{$this->get_hook_prefix()}setting_fallback_value", $setting_value, $setting_key, $this ); + } + + protected function supports_instance_settings() { + if ( $this->is_placeholder() ) { + return false; + } else { + $supports_settings = ( $this->method->supports( 'instance-settings' ) ) ? true : false; + + return apply_filters( 'woocommerce_gzd_shipping_provider_method_supports_instance_settings', $supports_settings, $this ); + } + } + + public function is_placeholder() { + return true === $this->is_placeholder; + } + + /** + * Get all available shipping method settings. This method (re-) loads all + * the settings available across every registered shipping provider. + * Call the cached version instead for performance improvements. + * + * @see Package::get_method_settings() + * + * @return mixed|void + */ + public static function get_admin_settings() { + /** + * Filter to adjust admin settings added to the shipment method instance specifically for shipping providers. + * + * @param array $settings Admin setting fields. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + $settings = apply_filters( + 'woocommerce_gzd_shipping_provider_method_admin_settings', + array( + 'shipping_provider_title' => array( + 'title' => _x( 'Shipping Provider Settings', 'shipments', 'woocommerce-germanized' ), + 'type' => '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' ), + ), + ) + ); + + foreach ( wc_gzd_get_shipping_providers() as $provider ) { + if ( ! $provider->is_activated() ) { + continue; + } + + $additional_settings = $provider->get_shipping_method_settings(); + $settings = array_merge( $settings, $additional_settings ); + } + + /** + * Append a stop title to make sure the table is closed within settings. + */ + $settings = array_merge( + $settings, + array( + 'shipping_provider_stop_title' => array( + 'title' => '', + 'type' => 'title', + 'default' => '', + ), + ) + ); + + return apply_filters( 'woocommerce_gzd_shipping_provider_method_admin_settings_wrapped', $settings ); + } + + protected function init() { + $this->instance_form_fields = Package::get_method_settings(); + + if ( ! array_key_exists( 'shipping_provider', $this->get_method()->instance_form_fields ) ) { + $this->get_method()->instance_form_fields = array_merge( $this->get_method()->instance_form_fields, $this->instance_form_fields ); + } + + // Refresh instance settings in case they were already loaded + if ( ! empty( $this->get_method()->instance_settings ) ) { + $this->get_method()->init_instance_settings(); + } + } + + protected function get_hook_prefix() { + $prefix = 'woocommerce_gzd_shipping_provider_method_'; + + return $prefix; + } + + /** + * Returns the Woo WC_Shipping_Method original object + * + * @return object|WC_Shipping_Method + */ + public function get_method() { + return $this->method; + } + + public function get_id() { + if ( ! $this->is_placeholder() ) { + return $this->method->id; + } else { + return $this->placeholder_id; + } + } + + public function get_instance_id() { + if ( ! $this->is_placeholder() ) { + return $this->method->get_instance_id(); + } else { + return $this->placeholder_instance_id; + } + } + + public function has_option( $key ) { + $fields = $this->instance_form_fields; + $key = $this->maybe_prefix_key( $key ); + $has_option = ( array_key_exists( $key, $fields ) && $this->setting_belongs_to_provider( $key ) ) ? true : false; + + /** + * Filter that allows checking whether a shipping provider method has a specific option or not. + * + * @param boolean $has_option Whether or not the option exists. + * @param string $key The setting key. + * @param Method $method The method instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}setting_prefix", $has_option, $key, $this ); + } + + public function setting_belongs_to_provider( $setting_key, $provider = '' ) { + $prefix = $this->get_custom_setting_prefix_key(); + + if ( ! empty( $provider ) ) { + $prefix = $provider . '_'; + } + + $belongs_to_provider = false; + + if ( ! empty( $prefix ) && substr( $setting_key, 0, strlen( $prefix ) ) === $prefix ) { + $belongs_to_provider = true; + } + + return $belongs_to_provider; + } + + public function is_provider_enabled( $provider ) { + return ( $this->get_provider() === $provider ) ? true : false; + } + + public function set_provider( $provider_name ) { + $this->provider_slug = $provider_name; + } + + public function get_provider() { + $id = sanitize_key( $this->get_id() ); + + if ( is_null( $this->provider_slug ) ) { + $provider_slug = $this->method ? $this->method->get_option( 'shipping_provider' ) : ''; + + if ( ! empty( $provider_slug ) ) { + if ( $provider = wc_gzd_get_shipping_provider( $provider_slug ) ) { + if ( ! $provider->is_activated() ) { + $provider_slug = ''; + } + } + } + + if ( empty( $provider_slug ) ) { + $provider_slug = wc_gzd_get_default_shipping_provider(); + } + + /** + * Filter that allows adjusting the shipping provider chosen for a specific shipping method. + * + * @param string $provider_slug The shipping provider. + * @param string $method_id The shipping method id. + * @param Method $method The method instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + $this->provider_slug = apply_filters( 'woocommerce_gzd_shipping_provider_method_provider', $provider_slug, $this->get_id(), $this ); + } + + /** + * Filter that allows choosing a shipping provider for a specific shipping method. + * + * The dynamic portion of this hook, `$id` refers to the shipping method id. + * + * Example hook name: `woocommerce_gzd_shipping_provider_method_flat_rate_provider` + * + * @param string $provider_slug The shipping provider name to be used. + * @param Method $method The method instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}{$id}_provider", $this->provider_slug, $this ); + } + + public function get_provider_instance() { + $provider_slug = $this->get_provider(); + + if ( ! empty( $provider_slug ) ) { + return wc_gzd_get_shipping_provider( $provider_slug ); + } + + return false; + } + + protected function get_custom_setting_prefix_key() { + $prefix = ''; + + if ( $provider = $this->get_provider_instance() ) { + $prefix = $provider->get_name() . '_'; + } + + return apply_filters( "{$this->get_hook_prefix()}custom_setting_prefix", $prefix, $this ); + } + + protected function maybe_prefix_key( $key ) { + $fields = $this->instance_form_fields; + $prefix = $this->get_custom_setting_prefix_key(); + $new_key = $key; + + // Do only prefix if the prefix does not yet exist. + if ( ! array_key_exists( $new_key, $fields ) ) { + if ( substr( $key, 0, strlen( $prefix ) ) !== $prefix ) { + $new_key = $prefix . $key; + } + } + + /** + * Filter that allows prefixing the setting key used for a shipping provider method. + * + * @param string $new_key The prefixed setting key. + * @param string $key The original setting key. + * @param Method $method The method instance. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}setting_key_prefixed", $new_key, $key, $this ); + } + + public function get_option( $key ) { + $key = $this->maybe_prefix_key( $key ); + $option_value = $this->get_fallback_setting_value( $key ); + + if ( ! $this->is_placeholder() ) { + if ( $this->has_option( $key ) && $this->supports_instance_settings() ) { + $option_type = isset( $this->instance_form_fields[ $key ]['type'] ) ? $this->instance_form_fields[ $key ]['type'] : 'text'; + + // Do only use method settings if the method is not a placeholder and method supports settings + $option_value = $this->method->get_option( $key, $option_value ); + + if ( in_array( $option_type, array( 'checkbox', 'radio' ), true ) ) { + $option_value = wc_string_to_bool( $option_value ); + + if ( $option_value ) { + $option_value = 'yes'; + } else { + $option_value = 'no'; + } + } + } + } + + return apply_filters( "{$this->get_hook_prefix()}setting_value", $option_value, $key, $this ); + } + + /** + * 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->method, $method ) ) { + return call_user_func_array( array( $this->method, $method ), $args ); + } + + return false; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/ShippingProvider/MethodPlaceholder.php b/packages/woocommerce-germanized-shipments/src/ShippingProvider/MethodPlaceholder.php new file mode 100644 index 000000000..bd4d02029 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/MethodPlaceholder.php @@ -0,0 +1,24 @@ + true, + 'title' => '', + 'name' => '', + 'description' => '', + 'order' => 0, + 'supports_customer_returns' => false, + 'supports_guest_returns' => false, + 'return_manual_confirmation' => true, + 'return_instructions' => '', + 'tracking_url_placeholder' => '', + 'tracking_desc_placeholder' => '', + ); + + protected $address_data = array( + 'shipper' => null, + 'return' => null, + ); + + /** + * Get the provider if ID is passed. In case it is an integration, data will be provided through the impl. + * This class should NOT be instantiated, but the `wc_gzd_get_shipping_provider` function should be used. + * + * @param int|object|ShippingProvider $provider Provider to read. + */ + public function __construct( $data = 0 ) { + parent::__construct( $data ); + + if ( $data instanceof ShippingProvider ) { + $this->set_id( absint( $data->get_id() ) ); + } elseif ( is_numeric( $data ) ) { + $this->set_id( $data ); + } elseif ( is_object( $data ) && isset( $data->shipping_provider_id ) ) { + $this->set_id( $data->shipping_provider_id ); + } + + $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_help_link() { + return ''; + } + + public function get_signup_link() { + return ''; + } + + public function is_pro() { + return false; + } + + /** + * Whether or not this instance is a manual integration. + * Manual integrations are constructed dynamically from DB and do not support + * automatic shipment handling, e.g. label creation. + * + * @return bool + */ + public function is_manual_integration() { + return true; + } + + /** + * Whether or not this instance supports a certain label type. + * + * @param string $label_type The label type e.g. simple or return. + * @param false|Shipment Shipment instance + * + * @return bool + */ + public function supports_labels( $label_type, $shipment = false ) { + return false; + } + + public function supports_customer_return_requests() { + if ( $this->is_manual_integration() ) { + return true; + } + + return false; + } + + /** + * Some providers (e.g. DHL) create return labels automatically and the return + * address is chosen dynamically depending on the country. For that reason the return address + * might not show up within emails or in customer panel. + * + * @return bool + */ + public function hide_return_address() { + return false; + } + + public function get_edit_link( $section = '' ) { + $url = admin_url( 'admin.php?page=wc-settings&tab=germanized-shipping_provider&provider=' . esc_attr( $this->get_name() ) ); + $url = add_query_arg( array( 'section' => $section ), $url ); + + return esc_url_raw( $url ); + } + + /** + * Returns whether the shipping provider is active for usage or not. + * + * @return bool + */ + public function is_activated() { + return $this->get_activated() === true; + } + + public function needs_manual_confirmation_for_returns() { + return $this->get_return_manual_confirmation() === true; + } + + /** + * @param false|\WC_Order $order + * + * @return bool + */ + public function supports_customer_returns( $order = false ) { + return $this->get_supports_customer_returns() === true; + } + + public function supports_guest_returns() { + return $this->get_supports_customer_returns() === true && $this->get_supports_guest_returns() === true; + } + + /** + * Returns a title for the shipping provider. + * + * @param string $context + * + * @return string + */ + public function get_title( $context = 'view' ) { + return $this->get_prop( 'title', $context ); + } + + /** + * Returns the provider order. + * + * @param string $context + * + * @return int + */ + public function get_order( $context = 'view' ) { + return $this->get_prop( 'order', $context ); + } + + /** + * Returns a unique slug/name for the shipping provider. + * + * @param string $context + * + * @return string + */ + public function get_name( $context = 'view' ) { + return $this->get_prop( 'name', $context ); + } + + /** + * Returns a description for the provider. + * + * @param string $context + * + * @return string + */ + public function get_description( $context = 'view' ) { + $desc = $this->get_prop( 'description', $context ); + + if ( 'view' === $context && empty( $desc ) ) { + return '-'; + } + + return $desc; + } + + /** + * Returns whether the shipping provider is activated or not. + * + * @param string $context + * + * @return string + */ + public function get_activated( $context = 'view' ) { + return $this->get_prop( 'activated', $context ); + } + + /** + * Returns whether the shipping provider needs manual confirmation for a return. + * + * @param string $context + * + * @return string + */ + public function get_return_manual_confirmation( $context = 'view' ) { + return $this->get_prop( 'return_manual_confirmation', $context ); + } + + /** + * Returns whether the shipping provider supports returns added by customers or not. + * + * @param string $context + * + * @return string + */ + public function get_supports_customer_returns( $context = 'view' ) { + return $this->get_prop( 'supports_customer_returns', $context ); + } + + /** + * Returns whether the shipping provider supports returns added by guests or not. + * + * @param string $context + * + * @return string + */ + public function get_supports_guest_returns( $context = 'view' ) { + return $this->get_prop( 'supports_guest_returns', $context ); + } + + /** + * Returns the tracking url placeholder which is being used to + * construct a tracking url. + * + * @param string $context + * + * @return mixed + */ + public function get_tracking_url_placeholder( $context = 'view' ) { + $data = $this->get_prop( 'tracking_url_placeholder', $context ); + + // In case the option value is not stored in DB yet + if ( 'view' === $context && empty( $data ) ) { + $data = $this->get_default_tracking_url_placeholder(); + } + + return $data; + } + + public function get_default_tracking_url_placeholder() { + return ''; + } + + /** + * Returns the tracking description placeholder which is being used to + * construct a tracking description. + * + * @param string $context + * + * @return mixed + */ + public function get_tracking_desc_placeholder( $context = 'view' ) { + $data = $this->get_prop( 'tracking_desc_placeholder', $context ); + + // In case the option value is not stored in DB yet + if ( 'view' === $context && empty( $data ) ) { + $data = $this->get_default_tracking_desc_placeholder(); + } + + return $data; + } + + public function get_default_tracking_desc_placeholder() { + return _x( 'Your shipment is being processed by {shipping_provider}. If you want to track the shipment, please use the following tracking number: {tracking_id}. Depending on the chosen shipping method it is possible that the tracking data does not reflect the current status when receiving this email.', 'shipments', 'woocommerce-germanized' ); + } + + /** + * Returns the return instructions. + * + * @param string $context + * + * @return mixed + */ + public function get_return_instructions( $context = 'view' ) { + return $this->get_prop( 'return_instructions', $context ); + } + + public function has_return_instructions() { + $instructions = $this->get_return_instructions(); + + return empty( $instructions ) ? false : true; + } + + protected function get_address_props( $address_type = 'shipper' ) { + if ( is_null( $this->address_data[ $address_type ] ) ) { + $this->address_data[ $address_type ] = wc_gzd_get_shipment_setting_address_fields( $address_type ); + } + + return $this->address_data[ $address_type ]; + } + + public function get_shipper_address_data() { + return $this->get_address_props( 'shipper' ); + } + + public function get_address_prop( $prop, $type = 'shipper' ) { + $address_fields = $this->get_address_props( $type ); + + return array_key_exists( $prop, $address_fields ) ? $address_fields[ $prop ] : ''; + } + + public function get_shipper_email() { + return $this->get_address_prop( 'email' ); + } + + public function get_shipper_phone() { + return $this->get_address_prop( 'phone' ); + } + + public function get_contact_phone() { + return get_option( 'woocommerce_gzd_shipments_contact_phone' ); + } + + public function get_shipper_first_name() { + return $this->get_address_prop( 'first_name' ); + } + + public function get_shipper_last_name() { + return $this->get_address_prop( 'last_name' ); + } + + public function get_shipper_name() { + return $this->get_shipper_formatted_full_name(); + } + + public function get_shipper_formatted_full_name() { + return $this->get_address_prop( 'full_name' ); + } + + public function get_shipper_company() { + return $this->get_address_prop( 'company' ); + } + + public function get_shipper_address() { + return $this->get_address_prop( 'address_1' ); + } + + public function get_shipper_address_1() { + return $this->get_shipper_address(); + } + + public function get_shipper_address_2() { + return $this->get_address_prop( 'address_2' ); + } + + public function get_shipper_street() { + return $this->get_address_prop( 'street' ); + } + + public function get_shipper_street_number() { + return $this->get_address_prop( 'street_number' ); + } + + public function get_shipper_postcode() { + return $this->get_address_prop( 'postcode' ); + } + + public function get_shipper_city() { + return $this->get_address_prop( 'city' ); + } + + public function get_shipper_customs_reference_number() { + return $this->get_address_prop( 'customs_reference_number' ); + } + + public function get_shipper_customs_uk_vat_id() { + return $this->get_address_prop( 'customs_uk_vat_id' ); + } + + public function get_shipper_country() { + $country_data = wc_format_country_state_string( $this->get_address_prop( 'country' ) ); + + return $country_data['country']; + } + + public function get_shipper_state() { + $country_data = wc_format_country_state_string( $this->get_address_prop( 'country' ) ); + + return $country_data['state']; + } + + public function get_return_address_data() { + return $this->get_address_props( 'return' ); + } + + public function get_return_first_name() { + return $this->get_address_prop( 'first_name', 'return' ); + } + + public function get_return_last_name() { + return $this->get_address_prop( 'last_name', 'return' ); + } + + public function get_return_company() { + return $this->get_address_prop( 'company', 'return' ); + } + + public function get_return_name() { + return $this->get_return_formatted_full_name(); + } + + public function get_return_formatted_full_name() { + return $this->get_address_prop( 'full_name', 'return' ); + } + + public function get_return_address() { + return $this->get_address_prop( 'address_1', 'return' ); + } + + public function get_return_address_2() { + return $this->get_address_prop( 'address_2', 'return' ); + } + + public function get_return_street() { + return $this->get_address_prop( 'street', 'return' ); + } + + public function get_return_street_number() { + return $this->get_address_prop( 'street_number', 'return' ); + } + + public function get_return_postcode() { + return $this->get_address_prop( 'postcode', 'return' ); + } + + public function get_return_city() { + return $this->get_address_prop( 'city', 'return' ); + } + + public function get_return_country() { + $country_data = wc_format_country_state_string( $this->get_address_prop( 'country', 'return' ) ); + + return $country_data['country']; + } + + public function get_return_state() { + $country_data = wc_format_country_state_string( $this->get_address_prop( 'country', 'return' ) ); + + return $country_data['state']; + } + + public function get_return_email() { + return $this->get_address_prop( 'email', 'return' ); + } + + public function get_return_phone() { + return $this->get_address_prop( 'phone', 'return' ); + } + + /** + * Set the current shipping provider to active or inactive. + * + * @param bool $is_activated + */ + public function set_activated( $is_activated ) { + $this->set_prop( 'activated', wc_string_to_bool( $is_activated ) ); + } + + /** + * Mark the current shipping provider as manual needed confirmation for returns. + * + * @param bool $needs_confirmation + */ + public function set_return_manual_confirmation( $needs_confirmation ) { + $this->set_prop( 'return_manual_confirmation', wc_string_to_bool( $needs_confirmation ) ); + } + + /** + * Set whether or not the current shipping provider supports customer returns + * + * @param bool $supports + */ + public function set_supports_customer_returns( $supports ) { + $this->set_prop( 'supports_customer_returns', wc_string_to_bool( $supports ) ); + } + + /** + * Set whether or not the current shipping provider supports guest returns + * + * @param bool $supports + */ + public function set_supports_guest_returns( $supports ) { + $this->set_prop( 'supports_guest_returns', wc_string_to_bool( $supports ) ); + } + + public function update_settings_with_defaults() { + foreach ( $this->get_all_settings() as $section => $settings ) { + foreach ( $settings as $setting ) { + $type = isset( $setting['type'] ) ? $setting['type'] : 'title'; + $default = isset( $setting['default'] ) ? $setting['default'] : null; + + if ( in_array( $type, array( 'title', 'sectionend', 'html' ), true ) || ! isset( $setting['id'] ) || empty( $setting['id'] ) ) { + continue; + } + + $current_value = $this->get_setting( $setting['id'], null, 'edit' ); + + /** + * Update meta data with default value in case it does not yet exist. + */ + if ( is_null( $current_value ) && ! is_null( $default ) ) { + $this->update_setting( $setting['id'], $default ); + } + } + } + } + + /** + * Activate current ShippingProvider instance. + */ + public function activate() { + $this->set_activated( true ); + $this->update_settings_with_defaults(); + $this->save(); + + /** + * This action fires as soon as a certain shipping provider gets activated. + * + * @param ShippingProvider $shipping_provider The shipping provider instance. + */ + do_action( 'woocommerce_gzd_shipping_provider_activated', $this ); + } + + /** + * Deactivate current ShippingProvider instance. + */ + public function deactivate() { + $this->set_activated( false ); + $this->save(); + + /** + * This action fires as soon as a certain shipping provider gets deactivated. + * + * @param ShippingProvider $shipping_provider The shipping provider instance. + */ + do_action( 'woocommerce_gzd_shipping_provider_deactivated', $this ); + } + + /** + * Set the name of the current shipping provider. + * + * @param string $name + */ + public function set_name( $name ) { + $this->set_prop( 'name', $name ); + } + + /** + * Set the title of the current shipping provider. + * + * @param string $title + */ + public function set_title( $title ) { + $this->set_prop( 'title', $title ); + } + + /** + * Set the order of the current shipping provider. + * + * @param int $order + */ + public function set_order( $order ) { + $this->set_prop( 'order', absint( $order ) ); + } + + /** + * Set the description of the current shipping provider. + * + * @param string $description + */ + public function set_description( $description ) { + $this->set_prop( 'description', $description ); + } + + /** + * Set the return instructions of the current shipping provider. + * + * @param string $instructions + */ + public function set_return_instructions( $instructions ) { + $this->set_prop( 'return_instructions', $instructions ); + } + + /** + * Set the tracking url placeholder of the current shipping provider. + * + * @param string $placeholder + */ + public function set_tracking_url_placeholder( $placeholder ) { + $this->set_prop( 'tracking_url_placeholder', $placeholder ); + } + + /** + * Set the tracking description placeholder of the current shipping provider. + * + * @param string $placeholder + */ + public function set_tracking_desc_placeholder( $placeholder ) { + $this->set_prop( 'tracking_desc_placeholder', $placeholder ); + } + + /** + * Returns the tracking url for a specific shipment. + * + * @param Shipment $shipment + * + * @return string + */ + public function get_tracking_url( $shipment ) { + + $tracking_url = ''; + $tracking_id = $shipment->get_tracking_id(); + + if ( '' !== $this->get_tracking_url_placeholder() && ! empty( $tracking_id ) ) { + $placeholders = $this->get_tracking_placeholders( $shipment ); + $tracking_url = str_replace( array_keys( $placeholders ), array_values( $placeholders ), $this->get_tracking_url_placeholder() ); + } + + /** + * This filter returns the tracking url provided by the shipping provider for a certain shipment. + * + * The dynamic portion of the hook `$this->get_hook_prefix()` refers to the + * current provider name. + * + * Example hook name: woocommerce_gzd_shipping_provider_dhl_get_tracking_url + * + * @param string $tracking_url The tracking url. + * @param Shipment $shipment The shipment used to build the url. + * @param ShippingProvider $provider The shipping provider. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( $this->get_hook_prefix() . 'tracking_url', $tracking_url, $shipment, $this ); + } + + /** + * Returns the tracking description for a certain shipment. + * + * @param Shipment $shipment + * + * @return string + */ + public function get_tracking_desc( $shipment, $plain = false ) { + $tracking_desc = ''; + $tracking_id = $shipment->get_tracking_id(); + + if ( '' !== $this->get_tracking_desc_placeholder() && ! empty( $tracking_id ) ) { + $placeholders = $this->get_tracking_placeholders( $shipment ); + + if ( ! $plain && apply_filters( "{$this->get_general_hook_prefix()}tracking_id_with_link", true, $shipment ) && $shipment->has_tracking() ) { + $placeholders['{tracking_id}'] = '' . $shipment->get_tracking_id() . ''; + } + + $tracking_desc = str_replace( array_keys( $placeholders ), array_values( $placeholders ), $this->get_tracking_desc_placeholder() ); + } + + /** + * This filter returns the tracking description provided by the shipping provider for a certain shipment. + * + * The dynamic portion of the hook `$this->get_hook_prefix()` refers to the + * current provider name. + * + * Example hook name: woocommerce_gzd_shipping_provider_dhl_get_tracking_description + * + * @param string $tracking_url The tracking description. + * @param Shipment $shipment The shipment used to build the url. + * @param ShippingProvider $provider The shipping provider. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( $this->get_hook_prefix() . 'tracking_desc', $tracking_desc, $shipment, $this ); + } + + /** + * @param bool|Shipment $shipment + * + * @return array + */ + public function get_tracking_placeholders( $shipment = false ) { + $label = false; + + if ( $shipment ) { + $label = $shipment->get_label(); + } + + /** + * This filter may be used to add or manipulate tracking placeholder data + * for a certain shipping provider. + * + * The dynamic portion of the hook `$this->get_hook_prefix()` refers to the + * current provider name. + * + * Example hook name: woocommerce_gzd_shipping_provider_dhl_get_tracking_placeholders + * + * @param array $placeholders Placeholders in key => value pairs. + * @param ShippingProvider $provider The shipping provider. + * @param Shipment|bool $shipment The shipment instance if available. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( + "{$this->get_hook_prefix()}tracking_placeholders", + array( + '{shipment_number}' => $shipment ? $shipment->get_shipment_number() : '', + '{order_number}' => $shipment ? $shipment->get_order_number() : '', + '{tracking_id}' => $shipment ? $shipment->get_tracking_id() : '', + '{postcode}' => $shipment ? $shipment->get_postcode() : '', + '{date_sent_day}' => $shipment && $shipment->get_date_sent() ? $shipment->get_date_sent()->format( 'd' ) : '', + '{date_sent_month}' => $shipment && $shipment->get_date_sent() ? $shipment->get_date_sent()->format( 'm' ) : '', + '{date_sent_year}' => $shipment && $shipment->get_date_sent() ? $shipment->get_date_sent()->format( 'Y' ) : '', + '{date_day}' => $shipment && $shipment->get_date_created() ? $shipment->get_date_created()->format( 'd' ) : '', + '{date_month}' => $shipment && $shipment->get_date_created() ? $shipment->get_date_created()->format( 'm' ) : '', + '{date_year}' => $shipment && $shipment->get_date_created() ? $shipment->get_date_created()->format( 'Y' ) : '', + '{label_date_day}' => $label ? $label->get_date_created()->format( 'd' ) : '', + '{label_date_month}' => $label ? $label->get_date_created()->format( 'm' ) : '', + '{label_date_year}' => $label ? $label->get_date_created()->format( 'Y' ) : '', + '{shipping_provider}' => $this->get_title(), + ), + $this, + $shipment + ); + } + + /** + * 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_'; + } + + /** + * Prefix for action and filter hooks on data. + * + * @since 3.0.0 + * @return string + */ + protected function get_general_hook_prefix() { + $name = sanitize_key( $this->get_name( 'edit' ) ); + + if ( empty( $name ) ) { + return 'woocommerce_gzd_shipping_provider_'; + } else { + return "woocommerce_gzd_shipping_provider_{$name}_"; + } + } + + protected function get_general_settings( $for_shipping_method = false ) { + $settings = array( + array( + 'title' => '', + 'type' => 'title', + 'id' => 'shipping_provider_options', + ), + ); + + if ( $this->is_manual_integration() ) { + $settings = array_merge( + $settings, + array( + array( + 'title' => _x( 'Title', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => _x( 'Choose a title for the shipping provider.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'shipping_provider_title', + 'value' => $this->get_title( 'edit' ), + 'default' => '', + 'type' => 'text', + ), + + array( + 'title' => _x( 'Description', 'shipments', 'woocommerce-germanized' ), + 'desc_tip' => _x( 'Choose a description for the shipping provider.', 'shipments', 'woocommerce-germanized' ), + 'id' => 'shipping_provider_description', + 'value' => $this->get_description( 'edit' ), + 'default' => '', + 'type' => 'textarea', + 'css' => 'width: 100%;', + ), + ) + ); + } + + $settings = array_merge( + $settings, + array( + array( + 'title' => _x( 'Tracking URL', 'shipments', 'woocommerce-germanized' ), + 'desc' => '
' . sprintf( _x( 'Adjust the placeholder used to construct the tracking URL for this shipping provider. You may use on of the following placeholders to insert the tracking id or other dynamic data: %s', 'shipments', 'woocommerce-germanized' ), '' . implode( ', ', array_keys( $this->get_tracking_placeholders() ) ) . '' ) . '
', + 'id' => 'shipping_provider_tracking_url_placeholder', + 'placeholder' => $this->get_default_tracking_url_placeholder(), + 'value' => $this->get_tracking_url_placeholder( 'edit' ), + 'default' => $this->get_default_tracking_url_placeholder(), + 'type' => 'text', + 'css' => 'width: 100%;', + ), + + array( + 'title' => _x( 'Tracking description', 'shipments', 'woocommerce-germanized' ), + 'desc' => '
' . sprintf( _x( 'Adjust the placeholder used to construct the tracking description for this shipping provider (e.g. used within notification emails). You may use on of the following placeholders to insert the tracking id or other dynamic data: %s', 'shipments', 'woocommerce-germanized' ), '' . implode( ', ', array_keys( $this->get_tracking_placeholders() ) ) . '' ) . '
', + 'id' => 'shipping_provider_tracking_desc_placeholder', + 'placeholder' => $this->get_default_tracking_desc_placeholder(), + 'value' => $this->get_tracking_desc_placeholder( 'edit' ), + 'default' => $this->get_default_tracking_desc_placeholder(), + 'type' => 'textarea', + 'css' => 'width: 100%; min-height: 60px; margin-top: 1em;', + ), + ) + ); + + $settings = array_merge( + $settings, + array( + array( + 'type' => 'sectionend', + 'id' => 'shipping_provider_options', + ), + ) + ); + + return $settings; + } + + /** + * @param Shipment $shipment + * @param $key + */ + public function get_shipment_setting( $shipment, $key, $default = null ) { + $value = $this->get_setting( $key, $default ); + + if ( $method = $shipment->get_shipping_method_instance() ) { + $prefixed_key = $this->get_name() . '_' . $key; + + /** + * Do only allow overriding settings in case the shipping provider + * selected for the shipping method matches the current shipping provider. + */ + if ( $method->get_provider() === $this->get_name() && $method->has_option( $prefixed_key ) ) { + $method_value = $method->get_option( $prefixed_key ); + + if ( ! is_null( $method_value ) && $value !== $method_value ) { + $value = $method_value; + } + } + } + + return $value; + } + + public function get_setting( $key, $default = null, $context = 'view' ) { + $clean_key = $this->unprefix_setting_key( $key ); + $getter = "get_{$clean_key}"; + $value = $default; + + if ( is_callable( array( $this, $getter ) ) ) { + $value = $this->$getter( $context ); + } elseif ( $this->meta_exists( $clean_key ) ) { + $value = $this->get_meta( $clean_key, true, $context ); + } + + if ( strstr( $key, 'password' ) && ! is_null( $value ) ) { + if ( class_exists( 'WC_GZD_Secret_Box_Helper' ) ) { + $result = \WC_GZD_Secret_Box_Helper::decrypt( $value ); + + if ( ! is_wp_error( $result ) ) { + $value = $result; + } + } + + $value = $this->retrieve_password( $value ); + } + + return $value; + } + + protected function retrieve_password( $value ) { + return is_null( $value ) ? $value : stripslashes( $value ); + } + + protected function unprefix_setting_key( $key ) { + $prefixes = array( + 'shipping_provider_', + $this->get_name() . '_', + ); + + foreach ( $prefixes as $prefix ) { + if ( substr( $key, 0, strlen( $prefix ) ) === $prefix ) { + $key = substr( $key, strlen( $prefix ) ); + } + } + + return $key; + } + + public function update_settings( $section = '', $data = null, $save = true ) { + $settings_to_save = Settings::get_sanitized_settings( $this->get_settings( $section ), $data ); + + foreach ( $settings_to_save as $option_name => $value ) { + $this->update_setting( $option_name, $value ); + } + + if ( $save ) { + $this->save(); + } + } + + public function update_setting( $setting, $value ) { + $setting_name_clean = $this->unprefix_setting_key( $setting ); + $setter = 'set_' . $setting_name_clean; + + try { + if ( is_callable( array( $this, $setter ) ) ) { + $this->{$setter}( $value ); + } else { + $this->update_meta_data( $setting_name_clean, $value ); + } + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } + } + + public function get_settings( $section = '', $for_shipping_method = false ) { + $settings = array(); + + if ( '' === $section || 'general' === $section ) { + $settings = $this->get_general_settings( $for_shipping_method ); + } elseif ( 'returns' === $section ) { + $settings = $this->get_return_settings( $for_shipping_method ); + } elseif ( is_callable( array( $this, "get_{$section}_settings" ) ) ) { + $settings = $this->{"get_{$section}_settings"}( $for_shipping_method ); + } + + /** + * This filter returns the admin settings available for a certain shipping provider. + * + * The dynamic portion of the hook `$this->get_hook_prefix()` refers to the + * current provider name. + * + * Example hook name: woocommerce_gzd_shipping_provider_dhl_get_settings + * + * @param array $settings Available settings. + * @param ShippingProvider $provider The shipping provider. + * + * @since 3.0.6 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( $this->get_hook_prefix() . 'settings', $settings, $section, $this, $for_shipping_method ); + } + + protected function get_return_settings( $for_shipping_method = false ) { + $settings = array( + array( + 'title' => '', + 'type' => 'title', + 'id' => 'shipping_provider_return_options', + ), + ); + + $settings = array_merge( + $settings, + array( + array( + 'title' => _x( 'Customer returns', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Allow customers to submit return requests to shipments.', 'shipments', 'woocommerce-germanized' ) . '
' . sprintf( _x( 'This option will allow your customers to submit return requests to orders. Return requests will be visible within your %1$s. To learn more about return requests by customers and/or guests, please check the %2$s.', 'shipments', 'woocommerce-germanized' ), '' . _x( 'Return Dashboard', 'shipments', 'woocommerce-germanized' ) . '', '' . _x( 'docs', 'shipments', 'woocommerce-germanized' ) . '' ) . '
', + 'id' => 'supports_customer_returns', + 'placeholder' => '', + 'value' => wc_bool_to_string( $this->get_supports_customer_returns( 'edit' ) ), + 'default' => 'no', + 'type' => 'gzd_toggle', + ), + + array( + 'title' => _x( 'Guest returns', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Allow guests to submit return requests to shipments.', 'shipments', 'woocommerce-germanized' ) . '
' . sprintf( _x( 'Guests will need to provide their email address and the order id to receive a one-time link to submit a return request. The placeholder %s might be used to place the request form on your site.', 'shipments', 'woocommerce-germanized' ), '[gzd_return_request_form]' ) . '
', + 'id' => 'supports_guest_returns', + 'default' => 'no', + 'value' => wc_bool_to_string( $this->get_supports_guest_returns( 'edit' ) ), + 'type' => 'gzd_toggle', + 'custom_attributes' => array( + 'data-show_if_shipping_provider_supports_customer_returns' => '', + ), + ), + + array( + 'title' => _x( 'Manual confirmation', 'shipments', 'woocommerce-germanized' ), + 'desc' => _x( 'Return requests need manual confirmation.', 'shipments', 'woocommerce-germanized' ) . '
' . _x( 'By default return request need manual confirmation e.g. a shop manager needs to review return requests which by default are added with the status "requested" after a customer submitted a return request. If you choose to disable this option, customer return requests will be added as "processing" and an email confirmation including instructions will be sent immediately to the customer.', 'shipments', 'woocommerce-germanized' ) . '
', + 'id' => 'return_manual_confirmation', + 'placeholder' => '', + 'value' => wc_bool_to_string( $this->get_return_manual_confirmation( 'edit' ) ), + 'default' => 'yes', + 'type' => 'gzd_toggle', + 'custom_attributes' => array( + 'data-show_if_shipping_provider_supports_customer_returns' => '', + ), + ), + + array( + 'title' => _x( 'Return instructions', 'shipments', 'woocommerce-germanized' ), + 'desc' => '
' . _x( 'Provide your customer with instructions on how to return the shipment after a return request has been confirmed e.g. explain how to prepare the return for shipment. In case a label cannot be generated automatically, make sure to provide your customer with information on how to obain a return label.', 'shipments', 'woocommerce-germanized' ) . '
', + 'id' => 'return_instructions', + 'placeholder' => '', + 'value' => $this->get_return_instructions( 'edit' ), + 'default' => '', + 'type' => 'textarea', + 'css' => 'width: 100%; min-height: 60px; margin-top: 1em;', + 'custom_attributes' => array( + 'data-show_if_shipping_provider_supports_customer_returns' => '', + ), + ), + ) + ); + + $settings = array_merge( + $settings, + array( + array( + 'type' => 'sectionend', + 'id' => 'shipping_provider_return_options', + ), + ) + ); + + return $settings; + } + + protected function get_all_settings( $for_shipping_method = false ) { + $settings = array(); + $sections = array_keys( $this->get_setting_sections() ); + + foreach ( $sections as $section ) { + $settings[ $section ] = $this->get_settings( $section, $for_shipping_method ); + } + + return $settings; + } + + public function get_shipping_method_settings() { + $settings = $this->get_all_settings( true ); + $sections = $this->get_setting_sections(); + + $method_settings = array(); + $include_current_section = false; + + foreach ( $settings as $section => $section_settings ) { + $global_settings_url = $this->get_edit_link( $section ); + $default_title = $sections[ $section ]; + + foreach ( $section_settings as $setting ) { + $include = false; + $setting = wp_parse_args( + $setting, + array( + 'allow_override' => ( $include_current_section && ! in_array( $setting['type'], array( 'title', 'sectionend' ), true ) ) ? true : false, + 'type' => '', + 'id' => '', + 'value' => '', + 'title_method' => '', + 'title' => '', + 'custom_attributes' => array(), + ) + ); + + if ( true === $setting['allow_override'] ) { + $include = true; + + if ( 'title' === $setting['type'] ) { + $include_current_section = true; + } + } elseif ( $include_current_section && ! in_array( $setting['type'], array( 'title', 'sectionend' ), true ) && false !== $setting['allow_override'] ) { + $include = true; + } elseif ( in_array( $setting['type'], array( 'title', 'sectionend' ), true ) ) { + $include_current_section = false; + } + + if ( $include ) { + $new_setting = array(); + $new_setting['id'] = $this->get_name() . '_' . $setting['id']; + $new_setting['type'] = str_replace( 'gzd_toggle', 'checkbox', $setting['type'] ); + $new_setting['default'] = $setting['value']; + $new_setting['custom_attributes'] = array(); + + if ( ! empty( $setting['custom_attributes'] ) ) { + foreach ( $setting['custom_attributes'] as $attr => $val ) { + $new_attr = $attr; + + if ( 'data-show_if_' === substr( $attr, 0, 13 ) ) { + $new_attr = 'data-show_if_' . $this->get_name() . '_' . substr( $attr, 13, strlen( $attr ) ); + } + + $new_setting['custom_attributes'][ $new_attr ] = $val; + } + } + + if ( 'checkbox' === $new_setting['type'] ) { + $new_setting['label'] = $setting['desc']; + } elseif ( isset( $setting['desc'] ) ) { + $new_setting['description'] = $setting['desc']; + } + + $copy = array( 'options', 'title', 'desc_tip' ); + + foreach ( $copy as $cp ) { + if ( isset( $setting[ $cp ] ) ) { + $new_setting[ $cp ] = $setting[ $cp ]; + } + } + + if ( 'title' === $new_setting['type'] ) { + $new_setting['description'] = sprintf( _x( 'These settings override your global %2$s options. Do only adjust these settings in case you would like to specifically adjust them for this specific shipping method.', 'shipments', 'woocommerce-germanized' ), esc_url( $global_settings_url ), $this->get_title() ); + + if ( empty( $setting['title'] ) ) { + $new_setting['title'] = $default_title; + } + + if ( ! empty( $setting['title_method'] ) ) { + $new_setting['title'] = $setting['title_method']; + } + } + + $method_settings[ $new_setting['id'] ] = $new_setting; + } + } + } + + return $method_settings; + } + + public function get_setting_sections() { + $sections = array( + '' => _x( 'General', 'shipments', 'woocommerce-germanized' ), + ); + + if ( $this->supports_customer_return_requests() ) { + $sections['returns'] = _x( 'Return Requests', 'shipments', 'woocommerce-germanized' ); + } + + return $sections; + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + * + * @return ShipmentLabel|false + */ + public function get_label( $shipment ) { + return apply_filters( "{$this->get_hook_prefix()}label", false, $shipment, $this ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + */ + public function get_label_fields_html( $shipment ) { + return apply_filters( "{$this->get_hook_prefix()}label_fields_html", '', $shipment, $this ); + } + + /** + * @param \Vendidero\Germanized\Shipments\Shipment $shipment + * @param mixed $props + */ + public function create_label( $shipment, $props = false ) { + $result = new ShipmentError( 'shipping-provider', _x( 'This shipping provider does not support creating labels.', 'shipments', 'woocommerce-germanized' ) ); + + return $result; + } +} diff --git a/packages/woocommerce-germanized-shipments/src/SimpleShipment.php b/packages/woocommerce-germanized-shipments/src/SimpleShipment.php new file mode 100644 index 000000000..3f1a3a983 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/SimpleShipment.php @@ -0,0 +1,379 @@ + 0, + ); + + /** + * Returns the shipment type. + * + * @return string + */ + public function get_type() { + return 'simple'; + } + + /** + * Returns the order id belonging to the shipment. + * + * @param string $context What the value is for. Valid values are 'view' and 'edit'. + * @return integer + */ + public function get_order_id( $context = 'view' ) { + return $this->get_prop( 'order_id', $context ); + } + + /** + * Set shipment order id. + * + * @param string $order_id The order id. + */ + public function set_order_id( $order_id ) { + // Reset order object + $this->order = null; + + $this->set_prop( 'order_id', absint( $order_id ) ); + } + + /** + * Set shipment order. + * + * @param Order $order_shipment The order shipment. + */ + public function set_order_shipment( &$order_shipment ) { + $this->order_shipment = $order_shipment; + } + + /** + * Tries to fetch the order for the current shipment. + * + * @return bool|WC_Order|null + */ + public function get_order() { + if ( is_null( $this->order ) ) { + $this->order = ( $this->get_order_id() > 0 ? wc_get_order( $this->get_order_id() ) : false ); + } + + return $this->order; + } + + /** + * Returns the order shipment instance. Loads from DB if not yet exists. + * + * @return bool|Order + */ + public function get_order_shipment() { + if ( is_null( $this->order_shipment ) ) { + $order = $this->get_order(); + $this->order_shipment = ( $order ? wc_gzd_get_shipment_order( $order ) : false ); + } + + return $this->order_shipment; + } + + /** + * Sync the shipment with it's corresponding order. + * + * @param array $args + * + * @return bool + */ + public function sync( $args = array() ) { + try { + if ( ! $order_shipment = $this->get_order_shipment() ) { + throw new Exception( _x( 'Invalid shipment order', 'shipments', 'woocommerce-germanized' ) ); + } + + /** + * Hotfix WCML infinite loop + * + */ + if ( function_exists( 'wc_gzd_remove_class_filter' ) ) { + wc_gzd_remove_class_filter( 'woocommerce_order_get_items', 'WCML_Orders', 'woocommerce_order_get_items', 10 ); + } + + $order = $order_shipment->get_order(); + + /** + * Make sure that manually adjusted providers are not overridden by syncing. + */ + $default_provider_instance = wc_gzd_get_order_shipping_provider( $order ); + $default_provider = $default_provider_instance ? $default_provider_instance->get_name() : ''; + $provider = $this->get_shipping_provider( 'edit' ); + $address_data = array_merge( + ( $order->has_shipping_address() ? $order->get_address( 'shipping' ) : $order->get_address( 'billing' ) ), + array( + 'email' => $order->get_billing_email(), + 'phone' => $order->get_billing_phone(), + ) + ); + + // Prefer shipping phone in case exists + if ( is_callable( array( $order, 'get_shipping_phone' ) ) && $order->get_shipping_phone() ) { + $address_data['phone'] = $order->get_shipping_phone(); + } + + /** + * Fix to make sure that we are not syncing formatted customer titles (e.g. Herr) + * which prevents shipment addresses from being translated. + */ + if ( isset( $address_data['title'] ) && ! empty( $address_data['title'] ) ) { + if ( $title = $order->get_meta( '_shipping_title', true ) ) { + $address_data['title'] = $title; + } + } + + /** + * Force the country to have a max length of 2. + * https://github.com/woocommerce/woocommerce/issues/27521 + */ + $country = substr( strtoupper( ( $order->has_shipping_address() ? $order->get_shipping_country() : $order->get_billing_country() ) ), 0, 2 ); + $packaging_id = $this->get_packaging_id( 'edit' ); + + $dimensions = array( + 'width' => $this->get_width( 'edit' ), + 'length' => $this->get_length( 'edit' ), + 'height' => $this->get_height( 'edit' ), + ); + + $args = wp_parse_args( + $args, + array( + 'order_id' => $order->get_id(), + 'shipping_method' => wc_gzd_get_shipment_order_shipping_method_id( $order ), + 'shipping_provider' => ( ! empty( $provider ) ) ? $provider : $default_provider, + 'packaging_id' => $this->get_packaging_id( 'edit' ), + 'address' => $address_data, + 'country' => $country, + 'weight' => $this->get_weight( 'edit' ), + 'packaging_weight' => $this->get_packaging_weight( 'edit' ), + 'length' => $dimensions['length'], + 'width' => $dimensions['width'], + 'height' => $dimensions['height'], + 'additional_total' => $order_shipment->calculate_shipment_additional_total( $this ), + ) + ); + + /** + * Filter to allow adjusting the shipment props synced from the corresponding order. + * + * @param mixed $args The properties in key => value pairs. + * @param SimpleShipment $shipment The shipment object. + * @param Order $order_shipment The shipment order object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $args = apply_filters( 'woocommerce_gzd_shipment_sync_props', $args, $this, $order_shipment ); + + $this->set_props( $args ); + + /** + * Action that fires after a shipment has been synced. Syncing is used to + * keep the shipment in sync with the corresponding order. + * + * @param SimpleShipment $shipment The shipment object. + * @param Order $order_shipment The shipment order object. + * @param array $args Array containing properties in key => value pairs to be updated. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_synced', $this, $order_shipment, $args ); + } catch ( Exception $e ) { + return false; + } + + return true; + } + + /** + * Sync items with the corresponding order items. + * Limits quantities and removes non-existing items. + * + * @param array $args + * + * @return bool + */ + public function sync_items( $args = array() ) { + try { + if ( ! $order_shipment = $this->get_order_shipment() ) { + throw new Exception( _x( 'Invalid shipment order', 'shipments', 'woocommerce-germanized' ) ); + } + + $order = $order_shipment->get_order(); + + $args = wp_parse_args( + $args, + array( + 'items' => array(), + ) + ); + + $available_items = $order_shipment->get_available_items_for_shipment( + array( + 'shipment_id' => $this->get_id(), + 'exclude_current_shipment' => true, + ) + ); + + foreach ( $available_items as $item_id => $item_data ) { + if ( $order_item = $order->get_item( $item_id ) ) { + $quantity = $item_data['max_quantity']; + + if ( ! empty( $args['items'] ) ) { + if ( isset( $args['items'][ $item_id ] ) ) { + $new_quantity = absint( $args['items'][ $item_id ] ); + + if ( $new_quantity < $quantity ) { + $quantity = $new_quantity; + } + } else { + continue; + } + } + + if ( ! $shipment_item = $this->get_item_by_order_item_id( $item_id ) ) { + $shipment_item = wc_gzd_create_shipment_item( $this, $order_item, array( 'quantity' => $quantity ) ); + + $this->add_item( $shipment_item ); + } else { + $shipment_item->sync( array( 'quantity' => $quantity ) ); + } + } + } + + foreach ( $this->get_items() as $item ) { + // Remove non-existent items + if ( ! $order_item = $order->get_item( $item->get_order_item_id() ) ) { + $this->remove_item( $item->get_id() ); + } + } + + // Sync packaging + $this->sync_packaging(); + + /** + * Action that fires after items of a shipment have been synced. + * + * @param SimpleShipment $shipment The shipment object. + * @param Order $order_shipment The shipment order object. + * @param array $args Array containing additional data e.g. items. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_items_synced', $this, $order_shipment, $args ); + + } catch ( Exception $e ) { + return false; + } + + return true; + } + + /** + * Returns available shipment methods by checking the corresponding order. + * + * @return string[] + */ + public function get_available_shipping_methods() { + $methods = array(); + + if ( $order = $this->get_order() ) { + $items = $order->get_shipping_methods(); + + foreach ( $items as $item ) { + $methods[ $item->get_method_id() . ':' . $item->get_instance_id() ] = $item->get_name(); + } + } + + return $methods; + } + + /** + * Returns the number of items available for shipment. + * + * @return int|mixed|void + */ + public function get_shippable_item_count() { + if ( $order_shipment = $this->get_order_shipment() ) { + return $order_shipment->get_shippable_item_count(); + } + + return 0; + } + + /** + * Returns whether the Shipment needs additional items or not. + * + * @param bool|integer[] $available_items + * + * @return bool + */ + public function needs_items( $available_items = false ) { + + if ( ! $available_items && ( $order = wc_gzd_get_shipment_order( $this->get_order() ) ) ) { + $available_items = array_keys( $order->get_available_items_for_shipment() ); + } + + return ( $this->is_editable() && ! $this->contains_order_item( $available_items ) ); + } + + /** + * Returns the edit shipment URL. + * + * @return mixed|string|void + */ + public function get_edit_shipment_url() { + /** + * Filter to adjust the edit Shipment admin URL. + * + * The dynamic portion of this hook, `$this->get_hook_prefix()` is used to construct a + * unique hook for a shipment type. + * + * Example hook name: woocommerce_gzd_shipment_get_edit_url + * + * @param string $url The URL. + * @param Shipment $this The shipment object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + return apply_filters( "{$this->get_hook_prefix()}edit_url", get_admin_url( null, 'post.php?post=' . $this->get_order_id() . '&action=edit&shipment_id=' . $this->get_id() ), $this ); + } +} diff --git a/packages/woocommerce-germanized-shipments/src/Validation.php b/packages/woocommerce-germanized-shipments/src/Validation.php new file mode 100644 index 000000000..c69a218aa --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/Validation.php @@ -0,0 +1,234 @@ +get_id() ) { + self::new_order( $order ); + } + }, + 300, + 1 + ); + }, + 10, + 1 + ); + + add_action( 'woocommerce_delete_order', array( __CLASS__, 'delete_order' ), 10, 1 ); + + foreach ( array( 'cancelled', 'failed', 'refunded' ) as $cancelled_status ) { + add_action( "woocommerce_order_status_{$cancelled_status}", array( __CLASS__, 'maybe_cancel_shipments' ), 10, 2 ); + } + + add_action( 'before_delete_post', array( __CLASS__, 'before_delete_refund' ), 10, 1 ); + add_action( 'woocommerce_delete_order_refund', array( __CLASS__, 'delete_refund_order' ), 10, 1 ); + add_action( 'woocommerce_order_refund_object_updated_props', array( __CLASS__, 'refresh_refund_order' ), 10, 1 ); + + // Check if order is shipped + add_action( 'woocommerce_gzd_shipment_status_changed', array( __CLASS__, 'maybe_update_order_date_shipped' ), 10, 4 ); + + add_action( 'woocommerce_gzd_shipping_provider_deactivated', array( __CLASS__, 'maybe_disable_default_shipping_provider' ), 10 ); + } + + /** + * In case a certain shipping provider is being deactivated make sure that the default + * shipping provider option is removed in case the option equals the deactivated provider. + * + * @param ShippingProvider $provider + */ + public static function maybe_disable_default_shipping_provider( $provider ) { + $default_provider = wc_gzd_get_default_shipping_provider(); + + if ( $default_provider === $provider->get_name() ) { + update_option( 'woocommerce_gzd_shipments_default_shipping_provider', '' ); + } + } + + /** + * @param $shipment_id + * @param $status_from + * @param $status_to + * @param Shipment $shipment + */ + public static function maybe_update_order_date_shipped( $shipment_id, $status_from, $status_to, $shipment ) { + if ( 'simple' === $shipment->get_type() && ( $order = $shipment->get_order() ) ) { + self::check_order_shipped( $order ); + } + } + + public static function check_order_shipped( $order ) { + if ( $shipment_order = wc_gzd_get_shipment_order( $order ) ) { + if ( $shipment_order->is_shipped() ) { + /** + * Action that fires as soon as an order has been shipped completely. + * That is the case when the order contains all relevant shipments and all the shipments are marked as shipped. + * + * @param string $order_id The order id. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipments_order_shipped', $shipment_order->get_order()->get_id() ); + + $shipment_order->get_order()->update_meta_data( '_date_shipped', time() ); + $shipment_order->get_order()->save(); + } else { + $shipment_order->get_order()->delete_meta_data( '_date_shipped' ); + $shipment_order->get_order()->save(); + } + } + } + + /** + * Delete editable shipments if an order is cancelled. + * + * @param $order_id + * @param WC_Order $order + */ + public static function maybe_cancel_shipments( $order_id, $order ) { + $shipments = wc_gzd_get_shipments_by_order( $order ); + + foreach ( $shipments as $shipment ) { + if ( $shipment->is_editable() ) { + $shipment->delete(); + } + } + } + + public static function before_delete_refund( $refund_id ) { + if ( $refund = wc_get_order( $refund_id ) ) { + + if ( is_a( $refund, 'WC_Order_Refund' ) ) { + self::$current_refund_parent_order = $refund->get_parent_id(); + } + } + } + + public static function delete_refund_order( $refund_id ) { + if ( false !== self::$current_refund_parent_order ) { + + if ( $order_shipment = wc_gzd_get_shipment_order( self::$current_refund_parent_order ) ) { + $order_shipment->validate_shipments(); + } + + self::$current_refund_parent_order = false; + } + } + + public static function refresh_refund_order( $refund ) { + if ( $refund->get_parent_id() <= 0 ) { + return; + } + + if ( $order_shipment = wc_gzd_get_shipment_order( $refund->get_parent_id() ) ) { + $order_shipment->validate_shipments(); + } + } + + public static function delete_order( $order_id ) { + if ( $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) { + + foreach ( $order_shipment->get_shipments() as $shipment ) { + + if ( $shipment->is_editable() ) { + $order_shipment->remove_shipment( $shipment->get_id() ); + } + } + + $order_shipment->save(); + } + } + + public static function new_order( $order ) { + if ( $order_shipment = wc_gzd_get_shipment_order( $order ) ) { + $order_shipment->validate_shipments(); + } + } + + public static function update_order( $order_id ) { + if ( $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) { + $order_shipment->validate_shipments(); + } + } + + public static function delete_order_item( $order_item_id ) { + try { + if ( $order_id = wc_get_order_id_by_order_item_id( $order_item_id ) ) { + + if ( $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) { + foreach ( $order_shipment->get_shipments() as $shipment ) { + + if ( $shipment->is_editable() ) { + if ( $item = $shipment->get_item_by_order_item_id( $order_item_id ) ) { + $shipment->remove_item( $item->get_id() ); + } + } + } + + $order_shipment->save(); + } + } + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } + } + + public static function create_order_item( $order_item_id, $order_item, $order_id ) { + if ( $order_shipment = wc_gzd_get_shipment_order( $order_id ) ) { + $order_shipment->validate_shipments(); + } + } + + protected static function is_admin_save_order_request() { + $is_admin_order_save_request = doing_action( 'save_post' ); + + /** + * Detect admin order adjustments e.g. add item, remove item, save post etc. and + * prevent singular order item hooks from executing to prevent multiple shipment validation requests + * which will execute on order save hook as well. + */ + if ( ! $is_admin_order_save_request && wp_doing_ajax() && isset( $_REQUEST['action'] ) && isset( $_REQUEST['order_id'] ) && strpos( wc_clean( wp_unslash( $_REQUEST['action'] ) ), 'woocommerce_' ) !== false ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $is_admin_order_save_request = true; + } + + return $is_admin_order_save_request; + } + + /** + * @param $order_item_id + * @param WC_Order_Item $order_item + */ + public static function update_order_item( $order_item_id, $order_item ) { + if ( ! self::is_admin_save_order_request() ) { + if ( is_callable( array( $order_item, 'get_order_id' ) ) ) { + + if ( $order_shipment = wc_gzd_get_shipment_order( $order_item->get_order_id() ) ) { + $order_shipment->validate_shipments(); + } + } + } + } +} diff --git a/packages/woocommerce-germanized-shipments/src/WPMLHelper.php b/packages/woocommerce-germanized-shipments/src/WPMLHelper.php new file mode 100644 index 000000000..f19e28653 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/src/WPMLHelper.php @@ -0,0 +1,166 @@ +get_order_id() ) ) { + $language = $order->get_meta( 'wpml_language', true ); + + foreach ( $args['items'] as $key => $item ) { + $id = $item->get_product_id(); + $id = apply_filters( 'wpml_object_id', $id, get_post_type( $id ), true, $language ); + + if ( $product = wc_get_product( $id ) ) { + $args['items'][ $key ]->set_name( $product->get_name() ); + } + } + } + + return $args; + } + + public static function register_provider_filters() { + add_filter( 'woocommerce_gzd_shipping_provider_get_tracking_desc_placeholder', array( __CLASS__, 'filter_shipping_provider_placeholder' ), 10, 2 ); + add_filter( 'woocommerce_gzd_shipping_provider_get_tracking_url_placeholder', array( __CLASS__, 'filter_shipping_provider_url' ), 10, 2 ); + add_filter( 'woocommerce_gzd_shipping_provider_get_return_instructions', array( __CLASS__, 'filter_shipping_provider_return_instructions' ), 10, 2 ); + + foreach ( Helper::instance()->get_shipping_providers() as $provider ) { + add_filter( "woocommerce_gzd_shipping_provider_{$provider->get_name()}_get_tracking_desc_placeholder", array( __CLASS__, 'filter_shipping_provider_placeholder' ), 10, 2 ); + add_filter( "woocommerce_gzd_shipping_provider_{$provider->get_name()}_get_tracking_url_placeholder", array( __CLASS__, 'filter_shipping_provider_url' ), 10, 2 ); + add_filter( "woocommerce_gzd_shipping_provider_{$provider->get_name()}_get_return_instructions", array( __CLASS__, 'filter_shipping_provider_return_instructions' ), 10, 2 ); + } + } + + public static function filter_shipping_provider_return_instructions( $instructions, $provider ) { + $string_name = 'return_instructions'; + $translated_string = apply_filters( 'wpml_translate_string', $instructions, self::get_shipping_provider_string_id( $string_name, $provider ), self::get_shipping_provider_string_package( $string_name, $provider ) ); + + return $translated_string; + } + + public static function filter_shipping_provider_url( $placeholder, $provider ) { + $string_name = 'tracking_url_placeholder'; + $translated_string = apply_filters( 'wpml_translate_string', $placeholder, self::get_shipping_provider_string_id( $string_name, $provider ), self::get_shipping_provider_string_package( $string_name, $provider ) ); + + return $translated_string; + } + + public static function filter_shipping_provider_placeholder( $placeholder, $provider ) { + $string_name = 'tracking_desc_placeholder'; + $translated_string = apply_filters( 'wpml_translate_string', $placeholder, self::get_shipping_provider_string_id( $string_name, $provider ), self::get_shipping_provider_string_package( $string_name, $provider ) ); + + return $translated_string; + } + + /** + * @param integer $provider_id + * @param Simple $provider + */ + public static function register_shipping_provider_strings( $provider_id, $provider ) { + + foreach ( self::get_shipping_provider_strings() as $string_name => $title ) { + $title = sprintf( $title, $provider->get_title() ); + $getter = "get_{$string_name}"; + + if ( is_callable( array( $provider, $getter ) ) ) { + $value = $provider->{$getter}(); + + do_action( 'wpml_register_string', $value, self::get_shipping_provider_string_id( $string_name, $provider ), self::get_shipping_provider_string_package( $string_name, $provider ), $title, 'AREA' ); + } + } + } + + protected static function get_shipping_provider_strings() { + $strings = array( + 'tracking_desc_placeholder' => _x( '%s tracking description', 'shipments', 'woocommerce-germanized' ), + 'tracking_url_placeholder' => _x( '%s tracking URL', 'shipments', 'woocommerce-germanized' ), + 'return_instructions' => _x( '%s return instructions', 'shipments', 'woocommerce-germanized' ), + ); + + return $strings; + } + + /** + * @param $string_name + * @param Simple $provider + */ + protected static function get_shipping_provider_string_id( $string_name, $provider ) { + return "woocommerce_gzd_shipping_provider_{$provider->get_name()}_{$string_name}"; + } + + /** + * @param $string_name + * @param Simple $provider + */ + protected static function get_shipping_provider_string_package( $string_name, $provider ) { + $strings = self::get_shipping_provider_strings(); + $package = array(); + + if ( array_key_exists( $string_name, $strings ) ) { + $title = sprintf( $strings[ $string_name ], $provider->get_title() ); + + $package = array( + 'kind' => 'Shipping Provider', + 'name' => "{$provider->get_name()}_{$string_name}", + 'edit_link' => $provider->get_edit_link(), + 'title' => $title, + ); + } + + return $package; + } + + /** + * @param $emails + */ + public static function register_emails( $emails ) { + $emails['WC_GZD_Email_Customer_Shipment'] = 'customer_shipment'; + $emails['WC_GZD_Email_Customer_Return_Shipment'] = 'customer_return_shipment'; + $emails['WC_GZD_Email_Customer_Return_Shipment_Delivered'] = 'customer_return_shipment_delivered'; + $emails['WC_GZD_Email_Customer_Guest_Return_Shipment_Request'] = 'customer_guest_return_shipment_request'; + + return $emails; + } +} diff --git a/packages/woocommerce-germanized-shipments/templates/emails/admin-new-return-shipment-request.php b/packages/woocommerce-germanized-shipments/templates/emails/admin-new-return-shipment-request.php new file mode 100644 index 000000000..0b3d10afb --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/admin-new-return-shipment-request.php @@ -0,0 +1,60 @@ + + + +

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() ); ?>

+ + +

+ + +
diff --git a/packages/woocommerce-germanized-shipments/templates/emails/email-return-shipment-instructions.php b/packages/woocommerce-germanized-shipments/templates/emails/email-return-shipment-instructions.php new file mode 100644 index 000000000..2789a1d36 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/email-return-shipment-instructions.php @@ -0,0 +1,26 @@ +get_shipping_provider_instance(); +?> + +has_return_instructions() ) : ?> +

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() ); ?> +
+
diff --git a/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-details.php b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-details.php new file mode 100644 index 000000000..a6ed2569d --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-details.php @@ -0,0 +1,87 @@ + + +

+ get_edit_shipment_url() ) . '">'; + $after = ''; + } else { + $before = ''; + $after = ''; + } + /* translators: %s: Order ID. */ + echo wp_kses_post( $before . ( ! $sent_to_admin ? sprintf( _x( 'Details to your %s', 'shipments', 'woocommerce-germanized' ), wc_gzd_get_shipment_label_title( $shipment->get_type() ) ) : sprintf( _x( '[%1$s #%2$s]', 'shipments', 'woocommerce-germanized' ), wc_gzd_get_shipment_label_title( $shipment->get_type() ), $shipment->get_shipment_number() ) ) . $after ); + ?> +

+ +
+ + + + + + + + + $sent_to_admin, + 'show_image' => false, + 'image_size' => array( 32, 32 ), + 'plain_text' => $plain_text, + 'sent_to_admin' => $sent_to_admin, + ) + ); + ?> + +
+
+ + diff --git a/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-items.php b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-items.php new file mode 100644 index 000000000..e17d2f062 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-items.php @@ -0,0 +1,114 @@ + $item ) : + $product = $item->get_product(); + $sku = $item->get_sku(); + $purchase_note = ''; + $image = ''; + + /** + * Filter to decide whether a specific ShipmentItem is visible within email table or not. + * + * @param boolean $is_visible Whether the ShipmentItem is visible or not. + * @param ShipmentItem $item The ShipmentItem object. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + if ( ! apply_filters( 'woocommerce_gzd_shipment_item_visible', true, $item ) ) { + continue; + } + + if ( is_object( $product ) ) { + $image = $product->get_image( $image_size ); + } + + ?> + + + get_name(), $item, false ) ); + + // SKU. + if ( $show_sku && $sku ) { + echo wp_kses_post( ' (#' . $sku . ')' ); + } + + /* + * Action that fires while outputting meta data for a ShipmentItem table display in an Email. + * + * @param integer $item_id The shipment item id. + * @param \Vendidero\Germanized\Shipments\ShipmentItem $item The shipment item instance. + * @param \Vendidero\Germanized\Shipments\Shipment $shipment The shipment instance. + * @param boolean $plain_text Whether this email is in plaintext format or not. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_item_meta', $item_id, $item, $shipment, $plain_text ); + + ?> + + + get_quantity(), $item ) ); + ?> + + + + diff --git a/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-tracking.php b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-tracking.php new file mode 100644 index 000000000..4d5cf3d4a --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-tracking.php @@ -0,0 +1,42 @@ + + + + + + +
+

+ + get_est_delivery_date() ) : ?> +

get_est_delivery_date(), wc_date_format() ) ); ?>

+ + + get_tracking_url() ) : ?> +

+ + + has_tracking_instruction() ) : ?> +

get_tracking_instruction() ); ?>

+ +
diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/admin-new-return-shipment-request.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/admin-new-return-shipment-request.php new file mode 100644 index 000000000..14481e946 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/admin-new-return-shipment-request.php @@ -0,0 +1,41 @@ +get_formatted_sender_full_name() ) ) . "\n\n"; + +echo "\n\n"; + +/* This hook is documented in templates/emails/customer-shipment.php */ +do_action( 'woocommerce_gzd_email_shipment_details', $shipment, $sent_to_admin, $plain_text, $email ); + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n----------------------------------------\n\n"; +} + +echo "\n----------------------------------------\n\n"; + +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-guest-return-shipment-request.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-guest-return-shipment-request.php new file mode 100644 index 000000000..132b76d58 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-guest-return-shipment-request.php @@ -0,0 +1,40 @@ +get_billing_first_name() ) ) . "\n\n"; // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + +echo sprintf( esc_html_x( 'You\'ve requested a return to your order %s. Please follow the link to add your return request.', 'shipments', 'woocommerce-germanized' ), esc_html( $order->get_order_number() ) ) . "\n\n"; + +echo esc_url( $add_return_request_url ) . "\n\n"; + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n----------------------------------------\n\n"; +} + +echo "\n----------------------------------------\n\n"; + +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment-delivered.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment-delivered.php new file mode 100644 index 000000000..4c5660934 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment-delivered.php @@ -0,0 +1,43 @@ +get_billing_first_name() ) ) . "\n\n"; // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + +echo esc_html_x( 'Thank you! Your return has been received successfully. There are more details below for your reference:', 'shipments', 'woocommerce-germanized' ); + +echo "\n\n"; + +/* This hook is documented in templates/emails/customer-shipment.php */ +do_action( 'woocommerce_gzd_email_shipment_details', $shipment, $sent_to_admin, $plain_text, $email ); + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n----------------------------------------\n\n"; +} + +echo "\n----------------------------------------\n\n"; + +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment.php new file mode 100644 index 000000000..6f2fcdcd7 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment.php @@ -0,0 +1,48 @@ +get_billing_first_name() ) ) . "\n\n"; // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + + +if ( $is_confirmation ) { + echo esc_html_x( 'Your return request has been accepted. Please follow the instructions beneath to return your shipment.', 'shipments', 'woocommerce-germanized' ); +} else { + echo esc_html_x( 'A new return has been added to your order. Please follow the instructions beneath to return your shipment.', 'shipments', 'woocommerce-germanized' ); +} + +echo "\n\n"; + +/* This hook is documented in templates/emails/customer-shipment.php */ +do_action( 'woocommerce_gzd_email_shipment_details', $shipment, $sent_to_admin, $plain_text, $email ); + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n----------------------------------------\n\n"; +} + +echo "\n----------------------------------------\n\n"; + +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-shipment.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-shipment.php new file mode 100644 index 000000000..751b764c4 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-shipment.php @@ -0,0 +1,49 @@ +get_billing_first_name() ) ) . "\n\n"; // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch + +if ( $partial_shipment ) { + /* translators: %s: Site title */ + printf( esc_html_x( 'Your order on %1$s has been partially shipped via %2$s. Find details below for your reference:', 'shipments', 'woocommerce-germanized' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), wc_gzd_get_shipment_shipping_provider_title( $shipment ) ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped +} else { + /* translators: %s: Site title */ + printf( esc_html_x( 'Your order on %1$s has been shipped via %2$s. Find details below for your reference:', 'shipments', 'woocommerce-germanized' ), wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ), wc_gzd_get_shipment_shipping_provider_title( $shipment ) ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped +} + +echo "\n\n"; + +/* This hook is documented in templates/emails/customer-shipment.php */ +do_action( 'woocommerce_gzd_email_shipment_details', $shipment, $sent_to_admin, $plain_text, $email ); + +/** + * Show user-defined additional content - this is set in each email's settings. + */ +if ( $additional_content ) { + echo esc_html( wp_strip_all_tags( wptexturize( $additional_content ) ) ); + echo "\n\n----------------------------------------\n\n"; +} + +echo "\n----------------------------------------\n\n"; + +echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ); diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/email-order-shipments.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-order-shipments.php new file mode 100644 index 000000000..2c8b85934 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-order-shipments.php @@ -0,0 +1,47 @@ + $shipment ) { + $count++; + + echo "\n"; + + if ( count( $shipments ) > 1 ) { + echo sprintf( esc_html_x( 'Shipment %1$d of %2$d', 'shipments', 'woocommerce-germanized' ), esc_html( $count ), esc_html( count( $shipments ) ) ) . "\n\n"; + } + + if ( $shipment->get_est_delivery_date() ) { + echo esc_html( _x( 'Estimated date:', 'shipments', 'woocommerce-germanized' ) ) . ' ' . esc_html( wc_format_datetime( $shipment->get_est_delivery_date(), wc_date_format() ) ) . "\n\n"; + } + + if ( $shipment->has_tracking() ) { + if ( $shipment->get_tracking_url() ) { + echo esc_html( _x( 'Track your shipment', 'shipments', 'woocommerce-germanized' ) ) . ': ' . esc_url( $shipment->get_tracking_url() ) . "\n"; + } + + if ( $shipment->has_tracking_instruction() ) { + echo esc_html( $shipment->get_tracking_instruction( true ) ) . "\n"; + } + } else { + echo esc_html( _x( 'Sorry, this shipment does currently not support tracking.', 'shipments', 'woocommerce-germanized' ) ) . "\n"; + } +} diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/email-return-shipment-instructions.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-return-shipment-instructions.php new file mode 100644 index 000000000..ee24b7d3b --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-return-shipment-instructions.php @@ -0,0 +1,23 @@ +get_shipping_provider_instance(); + +if ( $provider && $provider->has_return_instructions() ) { + echo wp_kses_post( wpautop( wptexturize( $provider->get_return_instructions() ) ) . PHP_EOL ); +} diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-address.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-address.php new file mode 100644 index 000000000..2eaac15ae --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-address.php @@ -0,0 +1,20 @@ +#i', "\n", $shipment->get_formatted_address() ) . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-details.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-details.php new file mode 100644 index 000000000..51c4b8206 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-details.php @@ -0,0 +1,43 @@ +get_type() ) ) : sprintf( _x( '[%1$s #%2$s]', 'shipments', 'woocommerce-germanized' ), wc_gzd_get_shipment_label_title( $shipment->get_type() ), $shipment->get_shipment_number() ) ) ) ) . "\n"; +echo "\n" . wc_gzd_get_email_shipment_items( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $shipment, + array( + 'show_sku' => $sent_to_admin, + 'show_image' => false, + 'image_size' => array( 32, 32 ), + 'plain_text' => true, + 'sent_to_admin' => $sent_to_admin, + ) +); + +echo "==========\n\n"; + +if ( $sent_to_admin ) { + /* translators: %s: Shipment link. */ + echo "\n" . sprintf( esc_html_x( 'View shipment: %s', 'shipments', 'woocommerce-germanized' ), esc_url( $shipment->get_edit_shipment_url() ) ) . "\n"; +} + +/* This hook is documented in templates/emails/customer-shipment.php */ +do_action( 'woocommerce_gzd_email_after_shipment_table', $shipment, $sent_to_admin, $plain_text, $email ); diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-items.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-items.php new file mode 100644 index 000000000..2fa1d8308 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-items.php @@ -0,0 +1,46 @@ + $item ) : + $product = $item->get_product(); + $sku = $item->get_sku(); + $purchase_note = ''; + + /* This filter is documented in templates/emails/email-shipment-items.php */ + if ( ! apply_filters( 'woocommerce_gzd_shipment_item_visible', true, $item ) ) { + continue; + } + + /* This filter is documented in templates/emails/email-shipment-items.php */ + echo wp_kses_post( apply_filters( 'woocommerce_gzd_shipment_item_name', $item->get_name(), $item, false ) ); + + if ( $show_sku && $sku ) { + echo ' (#' . esc_html( $sku ) . ')'; + } + + /* This filter is documented in templates/emails/email-shipment-items.php */ + echo ' X ' . wp_kses_post( apply_filters( 'woocommerce_gzd_email_shipment_item_quantity', $item->get_quantity(), $item ) ); + echo "\n"; + + /* This hook is documented in templates/emails/email-shipment-items.php */ + do_action( 'woocommerce_gzd_shipment_item_meta', $item_id, $item, $shipment, $plain_text ); + + echo "\n\n"; +endforeach; diff --git a/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-tracking.php b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-tracking.php new file mode 100644 index 000000000..403a94c5a --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-tracking.php @@ -0,0 +1,33 @@ +get_est_delivery_date() ) { + echo esc_html( _x( 'Estimated date:', 'shipments', 'woocommerce-germanized' ) ) . ' ' . esc_html( wc_format_datetime( $shipment->get_est_delivery_date(), wc_date_format() ) ) . "\n\n"; +} + +if ( $shipment->get_tracking_url() ) { + echo esc_html( _x( 'Track your shipment', 'shipments', 'woocommerce-germanized' ) ) . ': ' . esc_url( $shipment->get_tracking_url() ) . "\n"; +} + +if ( $shipment->has_tracking_instruction() ) { + echo esc_html( $shipment->get_tracking_instruction( true ) ) . "\n"; +} diff --git a/packages/woocommerce-germanized-shipments/templates/global/empty.php b/packages/woocommerce-germanized-shipments/templates/global/empty.php new file mode 100644 index 000000000..fd3489d94 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/global/empty.php @@ -0,0 +1,21 @@ + +
> + + + + + +

+ + +

+ +

+ + +

+ +
+ + + +

+ + +

+ +
+ + + +
diff --git a/packages/woocommerce-germanized-shipments/templates/myaccount/add-return-shipment.php b/packages/woocommerce-germanized-shipments/templates/myaccount/add-return-shipment.php new file mode 100644 index 000000000..9838f1e98 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/myaccount/add-return-shipment.php @@ -0,0 +1,96 @@ + +

+ +

+ + +

+ +

+ + +
+ + + + + + + + + + + + get_available_items_for_return() as $order_item_id => $item_data ) { + + wc_get_template( + 'shipment/add-return-shipment-item.php', + array( + 'item' => $shipment_order->get_simple_shipment_item( $order_item_id ), + 'order_item_id' => $order_item_id, + 'order' => $order, + 'max_quantity' => $item_data['max_quantity'], + ) + ); + } + + /** + * This action is executed after printing the add return shipment table on the customer account page. + * + * @param WC_Order $order The order instance. + * + * @since 3.1.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_add_return_shipment_details_after_shipment_table_items', $order ); + ?> + +
+ +

+ + + + + + +

+
+ diff --git a/packages/woocommerce-germanized-shipments/templates/myaccount/order-shipments.php b/packages/woocommerce-germanized-shipments/templates/myaccount/order-shipments.php new file mode 100644 index 000000000..ed4571e3e --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/myaccount/order-shipments.php @@ -0,0 +1,53 @@ + + + +

+ + 'simple', + 'shipments' => $shipments, + 'order' => $order, + ) + ); + ?> + + + +

+ +

+ + + +

+ + 'return', + 'shipments' => $returns, + 'order' => $order, + ) + ); + ?> + diff --git a/packages/woocommerce-germanized-shipments/templates/myaccount/shipments.php b/packages/woocommerce-germanized-shipments/templates/myaccount/shipments.php new file mode 100644 index 000000000..71d4cf1fc --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/myaccount/shipments.php @@ -0,0 +1,113 @@ + + + + + + $column_name ) : ?> + + + + + + + get_item_count(); + ?> + + get_type() ) as $column_id => $column_name ) : ?> + + + + + + + + diff --git a/packages/woocommerce-germanized-shipments/templates/myaccount/view-shipment.php b/packages/woocommerce-germanized-shipments/templates/myaccount/view-shipment.php new file mode 100644 index 000000000..7c28e5636 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/myaccount/view-shipment.php @@ -0,0 +1,42 @@ + +

+ ' . $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_product(); + $is_visible = $product && $product->is_visible(); + $item_sku = $item->get_sku(); + + /** This filter is documented in templates/myaccount/shipment/shipment-details-item.php */ + $product_permalink = apply_filters( 'woocommerce_gzd_shipment_item_permalink', $is_visible ? $product->get_permalink() : '', $item, $order ); + + /** This filter is documented in templates/emails/email-shipment-items.php */ + echo apply_filters( 'woocommerce_gzd_shipment_item_name', ( $product_permalink ? sprintf( '%s', esc_url( $product_permalink ), $item->get_name() ) : $item->get_name() ) . ( ! empty( $item_sku ) ? ' (' . esc_html( $item_sku ) . ')' : '' ), $item, $is_visible ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> + + + + + + + + + 1 + + 'item[' . esc_attr( $order_item_id ) . '][quantity]', + 'input_value' => 1, + 'max_value' => $max_quantity, + 'min_value' => 1, + ), + $item->get_product() + ); + ?> + + diff --git a/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-address.php b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-address.php new file mode 100644 index 000000000..6e0b8713c --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-address.php @@ -0,0 +1,52 @@ + +
+ +
+ +

+ +
+ get_formatted_address( esc_html_x( 'N/A', 'shipments', 'woocommerce-germanized' ) ) ); ?> + + get_phone() ) : ?> +

get_phone() ); ?>

+ + + get_email() ) : ?> + + +
+ +
+ +
+ + diff --git a/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-item.php b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-item.php new file mode 100644 index 000000000..cee0cfd43 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-item.php @@ -0,0 +1,93 @@ + + + + + is_visible(); + $item_sku = $item->get_sku(); + + /** + * This filter may adjust the shipment item permalink on the customer account page. + * + * @param string $permalink The permalink. + * @param ShipmentItem $item The shipment item instance. + * @param Shipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + $product_permalink = apply_filters( 'woocommerce_gzd_shipment_item_permalink', $is_visible ? $product->get_permalink() : '', $item, $shipment ); + + /** This filter is documented in templates/emails/email-shipment-items.php */ + echo apply_filters( 'woocommerce_gzd_shipment_item_name', ( $product_permalink ? sprintf( '%s', esc_url( $product_permalink ), $item->get_name() ) : $item->get_name() ) . ( ! empty( $item_sku ) ? ' (' . esc_html( $item_sku ) . ')' : '' ), $item, $is_visible ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> + + + + get_quantity(); + $qty_display = esc_html( $qty ); + + /** + * This filter may adjust the shipment item quantity HTML on the customer account page. + * + * @param string $html The HTML output. + * @param ShipmentItem $item The shipment item instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + echo apply_filters( 'woocommerce_gzd_shipment_item_quantity_html', ' ' . sprintf( '× %s', $qty_display ) . '', $item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> + + + diff --git a/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-tracking.php b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-tracking.php new file mode 100644 index 000000000..6cb603862 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-tracking.php @@ -0,0 +1,44 @@ + +
+ +

+ + get_tracking_url() ) : ?> +

+ + + has_tracking_instruction() ) : ?> +

get_tracking_instruction() ); ?>

+ + +
+ + diff --git a/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details.php b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details.php new file mode 100644 index 000000000..0050f657e --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details.php @@ -0,0 +1,123 @@ +get_order(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited +$show_receiver_details = is_user_logged_in() && $order && $order->get_user_id() === get_current_user_id(); +$show_tracking = $show_receiver_details && $shipment->has_tracking(); +$shipment_items = $shipment->get_items(); + +if ( 'return' === $shipment->get_type() ) { + if ( $provider = $shipment->get_shipping_provider_instance() ) { + if ( $provider->hide_return_address() ) { + $show_receiver_details = false; + } + } +} +?> +
+ + +

+ + + + + + + + + + + + $item ) { + $product = $item->get_product(); + + wc_get_template( + 'shipment/shipment-details-item.php', + array( + 'shipment' => $shipment, + 'item_id' => $item_id, + 'item' => $item, + 'product' => $product, + ) + ); + } + + /** + * This action is executed after printing the shipment table items on the customer account page. + * + * @param Shipment $shipment The shipment instance. + * + * @since 3.0.0 + * @package Vendidero/Germanized/Shipments + */ + do_action( 'woocommerce_gzd_shipment_details_after_shipment_table_items', $shipment ); + ?> + +
+ + +
+ + $shipment ) ); +} + +if ( $show_tracking ) { + wc_get_template( 'shipment/shipment-details-tracking.php', array( 'shipment' => $shipment ) ); +} diff --git a/packages/woocommerce-germanized-shipments/templates/shipment/shipment-return-instructions.php b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-return-instructions.php new file mode 100644 index 000000000..624594d02 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-return-instructions.php @@ -0,0 +1,41 @@ +get_shipping_provider_instance(); +?> + +has_return_instructions() ) : ?> +
get_return_instructions() ) ) ) ); ?>
+ + +has_status( 'delivered' ) && ( $label = $shipment->get_label() ) ) : ?> +

+ + + diff --git a/packages/woocommerce-germanized-shipments/woocommerce-germanized-shipments.php b/packages/woocommerce-germanized-shipments/woocommerce-germanized-shipments.php new file mode 100644 index 000000000..1e246dfc5 --- /dev/null +++ b/packages/woocommerce-germanized-shipments/woocommerce-germanized-shipments.php @@ -0,0 +1,76 @@ + +
+

+ composer install', + '' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '' + ); + ?> +

+
+