# 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, $houseNumber, $matches );
+ if ( $result === 0 ) {
+ throw new Exception( sprintf( 'Error occurred while trying to house number \'%s\'', $houseNumber ) );
+ } elseif ( $result === false ) {
+ throw new RuntimeException( sprintf( 'Error occurred while trying to house number \'%s\'', $houseNumber ) );
+ }
+
+ return array(
+ 'base' => $matches['House_number_base'],
+ 'extension' => $matches['House_number_extension']
+ );
+ }
+}
\ No newline at end of file
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..84839d2c8
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Admin/Admin.php
@@ -0,0 +1,954 @@
+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( $_POST['_hs_code'] ) : '';
+ $country = isset( $_POST['_manufacture_country'] ) ? wc_clean( $_POST['_manufacture_country'] ) : '';
+
+ $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 ) ) {
+ $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'] ) ) {
+ return;
+ }
+ ?>
+
+
wp-content/uploads/' . $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( 'manage_woocommerce' ) ) {
+ $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 ) . ' '; // WPCS: override ok.
+ 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 );
+ }
+
+ public 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();
+ }
+
+ // phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification -- Nonce verification already handled in WC_Admin_Settings::save()
+ if ( isset( $_POST['packaging'] ) ) {
+ $packaging_post = wc_clean( wp_unslash( $_POST['packaging'] ) );
+ $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 ) ? '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();
+
+ // phpcs:disable WordPress.Security.NonceVerification.NoNonceVerification -- Nonce verification already handled in WC_Admin_Settings::save()
+ if ( isset( $_POST['shipment_return_reason'] ) ) {
+
+ $reasons_post = wc_clean( wp_unslash( $_POST['shipment_return_reason'] ) );
+ $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();
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _x( 'Shipments', 'shipments', 'woocommerce-germanized' ),
+ 'path' => Package::get_path() . '/templates',
+ 'template_path' => $check['germanized']['template_path'],
+ 'outdated_help_url' => $check['germanized']['outdated_help_url'],
+ 'files' => array(),
+ 'has_outdated' => false,
+ );
+
+ return $check;
+ }
+
+ public static function add_table_view( $screen_ids ) {
+ $screen_ids[] = 'woocommerce_page_wc-gzd-shipments';
+ $screen_ids[] = 'woocommerce_page_wc-gzd-return-shipments';
+
+ return $screen_ids;
+ }
+
+ public static function handle_order_bulk_actions( $redirect_to, $action, $ids ) {
+ $ids = apply_filters( 'woocommerce_bulk_action_ids', array_reverse( array_map( 'absint', $ids ) ), $action, 'order' );
+ $changed = 0;
+ $report_action = '';
+
+ if ( 'gzd_create_shipments' === $action ) {
+
+ foreach ( $ids as $id ) {
+ $order = wc_get_order( $id );
+ $report_action = 'gzd_created_shipments';
+
+ if ( $order ) {
+ Automation::create_shipments( $id, false );
+ $changed++;
+ }
+ }
+ }
+
+ if ( $changed ) {
+ $redirect_to = add_query_arg(
+ array(
+ 'post_type' => 'shop_order',
+ 'bulk_action' => $report_action,
+ 'changed' => $changed,
+ 'ids' => join( ',', $ids ),
+ ),
+ $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' ) ) ) {
+ 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' ), 'manage_woocommerce', 'wc-gzd-shipments', array( __CLASS__, 'shipments_page' ) );
+ add_submenu_page( 'woocommerce', _x( 'Returns', 'shipments', 'woocommerce-germanized' ), _x( 'Returns', 'shipments', 'woocommerce-germanized' ), 'manage_woocommerce', '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;
+ $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 = explode( ',', $_REQUEST['ids'] );
+ } elseif ( ! empty( $_REQUEST['shipment'] ) ) {
+ $shipment_ids = array_map( 'intval', $_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_redirect( $sendback );
+ exit();
+
+ } elseif ( ! empty( $_REQUEST['_wp_http_referer'] ) ) {
+ wp_redirect( remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
+ 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' ), $_SERVER['REQUEST_URI'] );
+ ?>
+
+ views(); ?>
+
+
+
+
+
+
+
+
+
+
+
+ output_notice();
+ $_SERVER['REQUEST_URI'] = remove_query_arg( array( 'updated', 'changed', 'deleted', 'trashed', 'untrashed' ), $_SERVER['REQUEST_URI'] );
+ ?>
+
+ views(); ?>
+
+
+
+
+
+
+ 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() ) ) {
+ 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' ) ) ) {
+ 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'] ) ) {
+ wp_enqueue_style( 'woocommerce_gzd_shipments_admin' );
+ }
+ }
+
+ public static function admin_scripts() {
+ global $post;
+
+ $screen = get_current_screen();
+ $screen_id = $screen ? $screen->id : '';
+ $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+
+ wp_register_script( 'wc-gzd-admin-shipment-label-backbone', Package::get_assets_url() . '/js/admin-shipment-label-backbone' . $suffix . '.js', array( 'jquery', 'woocommerce_admin', 'wc-backbone-modal' ), Package::get_version() );
+ 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() );
+ 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() );
+ 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() );
+ wp_register_script( 'wc-gzd-admin-shipping-providers', Package::get_assets_url() . '/js/admin-shipping-providers' . $suffix . '.js', array( 'jquery' ), Package::get_version() );
+ 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() );
+
+ // Orders.
+ if ( in_array( str_replace( 'edit-', '', $screen_id ), wc_get_order_types( 'order-meta-boxes' ) ) ) {
+
+ wp_enqueue_script( 'wc-gzd-admin-shipments' );
+ wp_enqueue_script( 'wc-gzd-admin-shipment' );
+
+ 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' => isset( $post->ID ) ? $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' ),
+ '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' ),
+ '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'] ) ) {
+ 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' ),
+ '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'] && ( isset( $_GET['zone_id'] ) || isset( $_GET['instance_id'] ) ) ) {
+ 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;
+ }
+
+ public static function get_screen_ids() {
+
+ $screen_ids = array(
+ 'woocommerce_page_wc-gzd-shipments',
+ 'woocommerce_page_wc-gzd-return-shipments',
+ );
+
+ foreach ( wc_get_order_types() as $type ) {
+ $screen_ids[] = $type;
+ $screen_ids[] = 'edit-' . $type;
+ }
+
+ return $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..d6bd23935
--- /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( sizeof( $this->get_ids() ) / $this->get_limit() );
+ }
+
+ abstract public function get_limit();
+
+ public function get_total() {
+ return sizeof( $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;
+ }
+}
\ No newline at end of file
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..90bbda3a2
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Admin/BulkLabel.php
@@ -0,0 +1,194 @@
+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 = '' . _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( _x( 'Labels partially generated. %s', 'shipments', 'woocommerce-germanized' ), $download_button ) . '
';
+ }
+ }
+
+ 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();
+ $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 ) ) {
+ $this->add_notice( sprintf( _x( 'Error while creating label for %s: %s', 'shipments', 'woocommerce-germanized' ), '' . sprintf( _x( 'shipment #%d', 'shipments', 'woocommerce-germanized' ), $shipment_id ) . ' ', $result->get_error_message() ), 'error' );
+ } else {
+ $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 ) {}
+ }
+
+ $this->update_notices();
+ }
+}
\ No newline at end of file
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..8924f5614
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Admin/MetaBox.php
@@ -0,0 +1,171 @@
+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 ] ) ) {
+ $props['weight'] = wc_clean( wp_unslash( $_POST['shipment_weight'][ $id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_length'][ $id ] ) ) {
+ $props['length'] = wc_clean( wp_unslash( $_POST['shipment_length'][ $id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_width'][ $id ] ) ) {
+ $props['width'] = wc_clean( wp_unslash( $_POST['shipment_width'][ $id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_height'][ $id ] ) ) {
+ $props['height'] = wc_clean( wp_unslash( $_POST['shipment_height'][ $id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_shipping_method'][ $id ] ) ) {
+ $props['shipping_method'] = wc_clean( wp_unslash( $_POST['shipment_shipping_method'][ $id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_tracking_id'][ $id ] ) ) {
+ $props['tracking_id'] = wc_clean( wp_unslash( $_POST['shipment_tracking_id'][ $id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_packaging_id'][ $id ] ) ) {
+ $props['packaging_id'] = wc_clean( wp_unslash( $_POST['shipment_packaging_id'][ $id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_shipping_provider'][ $id ] ) ) {
+ $provider = wc_clean( wp_unslash( $_POST['shipment_shipping_provider'][ $id ] ) );
+ $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';
+
+ // 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() ) ) {
+ $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 ] ) ) {
+ $props['quantity'] = absint( wp_unslash( $_POST['shipment_item'][ $id ]['quantity'][ $item_id ] ) );
+ }
+
+ if ( isset( $_POST['shipment_item'][ $id ]['return_reason_code'][ $item_id ] ) ) {
+ $props['return_reason_code'] = wc_clean( wp_unslash( $_POST['shipment_item'][ $id ]['return_reason_code'][ $item_id ] ) );
+ }
+
+ $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';
+
+ if ( ! wc_gzd_is_shipment_status( $status ) ) {
+ $status = 'draft';
+ }
+
+ $shipment->set_status( $status );
+ }
+ }
+
+ /**
+ * Output the metabox.
+ *
+ * @param WP_Post $post
+ */
+ public static function output( $post ) {
+ global $post, $thepostid, $theorder;
+
+ if ( ! is_int( $thepostid ) ) {
+ $thepostid = $post->ID;
+ }
+
+ if ( ! is_object( $theorder ) ) {
+ $theorder = wc_get_order( $thepostid );
+ }
+
+ $order = $theorder;
+ $order_shipment = wc_gzd_get_shipment_order( $order );
+ $active_shipment = isset( $_GET['shipment_id'] ) ? absint( $_GET['shipment_id'] ) : 0;
+
+ 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..31067abd0
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Admin/ProviderSettings.php
@@ -0,0 +1,254 @@
+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 ( $provider->get_id() <= 0 ) {
+ 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() );
+ }
+ }
+
+ $provider->save();
+
+ 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();
+ }
+ }
+}
\ No newline at end of file
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..048531b33
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Admin/ReturnTable.php
@@ -0,0 +1,100 @@
+';
+ $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 '–';
+ }
+ }
+}
\ No newline at end of file
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..31131d5e2
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Admin/Settings.php
@@ -0,0 +1,534 @@
+ 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' ) . '',
+ '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' );
+ }
+
+ 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() ) ) {
+ 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() ) ) {
+ 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' => '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 get_sanitized_settings( $settings, $data = null ) {
+ if ( is_null( $data ) ) {
+ $data = $_POST; // WPCS: input var okay, CSRF ok.
+ }
+
+ 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' ) ) || ( 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( '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 ) {
+ echo '
';
+ $missing_div_closes--;
+ }
+
+ $missing_div_closes++;
+ ?>
+
+ 0 ) {
+ echo '
';
+ $missing_div_closes--;
+ }
+
+ $missing_div_closes++;
+ ?>
+
+
+
+ 0 ) {
+ while( $missing_div_closes > 0 ) {
+ $missing_div_closes--;
+ echo '';
+ }
+ }
+
+ $html = ob_get_clean();
+
+ if ( ! $echo ) {
+ return $html;
+ } else {
+ echo $html;
+ }
+ }
+}
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..4ffde8b79
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Admin/Table.php
@@ -0,0 +1,1161 @@
+ '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 '' . 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; // WPCS: input var ok, CSRF ok.
+ $bulk_action = isset( $_REQUEST['bulk_action'] ) ? wc_clean( wp_unslash( $_REQUEST['bulk_action'] ) ) : ''; // WPCS: input var ok, CSRF ok.
+
+ 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 );
+
+ /**
+ * 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' );
+ $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 ) ) ) {
+ $args['status'] = wc_clean( wp_unslash( $_REQUEST['shipment_status'] ) );
+ }
+
+ if ( isset( $_REQUEST['orderby'] ) ) {
+ if ( 'weight' === $_REQUEST['orderby'] ) {
+ $args['orderby'] = 'weight';
+ } else {
+ $args['orderby'] = wc_clean( wp_unslash( $_REQUEST['orderby'] ) );
+ }
+ }
+
+ if ( isset( $_REQUEST['order'] ) ) {
+ $args['order'] = 'asc' === $_REQUEST['order'] ? 'ASC' : 'DESC';
+ }
+
+ if ( isset( $_REQUEST['parent_id'] ) && ! empty( $_REQUEST['parent_id'] ) ) {
+ $args['parent_id'] = absint( $_REQUEST['parent_id'] );
+ }
+
+ if ( isset( $_REQUEST['order_id'] ) && ! empty( $_REQUEST['order_id'] ) ) {
+ $args['order_id'] = absint( $_REQUEST['order_id'] );
+ }
+
+ if ( isset( $_REQUEST['m'] ) ) {
+ $m = wc_clean( wp_unslash( $_REQUEST['m'] ) );
+ $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'] ) ) {
+ $search = wc_clean( wp_unslash( $_REQUEST['s'] ) );
+
+ 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 _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;
+ 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'] ) ) ) {
+ $class = 'current';
+ }
+
+ $all_inner_html = sprintf(
+ _nx(
+ 'All (%s) ',
+ 'All (%s) ',
+ $total_shipments, 'shipments', 'woocommerce-germanized'
+ ),
+ 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 ) ) || empty( $num_shipments[ $status ] ) ) {
+ continue;
+ }
+
+ if ( isset( $_REQUEST['shipment_status'] ) && $status === $_REQUEST['shipment_status'] ) {
+ $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 ] ),
+ 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'] ) ) {
+ 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'] ) {
+ $extra_checks = $wpdb->prepare( ' AND shipment_status = %s', wc_clean( wp_unslash( $_GET['shipment_status'] ) ) );
+ }
+
+ $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
+ " );
+
+ $month_count = count( $months );
+
+ if ( ! $month_count || ( 1 == $month_count && 0 == $months[0]->month ) ) {
+ return;
+ }
+
+ $m = isset( $_GET['m'] ) ? (int) $_GET['m'] : 0;
+ ?>
+
+
+ value="0">
+ year ) {
+ continue;
+ }
+
+ $month = zeroise( $arc_row->month, 2 );
+ $year = $arc_row->year;
+
+ printf(
+ "%s \n",
+ selected( $m, $year . $month, false ),
+ esc_attr( $arc_row->year . $month ),
+ /* translators: 1: month name, 2: 4-digit year */
+ sprintf( __( '%1$s %2$d' ), $wp_locale->get_month( $month ), $year )
+ );
+ }
+ ?>
+
+
+
+ get_notices( 'error' );
+ $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();
+
+ /**
+ * 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;
+
+ submit_button( _x( 'Filter', 'shipments', 'woocommerce-germanized' ), '', 'filter_action', false, array( 'id' => 'shipment-query-submit' ) );
+ }
+ }
+ ?>
+
+
+
+
+
+ ';
+ $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( '%s #%s', 'shipment title', 'woocommerce-germanized' ), wc_gzd_get_shipment_label_title( $shipment->get_type() ), $shipment->get_id() );
+
+ if ( $order = $shipment->get_order() ) {
+ echo '' . $title . ' ';
+ } else {
+ echo $title . ' ';
+ }
+
+ echo '';
+
+ if ( $packaging = $shipment->get_packaging() ) {
+ echo '' . sprintf( _x( '%s', 'shipments', 'woocommerce-germanized' ), $packaging->get_description() ) . ' ';
+ }
+
+ $provider = $shipment->get_shipping_provider();
+
+ if ( ! empty( $provider ) ) {
+ echo '' . sprintf( _x( 'via %s', 'shipments', 'woocommerce-germanized' ), 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 '' . $tracking_id . ' ';
+ } else {
+ echo '' . $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 ); // WPCS: XSS ok.
+
+ /**
+ * 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_id() ); ?>
+
+
+
+
+
+ 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 '' . 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_weight(), $shipment->get_weight_unit() );
+ }
+
+ /**
+ * 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 $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() );
+ }
+
+ /**
+ * 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', current_time( 'timestamp', true ) ) && $shipment_timestamp <= current_time( 'timestamp', true ) ) {
+ $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(), current_time( 'timestamp', true ) )
+ );
+ } 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(
+ '%3$s ',
+ 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 '' . $order->get_order_number() . ' ';
+ } else {
+ echo $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 );
+ }
+
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/Ajax.php b/packages/woocommerce-germanized-shipments/src/Ajax.php
new file mode 100644
index 000000000..0f843d4d9
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Ajax.php
@@ -0,0 +1,1453 @@
+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' ) ) ) {
+ continue;
+ }
+
+ $data[ $key ] = wc_clean( wp_unslash( $value ) );
+ }
+
+ $result = $shipment->create_label( $data );
+ }
+
+ if ( is_wp_error( $result ) ) {
+ $response = array(
+ 'success' => false,
+ 'messages' => $result->get_error_messages(),
+ );
+ } 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,
+ '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( $_POST['provider'] ) );
+ $enable = wc_clean( $_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( $_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 shipments_bulk_action_handle() {
+ $action = isset( $_POST['bulk_action'] ) ? wc_clean( $_POST['bulk_action'] ) : '';
+ $type = isset( $_POST['type'] ) ? wc_clean( $_POST['type'] ) : 'simple';
+
+ 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) $_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_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 ( ! is_numeric( $term ) ) {
+ $ids = wc_order_search( $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 ) ) . '%'
+ )
+ );
+ }
+
+ $found_orders = array();
+
+ if ( ! empty( $_GET['exclude'] ) ) {
+ $ids = array_diff( $ids, array_map( 'absint', (array) wp_unslash( $_GET['exclude'] ) ) );
+ }
+
+ foreach ( $ids as $id ) {
+ if ( $order = wc_get_order( $id ) ) {
+ $found_orders[ $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();
+ ?>
+
+
+ needs_payment() ) {
+ printf(
+ '%s ',
+ esc_url( $order->get_checkout_payment_url() ),
+ _x( 'Customer payment page →', 'shipments', 'woocommerce-germanized' )
+ );
+ }
+ ?>
+
+
+ $status_name ) {
+ echo 'get_status( 'edit' ), false ) . '>' . esc_html( $status_name ) . ' ';
+ }
+ ?>
+
+
+ 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( $_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 && $item_quantity === 0 ) {
+ $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 );
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/Api.php b/packages/woocommerce-germanized-shipments/src/Api.php
new file mode 100644
index 000000000..b20221be6
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Api.php
@@ -0,0 +1,329 @@
+get_data();
+ $response_order_data['shipments'] = array();
+ $context = 'view';
+
+ if ( $order ) {
+ $order_shipment = wc_gzd_get_shipment_order( $order );
+ $shipments = $order_shipment->get_shipments();
+
+ if ( ! empty( $shipments ) ) {
+
+ foreach( $shipments as $shipment ) {
+
+ $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 ),
+ 'quantity' => $item->get_quantity( $context ),
+ );
+ }
+
+ $shipment_data = array(
+ 'id' => $shipment->get_id(),
+ '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(), $request['dp'] ),
+ 'weight' => $shipment->get_weight( $context ),
+ 'status' => $shipment->get_status(),
+ 'tracking_id' => $shipment->get_tracking_id(),
+ 'tracking_url' => $shipment->get_tracking_url(),
+ 'shipping_provider' => $shipment->get_shipping_provider(),
+ 'dimensions' => array(
+ 'length' => $shipment->get_length( $context ),
+ 'width' => $shipment->get_width( $context ),
+ 'height' => $shipment->get_height( $context ),
+ ),
+ 'address' => $shipment->get_address( $context ),
+ 'items' => $item_data,
+ );
+
+ $response_order_data['shipments'][] = $shipment_data;
+ }
+ }
+ }
+
+ $response->set_data( $response_order_data );
+
+ return $response;
+ }
+
+ public static function order_shipments_schema( $schema ) {
+ $weight_unit = get_option( 'woocommerce_weight_unit' );
+ $dimension_unit = get_option( 'woocommerce_dimension_unit' );
+
+ $schema['shipments'] = array(
+ 'description' => _x( 'List of shipments.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'array',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => _x( 'Shipment ID.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'integer',
+ '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(),
+ 'readonly' => true,
+ ),
+ 'tracking_id' => array(
+ 'description' => _x( 'Shipment tracking id.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ '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' ),
+ 'readonly' => true,
+ ),
+ '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' ),
+ 'readonly' => true,
+ ),
+ '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' ),
+ 'readonly' => true,
+ ),
+ 'weight' => array(
+ /* translators: %s: weight unit */
+ 'description' => sprintf( _x( 'Shipment weight (%s).', 'shipments', 'woocommerce-germanized' ), $weight_unit ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'dimensions' => array(
+ 'description' => _x( 'Shipment dimensions.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ 'properties' => array(
+ 'length' => array(
+ /* translators: %s: dimension unit */
+ 'description' => sprintf( _x( 'Shipment length (%s).', 'shipments', 'woocommerce-germanized' ), $dimension_unit ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'width' => array(
+ /* translators: %s: dimension unit */
+ 'description' => sprintf( _x( 'Shipment width (%s).', 'shipments', 'woocommerce-germanized' ), $dimension_unit ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'height' => array(
+ /* translators: %s: dimension unit */
+ 'description' => sprintf( _x( 'Shipment height (%s).', 'shipments', 'woocommerce-germanized' ), $dimension_unit ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'address' => array(
+ 'description' => _x( 'Shipping address.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ 'properties' => array(
+ 'first_name' => array(
+ 'description' => _x( 'First name.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'last_name' => array(
+ 'description' => _x( 'Last name.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'company' => array(
+ 'description' => _x( 'Company name.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'address_1' => array(
+ 'description' => _x( 'Address line 1', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'address_2' => array(
+ 'description' => _x( 'Address line 2', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'city' => array(
+ 'description' => _x( 'City name.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'state' => array(
+ 'description' => _x( 'ISO code or name of the state, province or district.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'postcode' => array(
+ 'description' => _x( 'Postal code.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'country' => array(
+ 'description' => _x( 'Country code in ISO 3166-1 alpha-2 format.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'items' => array(
+ 'description' => _x( 'Shipment items.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'array',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ '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' ),
+ 'readonly' => true,
+ ),
+ 'order_item_id' => array(
+ 'description' => _x( 'Order Item ID.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'product_id' => array(
+ 'description' => _x( 'Product ID.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'mixed',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'quantity' => array(
+ 'description' => _x( 'Quantity.', 'shipments', 'woocommerce-germanized' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ return $schema;
+ }
+
+ public static function register_controllers( $controller ) {
+ $controller['wc/v3']['shipments'] = 'Vendidero\Germanized\Shipments\Rest\Shipments.php';
+
+ return $controller;
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/Automation.php b/packages/woocommerce-germanized-shipments/src/Automation.php
new file mode 100644
index 000000000..446e14c38
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Automation.php
@@ -0,0 +1,238 @@
+get_id() ) {
+ self::maybe_create_shipments( $order );
+ }
+ }, 150 );
+ }, 10, 1 );
+
+ add_action( 'woocommerce_new_order', array( __CLASS__, 'maybe_create_shipments' ), 10, 2 );
+ add_filter( 'wcs_renewal_order_created', array( __CLASS__, 'maybe_create_subscription_shipments' ), 10 );
+ }
+
+ if ( 'yes' === Package::get_setting( 'auto_order_shipped_completed_enable' ) ) {
+ add_action( 'woocommerce_gzd_shipments_order_shipped', array( __CLASS__, 'mark_order_completed' ), 10 );
+ }
+
+ if ( 'yes' === Package::get_setting( 'auto_order_completed_shipped_enable' ) ) {
+ add_action( 'woocommerce_order_status_changed', array( __CLASS__, 'maybe_mark_shipments_shipped' ), 150, 4 );
+ }
+ }
+
+ /**
+ * @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'] );
+ }
+
+ 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 : 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 ) ) {
+ $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;
+ }
+}
\ No newline at end of file
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..58f928458
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/DataStores/Label.php
@@ -0,0 +1,556 @@
+set_date_created( current_time( 'timestamp', true ) );
+
+ $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 ] = gmdate( 'Y-m-d H:i:s', $label->{'get_' . $prop}( 'edit' )->getOffsetTimestamp() );
+ $label_data[ 'label_' . $prop . '_gmt' ] = gmdate( 'Y-m-d H:i:s', $label->{'get_' . $prop}( 'edit' )->getTimestamp() );
+ 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' => '0000-00-00 00:00:00' !== $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 ) ) {
+ 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();
+ }
+
+ // 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..ddeb5ed32
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/DataStores/Packaging.php
@@ -0,0 +1,649 @@
+set_date_created( current_time( 'timestamp', true ) );
+
+ $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' => gmdate( 'Y-m-d H:i:s', $packaging->get_date_created( 'edit' )->getOffsetTimestamp() ),
+ 'packaging_date_created_gmt' => gmdate( 'Y-m-d H:i:s', $packaging->get_date_created( 'edit' )->getTimestamp() ),
+ );
+
+ $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 ] = gmdate( 'Y-m-d H:i:s', $packaging->{'get_' . $prop}( 'edit' )->getOffsetTimestamp() );
+ $packaging_data[ 'packaging_' . $prop . '_gmt' ] = gmdate( 'Y-m-d H:i:s', $packaging->{'get_' . $prop}( 'edit' )->getTimestamp() );
+ }
+ 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' => '0000-00-00 00:00:00' !== $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 ) ) {
+ 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();
+ }
+
+ // 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 );
+
+ wp_cache_set( 'packaging-list', $results, 'packaging' );
+ }
+ } else {
+ $results = $wpdb->get_results( $query );
+ }
+
+ foreach ( $results as $key => $packaging ) {
+ $results[ $key ] = wc_gzd_get_packaging( $packaging );
+ }
+
+ return $results;
+ }
+
+ /**
+ * @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 && sizeof( $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 ) {
+ $box = new PackagingBox( $packaging );
+ $org_size = sizeof( $items_to_pack );
+
+ $packer = new VolumePacker( $box, $items );
+ $packed = $packer->pack();
+
+ if ( sizeof( $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 );
+ $results = $wpdb->get_results( $query );
+
+ 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 ( sizeof( $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 );
+ }
+ }
+
+ return apply_filters( 'woocommerce_gzd_find_available_packaging_for_shipment', $this->sort_packaging_list( $packaging_available ), $shipment );
+ }
+
+ 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 ( sizeof( $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..81837e655
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/DataStores/Shipment.php
@@ -0,0 +1,716 @@
+set_date_created( current_time( 'timestamp', true ) );
+ $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 ) ) {
+ $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 ) ) {
+
+ 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 ] = gmdate( 'Y-m-d H:i:s', $shipment->{'get_' . $prop}( 'edit' )->getOffsetTimestamp() );
+ $shipment_data[ 'shipment_' . $prop . '_gmt' ] = gmdate( 'Y-m-d H:i:s', $shipment->{'get_' . $prop}( 'edit' )->getTimestamp() );
+ }
+ 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' => $this->is_valid_timestamp( $data->shipment_date_created_gmt ) ? wc_string_to_timestamp( $data->shipment_date_created_gmt ) : null,
+ 'date_sent' => $this->is_valid_timestamp( $data->shipment_date_sent_gmt ) ? wc_string_to_timestamp( $data->shipment_date_sent_gmt ) : null,
+ 'est_delivery_date' => $this->is_valid_timestamp( $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' ) );
+ }
+ }
+
+ protected function is_valid_timestamp( $mysql_date ) {
+ return ( '0000-00-00 00:00:00' === $mysql_date || null === $mysql_date ) ? false : true;
+ }
+
+ /**
+ * 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 ) ) {
+ 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' ) ) && ! $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();
+ }
+
+ // 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 ) );
+ }
+}
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..8998bbd04
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/DataStores/ShipmentItem.php
@@ -0,0 +1,353 @@
+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 ) ) {
+ 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;
+
+ switch ( $prop ) {}
+
+ $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..af689d224
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/DataStores/ShippingProvider.php
@@ -0,0 +1,460 @@
+set_name( $this->get_unqiue_name( $provider ) );
+
+ $data = array(
+ 'shipping_provider_activated' => $provider->is_activated() ? 1 : 0,
+ 'shipping_provider_name' => $provider->get_name( 'edit' ),
+ 'shipping_provider_title' => $provider->get_title( '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() ) );
+
+ if ( $provider_name_check ) {
+ $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() ) );
+ $suffix++;
+ } while ( $provider_name_check );
+ $slug = $alt_provider_name;
+ }
+
+ return $slug;
+ }
+
+ /**
+ * 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,
+ )
+ );
+
+ $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 ) ) {
+ 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" );
+ $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..ba14cba79
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Emails.php
@@ -0,0 +1,221 @@
+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' ) ) ) {
+ 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,
+ )
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/FormHandler.php b/packages/woocommerce-germanized-shipments/src/FormHandler.php
new file mode 100644
index 000000000..04adf6a99
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/FormHandler.php
@@ -0,0 +1,280 @@
+ $email,
+ 'post__in' => array( $order_id ),
+ 'limit' => 1,
+ 'return' => 'ids'
+ ) ) );
+
+ // Now lets try to find the order by a custom order number
+ if ( empty( $orders ) ) {
+
+ $orders = new WP_Query( apply_filters( 'woocommerce_gzd_return_request_order_query_args', array(
+ 'post_type' => 'shop_order',
+ 'post_status' => 'any',
+ 'limit' => 1,
+ 'fields' => 'ids',
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ array(
+ 'key' => '_billing_email',
+ 'value' => $email,
+ 'compare' => '=',
+ ),
+ array(
+ 'key' => apply_filters( 'woocommerce_gzd_return_request_customer_order_number_meta_key', '_order_number' ),
+ 'value' => $order_id,
+ 'compare' => '='
+ ),
+ ),
+ ) ) );
+
+ if ( ! empty( $orders->posts ) ) {
+ $db_order_id = $orders->posts[0];
+ }
+ } else {
+ $db_order_id = $orders[0];
+ }
+
+ if ( ! $db_order_id || ( ! $order = wc_get_order( $db_order_id ) ) ) {
+ throw new Exception( '' . _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_successfull', $order );
+
+ } catch( Exception $e ) {
+ wc_add_notice( $e->getMessage(), 'error' );
+ do_action( 'woocommerce_gzd_return_request_failed' );
+ }
+ }
+ }
+
+ /**
+ * 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'] ) ? $_REQUEST['add-return-shipment-nonce'] : '';
+
+ 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( $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..4037563a6
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Install.php
@@ -0,0 +1,291 @@
+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;
+
+ $wpdb->hide_errors();
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+ dbDelta( self::get_schema() );
+ }
+
+ private static function create_upload_dir() {
+ Package::maybe_set_upload_dir();
+
+ $dir = Package::get_upload_dir();
+
+ if ( ! @is_dir( $dir['basedir'] ) ) {
+ @mkdir( $dir['basedir'] );
+ }
+
+ if ( ! file_exists( trailingslashit( $dir['basedir'] ) . '.htaccess' ) ) {
+ @file_put_contents( trailingslashit( $dir['basedir'] ) . '.htaccess', 'deny from all' );
+ }
+
+ if ( ! file_exists( trailingslashit( $dir['basedir'] ) . 'index.php' ) ) {
+ @touch( trailingslashit( $dir['basedir'] ) . 'index.php' );
+ }
+ }
+
+ private static function get_schema() {
+ global $wpdb;
+
+ $collate = '';
+
+ if ( $wpdb->has_cap( 'collation' ) ) {
+ $collate = $wpdb->get_charset_collate();
+ }
+
+ $tables = "
+CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipment_items (
+ shipment_item_id BIGINT UNSIGNED NOT NULL auto_increment,
+ shipment_id BIGINT UNSIGNED NOT NULL,
+ shipment_item_name TEXT NOT NULL,
+ shipment_item_order_item_id BIGINT UNSIGNED NOT NULL,
+ shipment_item_product_id BIGINT UNSIGNED NOT NULL,
+ shipment_item_parent_id BIGINT UNSIGNED NOT NULL,
+ shipment_item_quantity SMALLINT 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 UNSIGNED NOT NULL auto_increment,
+ gzd_shipment_item_id BIGINT 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 UNSIGNED NOT NULL auto_increment,
+ shipment_date_created datetime NOT NULL default '0000-00-00 00:00:00',
+ shipment_date_created_gmt datetime NOT NULL default '0000-00-00 00:00:00',
+ shipment_date_sent datetime NOT NULL default '0000-00-00 00:00:00',
+ shipment_date_sent_gmt datetime NOT NULL default '0000-00-00 00:00:00',
+ shipment_est_delivery_date datetime NOT NULL default '0000-00-00 00:00:00',
+ shipment_est_delivery_date_gmt datetime NOT NULL default '0000-00-00 00:00:00',
+ shipment_status varchar(20) NOT NULL default 'gzd-draft',
+ shipment_order_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
+ shipment_packaging_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
+ shipment_parent_id BIGINT 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 UNSIGNED NOT NULL auto_increment,
+ label_date_created datetime NOT NULL default '0000-00-00 00:00:00',
+ label_date_created_gmt datetime NOT NULL default '0000-00-00 00:00:00',
+ label_shipment_id BIGINT UNSIGNED NOT NULL,
+ label_parent_id BIGINT 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 UNSIGNED NOT NULL auto_increment,
+ gzd_shipment_label_id BIGINT 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 UNSIGNED NOT NULL auto_increment,
+ gzd_shipment_id BIGINT 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 UNSIGNED NOT NULL auto_increment,
+ packaging_date_created datetime NOT NULL default '0000-00-00 00:00:00',
+ packaging_date_created_gmt datetime NOT NULL default '0000-00-00 00:00:00',
+ 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 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 UNSIGNED NOT NULL auto_increment,
+ gzd_packaging_id BIGINT 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 UNSIGNED NOT NULL auto_increment,
+ shipping_provider_activated TINYINT(1) NOT NULL default 1,
+ shipping_provider_title varchar(200) NOT NULL DEFAULT '',
+ shipping_provider_name varchar(200) NOT NULL DEFAULT '',
+ PRIMARY KEY (shipping_provider_id)
+) $collate;
+CREATE TABLE {$wpdb->prefix}woocommerce_gzd_shipping_providermeta (
+ meta_id BIGINT UNSIGNED NOT NULL auto_increment,
+ gzd_shipping_provider_id BIGINT 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;
+ }
+}
\ No newline at end of file
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..7e5013dc0
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Interfaces/ShipmentLabel.php
@@ -0,0 +1,97 @@
+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 ) ) {
+ 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'] );
+ }
+
+ 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() ) {
+ add_action( 'woocommerce_process_shop_order_meta', array( __CLASS__, 'create_label' ), 70 );
+ } else {
+ self::create_label( $shipment_id, $shipment );
+ }
+ }
+}
\ No newline at end of file
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..d2fff1c8c
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Labels/DownloadHandler.php
@@ -0,0 +1,127 @@
+ 'no',
+ 'print' => 'no',
+ ) );
+
+ $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( $_REQUEST['_wpnonce'], 'download-shipment-label' ) ) {
+ $shipment_id = absint( $_GET['shipment_id'] );
+ $has_permission = current_user_can( 'edit_shop_orders' );
+
+ $args = self::parse_args( array(
+ 'force' => wc_string_to_bool( isset( $_GET['force'] ) ? wc_clean( $_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 Generic.PHP.NoSilencedErrors.Discouraged
+ }
+ } else {
+ @ob_end_clean(); // phpcs:ignore Generic.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 Generic.PHP.NoSilencedErrors.Discouraged
+
+ if ( ! $file_size ) {
+ return;
+ }
+
+ header( 'Content-Length: ' . $file_size );
+
+ @readfile( $file_path );
+ 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..64417d2e7
--- /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__, func_get_args() );
+ 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..13d28891b
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Labels/Label.php
@@ -0,0 +1,833 @@
+ 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_{$this->get_shipping_provider()}_{$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 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() ) );
+ }
+
+ 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 sizeof( $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 ) && in_array( $service, $available_services ) ) {
+ $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 ) ) {
+ $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() );
+ }
+
+ 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 apply_filters( "{$this->get_hook_prefix()}download_url", $download_url, $this );
+ }
+
+ /**
+ * @return \WP_Error|true
+ */
+ public function fetch() {
+ $result = new \WP_Error( '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 = [
+ 'name' => $this->get_filename( $file_type ),
+ 'type' => 'application/pdf',
+ 'tmp_name' => $temp_file,
+ 'error' => 0,
+ 'size' => filesize( $temp_file ),
+ ];
+
+ $overrides = [
+ '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 = sizeof( $item_weights );
+
+ /**
+ * Discrepancies detected between item weights an total shipment weight.
+ * Try to distribute the mismatch between items.
+ */
+ if ( $item_total_weight != $total_weight ) {
+ $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(),
+ '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..46a815c44
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Labels/Query.php
@@ -0,0 +1,489 @@
+ 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 );
+ } else {
+ $this->results = $wpdb->get_col( $this->request );
+ }
+
+ if ( isset( $qv['count_total'] ) && $qv['count_total'] ) {
+ $found_labels_query = 'SELECT FOUND_ROWS()';
+ $this->total_labels = (int) $wpdb->get_var( $found_labels_query );
+ }
+ }
+
+ 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'] );
+ }
+
+ // Shipping provider
+ if ( isset( $this->args['shipping_provider'] ) ) {
+ $this->query_where .= $wpdb->prepare( " AND label_shipping_provider IN ('%s')", $this->args['shipping_provider'] );
+ }
+
+ // Shipping provider
+ if ( isset( $this->args['product_id'] ) ) {
+ $this->query_where .= $wpdb->prepare( " AND label_product_id IN ('%s')", $this->args['product_id'] );
+ }
+
+ // 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 );
+ } else {
+ $searches[] = $wpdb->prepare( "$col LIKE %s", $like );
+ }
+ }
+
+ 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' ) ) ) {
+ $_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..40ae4315f
--- /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() );
+ }
+
+ 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..33a99fcbe
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Order.php
@@ -0,0 +1,947 @@
+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 get_shipping_status() {
+ $status = 'not-shipped';
+ $shipments = $this->get_simple_shipments();
+
+ if ( ! empty( $shipments ) ) {
+ foreach( $shipments as $shipment ) {
+
+ if ( $shipment->is_shipped() ) {
+ $status = 'partially-shipped';
+ break;
+ }
+ }
+ }
+
+ if ( ! $this->needs_shipping( array( 'sent_only' => true ) ) ) {
+ $status = 'shipped';
+ }
+
+ return $status;
+ }
+
+ 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
+ */
+ public function calculate_shipment_additional_total( $shipment ) {
+ $fees_total = 0;
+
+ foreach ( $this->get_order()->get_fees() as $item ) {
+ $fees_total += ( (float) $item->get_total() + (float) $item->get_total_tax() );
+ }
+
+ $additional_total = $fees_total + $this->get_order()->get_shipping_total() + $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 ( $additional_total < 0 ) {
+ $additional_total = 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 );
+ }
+
+ 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 ) {}
+ }
+ }
+
+ 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 ) ) {
+ $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;
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/PDFMerger.php b/packages/woocommerce-germanized-shipments/src/PDFMerger.php
new file mode 100644
index 000000000..e8543b265
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/PDFMerger.php
@@ -0,0 +1,143 @@
+_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 = [], $width = 210 ) {
+ if ( file_exists( $filename ) ) {
+ $pageCount = $this->_pdf->setSourceFile( $filename );
+
+ for ( $i = 1; $i <= $pageCount; $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 $pageNumber
+ *
+ * @throws PdfReaderException
+ */
+ private function _addPage( $pageNumber, $width = 210 ) {
+ $pageId = $this->_pdf->importPage( $pageNumber );
+ $size = $this->_pdf->getTemplateSize( $pageId );
+
+ $orientation = isset( $size['orientation'] ) ? $size['orientation'] : '';
+
+ $this->_pdf->addPage( $orientation, $size );
+
+ if ( ! isset( $size['width'] ) || empty( $size['width'] ) ) {
+ $this->_pdf->useImportedPage( $pageId, 0, 0, $width, null, true );
+ } else {
+ $this->_pdf->useImportedPage( $pageId );
+ }
+ }
+
+
+ /**
+ * Check if a specific page should be merged.
+ * If pages are empty, all pages will be merged
+ *
+ * @return bool
+ */
+ private function _isPageInRange( $pageNumber, $pages = [] ) {
+ if ( empty( $pages ) ) {
+ return true;
+ }
+
+ foreach ( $pages as $range ) {
+ if ( in_array( $pageNumber, $this->_getRange( $range ) ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Get range by given value
+ *
+ * @param mixed $value
+ *
+ * @return array
+ */
+ private function _getRange( $value = null ) {
+ $value = preg_replace( '/[^0-9\-.]/is', '', $value );
+
+ if ( $value == '' ) {
+ return false;
+ }
+
+ $value = explode( '-', $value );
+ if ( count( $value ) == 1 ) {
+ return $value;
+ }
+
+ return range( $value[0] > $value[1] ? $value[1] : $value[0], $value[0] > $value[1] ? $value[0] : $value[1] );
+
+ }
+
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/PDFSplitter.php b/packages/woocommerce-germanized-shipments/src/PDFSplitter.php
new file mode 100644
index 000000000..8f8152810
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/PDFSplitter.php
@@ -0,0 +1,185 @@
+_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 ) {
+
+ } catch( Exception $e ) {}
+ }
+
+ 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 = [] ) {
+ if (file_exists($filename))
+ {
+ $pageCount = $this->_pdf->setSourceFile($filename);
+ for ($i = 1; $i <= $pageCount; $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 $pageNumber
+ * @throws PdfReaderException
+ */
+ private function _addPage($pageNumber)
+ {
+ $pageId = $this->_pdf->importPage($pageNumber);
+ $this->_pdf->addPage();
+ $this->_pdf->useImportedPage($pageId);
+ }
+
+
+ /**
+ * Check if a specific page should be merged.
+ * If pages are empty, all pages will be merged
+ *
+ * @return bool
+ */
+ private function _isPageInRange($pageNumber, $pages = [])
+ {
+ if (empty($pages)) {
+ return true;
+ }
+
+ foreach ($pages as $range) {
+ if (in_array($pageNumber, $this->_getRange($range))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Get range by given value
+ *
+ * @param mixed $value
+ * @return array
+ */
+ private function _getRange($value = null)
+ {
+ $value = preg_replace('/[^0-9\-.]/is', '', $value);
+
+ if ($value == '') {
+ return false;
+ }
+
+ $value = explode('-', $value);
+ if (count($value) == 1) {
+ return $value;
+ }
+
+ return range($value[0] > $value[1] ? $value[1] : $value[0], $value[0] > $value[1] ? $value[0] : $value[1]);
+
+ }
+
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/Package.php b/packages/woocommerce-germanized-shipments/src/Package.php
new file mode 100644
index 000000000..465e59a72
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Package.php
@@ -0,0 +1,692 @@
+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 set_method_filters( $methods ) {
+ foreach ( $methods as $method => $class ) {
+ 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 ) {
+ $shipping_provider_method = new Method( $method );
+ }
+ }
+
+ 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 ) ) {
+ $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' );
+ }
+ }
+
+ /**
+ * 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 ) );
+ }
+
+ 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();
+
+ 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'];
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/Packaging.php b/packages/woocommerce-germanized-shipments/src/Packaging.php
new file mode 100644
index 000000000..ce8235f89
--- /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/PackagingFactory.php b/packages/woocommerce-germanized-shipments/src/PackagingFactory.php
new file mode 100644
index 000000000..b6f0ccaa2
--- /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..1ab563751
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Packing/Helper.php
@@ -0,0 +1,26 @@
+get_id() ] = new PackagingBox( $packaging );
+ }
+ }
+
+ if ( $id ) {
+ return array_key_exists( $id, self::$packaging ) ? self::$packaging[ $id ] : false;
+ }
+
+ return self::$packaging;
+ }
+}
\ No newline at end of file
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..530453ac5
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Packing/OrderItem.php
@@ -0,0 +1,116 @@
+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..fabe711ed
--- /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;
+ }
+}
\ No newline at end of file
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..ad1ea87cb
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Packing/ShipmentItem.php
@@ -0,0 +1,110 @@
+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..8110b013c
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Product.php
@@ -0,0 +1,129 @@
+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;
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/ReturnReason.php b/packages/woocommerce-germanized-shipments/src/ReturnReason.php
new file mode 100644
index 000000000..f22456416
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ReturnReason.php
@@ -0,0 +1,43 @@
+ '',
+ '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'] );
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/ReturnShipment.php b/packages/woocommerce-germanized-shipments/src/ReturnShipment.php
new file mode 100644
index 000000000..82dd0634a
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ReturnShipment.php
@@ -0,0 +1,452 @@
+ 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() ) );
+
+ $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;
+ }
+ }
+
+ $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..34db720be
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Shipment.php
@@ -0,0 +1,2705 @@
+ 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' );
+ }
+
+ 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++ ) {
+ $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;
+ }
+
+ /**
+ * 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() ) );
+ }
+
+ /**
+ * 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 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 ) : '';
+ }
+
+ 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 ) {
+ $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 agains.
+ * @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() {
+ return array(
+ 'length' => $this->get_length(),
+ 'width' => $this->get_width(),
+ 'height' => $this->get_height(),
+ );
+ }
+
+ /**
+ * 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( current_time( 'timestamp', true ) );
+ }
+ }
+ }
+
+ /**
+ * 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_available_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 ( $packaging_id == $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;
+ }
+
+ /**
+ * 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 ) {
+ $this->packaging = wc_gzd_get_packaging( $this->get_packaging_id() );
+ }
+
+ return $this->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 );
+ }
+
+ 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 );
+ }
+
+ public function create_label( $props = false ) {
+ $hook_prefix = $this->get_general_hook_prefix();
+ $provider_name = '';
+ $error = new WP_Error();
+
+ /**
+ * 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 ) ) {
+ return $result;
+ }
+ } 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 ) ) {
+ 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();
+ }
+
+ 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 ) {
+ $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/ShipmentFactory.php b/packages/woocommerce-germanized-shipments/src/ShipmentFactory.php
new file mode 100644
index 000000000..5c89d8a76
--- /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__, func_get_args() );
+ 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..3b7cc0055
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ShipmentItem.php
@@ -0,0 +1,597 @@
+ 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' ) {
+
+ if ( 'view' === $context && ( $item = $this->get_order_item() ) ) {
+ return $item->get_name();
+ }
+
+ return $this->get_prop( 'name', $context );
+ }
+
+ 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() {
+ return array(
+ 'length' => $this->get_length(),
+ 'width' => $this->get_width(),
+ 'height' => $this->get_height(),
+ );
+ }
+
+ 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..107de9f95
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ShipmentQuery.php
@@ -0,0 +1,532 @@
+ array_keys( wc_gzd_get_shipment_statuses() ),
+ 'limit' => 10,
+ 'order_id' => '',
+ 'parent_id' => '',
+ 'type' => 'simple',
+ 'country' => '',
+ 'tracking_id' => '',
+ 'order' => 'DESC',
+ 'orderby' => 'date_created',
+ '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;
+ }
+
+ /**
+ * 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 );
+ } else {
+ $this->results = $wpdb->get_col( $this->request );
+ }
+
+ if ( isset( $qv['count_total'] ) && $qv['count_total'] ) {
+ $found_shipments_query = 'SELECT FOUND_ROWS()';
+ $this->total_shipments = (int) $wpdb->get_var( $found_shipments_query );
+ }
+ }
+
+ 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['parent_id'] ) ) {
+ $this->args['parent_id'] = absint( $this->args['parent_id'] );
+ }
+
+ 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() ) ) {
+ $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'] );
+ }
+
+ // tracking id
+ if ( isset( $this->args['tracking_id'] ) ) {
+ $this->query_where .= $wpdb->prepare( " AND shipment_tracking_id IN ('%s')", $this->args['tracking_id'] );
+ }
+
+ // parent id
+ if ( isset( $this->args['parent_id'] ) ) {
+ $this->query_where .= $wpdb->prepare( " AND shipment_parent_id = %d", $this->args['parent_id'] );
+ }
+
+ // country
+ if ( isset( $this->args['country'] ) ) {
+ $this->query_where .= $wpdb->prepare( " AND shipment_country IN ('%s')", $this->args['country'] );
+ }
+
+ // 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' );
+ } 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( "$col = %s", $string );
+ } else {
+ $searches[] = $wpdb->prepare( "$col LIKE %s", $like );
+ }
+ }
+
+ 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' ) ) ) {
+ $_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..be8184518
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ShipmentReturnItem.php
@@ -0,0 +1,31 @@
+ '',
+ );
+
+ 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..a8102ad01
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Auto.php
@@ -0,0 +1,542 @@
+ '',
+ '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 __construct( $data = 0 ) {
+ parent::__construct( $data );
+ }
+
+ 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' => $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' => $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 \WP_Error|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
+ */
+ 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 ) ) ) {
+ if ( ! in_array( $new_key, $props['services'] ) ) {
+ $props['services'][] = $new_key;
+ }
+ unset( $props[ $key ] );
+ } else {
+ if ( ( $service_key = array_search( $new_key, $props['services'] ) ) !== 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 ) ) {
+ return $result;
+ } else {
+ do_action( "{$this->get_general_hook_prefix()}created_label", $label, $this );
+
+ return $label->save();
+ }
+ }
+
+ return new \WP_Error( '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 );
+}
\ No newline at end of file
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..6ce0c3f23
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Helper.php
@@ -0,0 +1,202 @@
+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() {
+ $this->shipping_providers = array();
+
+ // Unique provider name => provider class name.
+ $shipping_providers = array_merge( $this->get_shipping_provider_class_names(), WC_Data_Store::load( 'shipping-provider' )->get_shipping_providers() );
+
+ // 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();
+ }
+
+ 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 );
+ }
+}
\ No newline at end of file
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..a7af062cb
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/Method.php
@@ -0,0 +1,400 @@
+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;
+ }
+ }
+
+ if ( ! is_numeric( $id ) ) {
+ $expl = explode( ':', $id );
+ $instance_id = ( ( ! empty( $expl ) && sizeof( $expl ) > 1 ) ? $expl[1] : 0 );
+ $id = ( ( ! empty( $expl ) && sizeof( $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' ) && $this->method->supports( 'instance-settings-modal' ) ) ? true : false;
+
+ return apply_filters( 'woocommerce_gzd_shipping_provider_method_supports_instance_settings', $supports_settings, $this );
+ }
+ }
+
+ public function is_placeholder() {
+ return $this->is_placeholder === true;
+ }
+
+ /**
+ * 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' ) ) ) {
+ $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;
+ }
+}
\ No newline at end of file
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..2dc1ebf4b
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/ShippingProvider/MethodPlaceholder.php
@@ -0,0 +1,24 @@
+ true,
+ 'title' => '',
+ 'name' => '',
+ 'description' => '',
+ '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 $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 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_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' ) ) || ! 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 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 );
+ }
+
+ protected function set_prop( $prop, $value ) {
+ parent::set_prop( $prop, $value );
+ }
+
+ /**
+ * @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;
+
+ if ( $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' ) ) {
+ 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 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 ) {}
+ }
+
+ 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' ) . '',
+ '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 : false,
+ 'type' => '',
+ 'id' => '',
+ 'value' => '',
+ 'title_method' => '',
+ 'title' => ''
+ ) );
+
+ 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' ) ) && false !== $setting['allow_override'] ) {
+ $include = true;
+ } elseif( in_array( $setting['type'], array( 'title', 'sectionend' ) ) ) {
+ $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'];
+
+ 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' ), $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;
+ }
+
+ public function save() {
+ return parent::save();
+ }
+
+ /**
+ * @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 \WP_Error( 'shipping-provider', _x( 'This shipping provider does not support creating labels.', 'shipments', 'woocommerce-germanized' ) );
+
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/SimpleShipment.php b/packages/woocommerce-germanized-shipments/src/SimpleShipment.php
new file mode 100644
index 000000000..f8357d321
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/SimpleShipment.php
@@ -0,0 +1,364 @@
+ 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() ) );
+
+ /**
+ * 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..77838bfef
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/Validation.php
@@ -0,0 +1,222 @@
+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 ( 'shipped' === $shipment_order->get_shipping_status() ) {
+ /**
+ * 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', current_time( 'timestamp', true ) );
+ $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 ( self::$current_refund_parent_order !== false ) {
+
+ 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 ) {}
+ }
+
+ 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 && is_ajax() && isset( $_REQUEST['action'] ) && isset( $_REQUEST['order_id'] ) && strpos( $_REQUEST['action'], 'woocommerce_' ) !== false ) {
+ $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();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/src/WPMLHelper.php b/packages/woocommerce-germanized-shipments/src/WPMLHelper.php
new file mode 100644
index 000000000..3f8cb95fe
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/src/WPMLHelper.php
@@ -0,0 +1,142 @@
+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;
+ }
+}
\ No newline at end of file
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..5dcd06ebe
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/emails/admin-new-return-shipment-request.php
@@ -0,0 +1,60 @@
+
+
+
+ get_formatted_sender_full_name() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+
+
+
+ get_billing_first_name() ); ?>
+
+
+ get_order_number() ); ?>
+
+
+
+
+
+
+
+
+
+
+ get_billing_first_name() ); ?>
+
+
+
+
+
+
+
+ get_billing_first_name() ); ?>
+
+
+
+
+
+
+
+ get_billing_first_name() ); ?>
+
+
+
+
+
+
+
+
+ $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..753efff2d
--- /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..6581668aa
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-details.php
@@ -0,0 +1,84 @@
+
+
+
+ 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( '[%s #%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..7cf680f65
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/emails/email-shipment-items.php
@@ -0,0 +1,113 @@
+ $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..e6ce55afe
--- /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..8d66b0e99
--- /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";
+
+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' ), $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' ) ) );
\ No newline at end of file
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..23c84abbb
--- /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";
+
+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..5d650156d
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-return-shipment.php
@@ -0,0 +1,48 @@
+get_billing_first_name() ) ) . "\n\n";
+
+
+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..4bfd87496
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/customer-shipment.php
@@ -0,0 +1,49 @@
+get_billing_first_name() ) ) . "\n\n";
+
+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..72ac48125
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-order-shipments.php
@@ -0,0 +1,47 @@
+ $shipment ) {
+ $count++;
+
+ echo "\n";
+
+ if ( sizeof( $shipments ) > 1 ) {
+ echo sprintf( esc_html_x( 'Shipment %d of %d', 'shipments', 'woocommerce-germanized' ), $count, sizeof( $shipments ) ) . "\n\n";
+ }
+
+ if ( $shipment->get_est_delivery_date() ) {
+ echo esc_html( _x( 'Estimated date:', 'shipments', 'woocommerce-germanized' ) ) . ' ' . 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..4856f1d46
--- /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 );
+}
\ No newline at end of file
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..257185a46
--- /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"; // WPCS: XSS ok.
\ No newline at end of file
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..589278554
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/emails/plain/email-shipment-details.php
@@ -0,0 +1,43 @@
+get_type() ) ) : sprintf( _x( '[%s #%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 );
\ No newline at end of file
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..ab14f5d3d
--- /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 apply_filters( 'woocommerce_gzd_shipment_item_name', $item->get_name(), $item, false );
+
+ if ( $show_sku && $sku ) {
+ echo ' (#' . $sku . ')';
+ }
+
+ /* This filter is documented in templates/emails/email-shipment-items.php */
+ echo ' X ' . 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..3ae94287d
--- /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' ) ) . ' ' . 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..f9175f69e
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/global/empty.php
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/packages/woocommerce-germanized-shipments/templates/global/form-return-request.php b/packages/woocommerce-germanized-shipments/templates/global/form-return-request.php
new file mode 100644
index 000000000..f305ea694
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/global/form-return-request.php
@@ -0,0 +1,51 @@
+
+
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..8209c82d1
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/myaccount/add-return-shipment.php
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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..c5eca7605
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/myaccount/order-shipments.php
@@ -0,0 +1,45 @@
+
+
+
+
+
+ '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..bc3de1a79
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/myaccount/shipments.php
@@ -0,0 +1,112 @@
+
+
+
+
+
+ $column_name ) : ?>
+
+
+
+
+
+
+ get_item_count();
+ ?>
+
+ get_type() ) as $column_id => $column_name ) : ?>
+
+
+
+
+
+
+ get_type() ), $shipment->get_shipment_number() ) ); ?>
+
+
+
+ get_date_created() ) ); ?>
+
+
+ get_status() ) ); ?>
+
+ has_tracking() && ! $shipment->has_status( 'delivered' ) ) : ?>
+
+
+
+
+
+ $action ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride.OverrideProhibited
+ echo '' . esc_html( $action['name'] ) . ' ';
+ }
+ }
+ ?>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..b10ce3268
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/shipment/add-return-shipment-item.php
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+ 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 ', $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_order_item() ) as $reason ) : ?>
+ get_reason(); ?>
+
+
+
+
+
+ 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..4a722ad0d
--- /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() ) : ?>
+ 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..99f795814
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details-item.php
@@ -0,0 +1,92 @@
+
+
+
+
+ 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 ', $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..2b252e739
--- /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..fbb86784c
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/templates/shipment/shipment-details.php
@@ -0,0 +1,121 @@
+get_order();
+$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 ) );
+}
\ No newline at end of file
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..edbbc6975
--- /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..8a4ffa620
--- /dev/null
+++ b/packages/woocommerce-germanized-shipments/woocommerce-germanized-shipments.php
@@ -0,0 +1,72 @@
+
+
+
+ composer install',
+ '' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '
'
+ );
+ ?>
+
+
+ 0 ) {
+ if ( $.inArray( elementId, exclude_hide_experts ) == -1 ) {
+ showElement = false;
+ }
+ }
+ }
+
+ if ( showElement ) {
+ $( this ).parents( 'tr' ).show();
+ } else {
+ $( this ).parents( 'tr' ).hide();
+ }
+ });
+ },
+
+ onSidebarTitelChange: function() {
+ var $next = $( this ).nextAll( 'table.form-table:first' );
+ $next.find( 'tr:first' ).trigger( 'click' );
+ },
+
+ onSidebarChange: function() {
+ var $sidebar_elem = $( this ).find( '[data-sidebar]' ),
+ $table = $( this ).parents( '.form-table' ),
+ $current_sidebar = $( '.wc-ts-sidebar-active' ),
+ $sidebar = $current_sidebar;
+
+ if ( $sidebar_elem.length <= 0 ) {
+ if ( $table.find( '[data-sidebar]' ).length > 0 ) {
+ $sidebar_elem = $table.find( '[data-sidebar]:first' );
+ }
+ }
+
+ if ( $sidebar_elem.length <= 0 ) {
+ $sidebar = $( '#wc-ts-sidebar-default' );
+ } else {
+ $sidebar = $( '#' + $sidebar_elem.data( 'sidebar' ) );
+ }
+
+ $current_sidebar.removeClass( 'wc-ts-sidebar-active' );
+ $sidebar.addClass( 'wc-ts-sidebar-active' );
+ },
+
+ getSettingsWrapper: function() {
+ var self = trusted_shops.admin;
+ var prefix = self.optionPrefix.replace( '_', '-' );
+
+ return $( '.wc-' + prefix + 'admin-settings' );
+ },
+
+ addNotice: function( type, texts ) {
+ var self = trusted_shops.admin;
+
+ self.getSettingsWrapper().find( '#message' ).remove();
+ self.getSettingsWrapper().prepend( '' );
+
+ $( 'html, body' ).animate( {
+ scrollTop: ( self.getSettingsWrapper().offset().top - 100 )
+ }, 1000 );
+ },
+
+ validate: function( $elem ) {
+ var self = trusted_shops.admin,
+ isValid = true,
+ id = $elem.attr( 'id' ),
+ isCode = id.substr( id.length - 5 ) === '_code',
+ value = $elem.val();
+
+ if ( $elem.data( 'validate' ) ) {
+ var type = $elem.data( 'validate' );
+
+ if ( 'integer' === type ) {
+ value = parseInt( value );
+
+ if ( isNaN( value ) ) {
+ isValid = false;
+ }
+ }
+ } else if( self.isExpertMode() && isCode ) {
+ if ( '' === value ) {
+ isValid = false;
+ }
+ }
+
+ return isValid;
+ },
+
+ onSaveForm: function() {
+ var self = trusted_shops.admin;
+ var doSubmit = true;
+
+ $( 'textarea, input, select' ).removeClass( 'wc-ts-has-error' );
+
+ $( 'textarea:visible, input:visible, select:visible' ).each( function() {
+
+ var id = $( this ).attr( 'id' ),
+ isCode = id.substr( id.length - 5 ) === '_code',
+ $td = $( this ).parents( 'tr' ).find( 'td' );
+
+ $td.find( '.wc-ts-error' ).remove();
+
+ if ( ! self.validate( $( this ) ) ) {
+ $( this ).addClass( 'wc-ts-has-error' );
+
+ if ( isCode ) {
+ var message = self.params.i18n_error_mandatory;
+ } else {
+ var message = $( this ).data( 'validate-msg' );
+ }
+
+ $td.append( '' + message + ' ' );
+
+ doSubmit = false;
+ }
+ });
+
+ if ( ! doSubmit ) {
+ $( 'html, body' ).animate( {
+ scrollTop: ( self.getSettingsWrapper().find( '.wc-ts-has-error:first' ).offset().top - 100 )
+ }, 1000 );
+ }
+
+ return doSubmit;
+ },
+
+ isExpertMode: function() {
+ var self = trusted_shops.admin;
+ return $( '#woocommerce_' + this.optionPrefix + 'trusted_shops_integration_mode' ).val() === 'expert';
+ },
+
+ onClickExport: function() {
+ var self = trusted_shops.admin;
+ var href_org = $( this ).data( 'href-org' );
+
+ $( this ).attr( 'href', href_org + '&interval=' + $( '#woocommerce_' + self.optionPrefix + 'trusted_shops_review_collector' ).val() + '&days=' + $( '#woocommerce_' + self.params.option_prefix + 'trusted_shops_review_collector_days_to_send' ).val() );
+ }
+ };
+
+ $( document ).ready( function() {
+ trusted_shops.admin.init();
+ });
+
+})( jQuery, wp, window.trusted_shops );
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/assets/js/admin.min.js b/packages/woocommerce-trusted-shops/assets/js/admin.min.js
new file mode 100644
index 000000000..12f36766d
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/assets/js/admin.min.js
@@ -0,0 +1 @@
+window.trusted_shops=window.trusted_shops||{},function(s,a){a.admin={params:{},optionPrefix:"",init:function(){this.params=trusted_shops_params,this.optionPrefix=this.params.option_prefix;var e=this;s(document).on("click","a.woocommerce-ts-input-toggle-trigger",this.onInputToogleClick),s(document).on("change","#woocommerce_"+this.optionPrefix+"trusted_shops_integration_mode",this.onChangeIntegrationMode),s(document).on("change",":input[id$=_enable]",this.onChangeEnable),s(document).on("change","#woocommerce_"+this.optionPrefix+"trusted_shops_reviews_enable",this.onChangeEnableReviews),s(document).find("#woocommerce_"+this.optionPrefix+"trusted_shops_integration_mode").trigger("change"),s(document).find(":input[id$=_enable]").trigger("change"),s(document).on("click","#wc-gzd-trusted-shops-export",this.onClickExport),s(document).on("click","table.form-table tr",this.onSidebarChange),s(":data(sidebar)").each(function(){s(this).parents("tr").on("click",e.onSidebarChange)}),s(document).on("click",'h2, div[id$="options-description"]',this.onSidebarTitelChange),s(document).on("submit","#mainform",this.onSaveForm)},onInputToogleClick:function(){var e=s(this).find("span.woocommerce-ts-input-toggle"),t=e.parents("tr").find("input[type=checkbox]"),o=e.hasClass("woocommerce-input-toggle--enabled");return e.removeClass("woocommerce-input-toggle--enabled"),e.removeClass("woocommerce-input-toggle--disabled"),o?(t.prop("checked",!1),e.addClass("woocommerce-input-toggle--disabled")):(t.prop("checked",!0),e.addClass("woocommerce-input-toggle--enabled")),t.trigger("change"),!1},onChangeEnableReviews:function(){var e=a.admin;s(this).is(":checked")?(s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_sticker_enable").parents("tr").show(),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_widget_enable").parents("tr").show(),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_brand_attribute").parents("tr").show()):(s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_sticker_enable").prop("checked",!1),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_widget_enable").prop("checked",!1),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_sticker_enable").parents("tr").hide(),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_widget_enable").parents("tr").hide(),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_brand_attribute").parents("tr").hide()),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_sticker_enable").trigger("change"),s(document).find("#woocommerce_"+e.optionPrefix+"trusted_shops_product_widget_enable").trigger("change")},onChangeIntegrationMode:function(){a.admin;s(document).find(":input[id$=_enable]").trigger("change")},onChangeEnable:function(){self=a.admin,self.showHideGroupElements(s(this))},showHideGroupElements:function(e){var t=e.attr("id"),o=a.admin,t=t.replace("woocommerce_"+o.optionPrefix+"trusted_shops_",""),i=t.substr(0,t.length-7),t=s(":input[id^=woocommerce_"+o.optionPrefix+"trusted_shops_"+i+"_], th[id^=woocommerce_"+o.optionPrefix+"trusted_shops_"+i+"_]"),r=!1,n=["woocommerce_"+o.optionPrefix+"trusted_shops_rich_snippets_category","woocommerce_"+o.optionPrefix+"trusted_shops_rich_snippets_product","woocommerce_"+o.optionPrefix+"trusted_shops_rich_snippets_home","woocommerce_"+o.optionPrefix+"trusted_shops_product_sticker_tab_text"];e.is(":checked")&&(r=!0),t.each(function(){var e=s(this).attr("id"),t=r;"woocommerce_"+o.optionPrefix+"trusted_shops_"+i+"_enable"!==e&&("woocommerce_"+o.optionPrefix+"trusted_shops_"+i+"_code"===e||"woocommerce_"+o.optionPrefix+"trusted_shops_"+i+"_selector"===e?!o.isExpertMode()&&t&&(t=!1):o.isExpertMode()&&0'+t.join(" ")+"
"),s("html, body").animate({scrollTop:o.getSettingsWrapper().offset().top-100},1e3)},validate:function(e){var t=a.admin,o=!0,i=e.attr("id"),r="_code"===i.substr(i.length-5),i=e.val();return e.data("validate")?"integer"===e.data("validate")&&(i=parseInt(i),isNaN(i)&&(o=!1)):t.isExpertMode()&&r&&""===i&&(o=!1),o},onSaveForm:function(){var o=a.admin,i=!0;return s("textarea, input, select").removeClass("wc-ts-has-error"),s("textarea:visible, input:visible, select:visible").each(function(){var e=s(this).attr("id"),t="_code"===e.substr(e.length-5),e=s(this).parents("tr").find("td");e.find(".wc-ts-error").remove(),o.validate(s(this))||(s(this).addClass("wc-ts-has-error"),t=t?o.params.i18n_error_mandatory:s(this).data("validate-msg"),e.append(''+t+" "),i=!1)}),i||s("html, body").animate({scrollTop:o.getSettingsWrapper().find(".wc-ts-has-error:first").offset().top-100},1e3),i},isExpertMode:function(){a.admin;return"expert"===s("#woocommerce_"+this.optionPrefix+"trusted_shops_integration_mode").val()},onClickExport:function(){var e=a.admin,t=s(this).data("href-org");s(this).attr("href",t+"&interval="+s("#woocommerce_"+e.optionPrefix+"trusted_shops_review_collector").val()+"&days="+s("#woocommerce_"+e.params.option_prefix+"trusted_shops_review_collector_days_to_send").val())}},s(document).ready(function(){a.admin.init()})}(jQuery,(wp,window.trusted_shops));
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/composer.json b/packages/woocommerce-trusted-shops/composer.json
new file mode 100644
index 000000000..9deecae82
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "vendidero/woocommerce-trusted-shops",
+ "description": "Trustbadge Reviews for WooCommerce.",
+ "homepage": "https://github.com/vendidero/woocommerce-trusted-shops",
+ "license": "GPL-3.0-or-later",
+ "type": "wordpress-plugin",
+ "prefer-stable": true,
+ "minimum-stability": "dev",
+ "require": {
+ "automattic/jetpack-autoloader": "^2.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "6.5.14"
+ },
+ "scripts": {
+ "post-install-cmd": [
+ "composer dump-autoload"
+ ],
+ "post-update-cmd": [
+ "composer dump-autoload"
+ ]
+ },
+ "autoload": {
+ "psr-4": {
+ "Vendidero\\TrustedShops\\": "src"
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/composer.lock b/packages/woocommerce-trusted-shops/composer.lock
new file mode 100644
index 000000000..e52722ed2
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/composer.lock
@@ -0,0 +1,1776 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "afaceafbf78e093ec63013e98b62f10d",
+ "packages": [
+ {
+ "name": "automattic/jetpack-autoloader",
+ "version": "v2.10.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Automattic/jetpack-autoloader.git",
+ "reference": "70cb300a7a215ae87c671f600f77093518f87bac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/70cb300a7a215ae87c671f600f77093518f87bac",
+ "reference": "70cb300a7a215ae87c671f600f77093518f87bac",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.1 || ^2.0"
+ },
+ "require-dev": {
+ "automattic/jetpack-changelogger": "^1.2",
+ "yoast/phpunit-polyfills": "0.2.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "autotagger": true,
+ "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin",
+ "mirror-repo": "Automattic/jetpack-autoloader",
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}"
+ },
+ "branch-alias": {
+ "dev-master": "2.10.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/AutoloadGenerator.php"
+ ],
+ "psr-4": {
+ "Automattic\\Jetpack\\Autoloader\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "Creates a custom autoloader for a plugin or theme.",
+ "support": {
+ "source": "https://github.com/Automattic/jetpack-autoloader/tree/v2.10.4"
+ },
+ "time": "2021-08-10T06:44:08+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^8.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/instantiator/issues",
+ "source": "https://github.com/doctrine/instantiator/tree/1.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-10T18:47:58+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.10.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "replace": {
+ "myclabs/deep-copy": "self.version"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-13T09:40:50+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0",
+ "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "phar-io/version": "^1.0.1",
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/master"
+ },
+ "time": "2017-03-05T18:14:27+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df",
+ "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.6 || ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/master"
+ },
+ "time": "2017-03-05T17:38:23+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+ },
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.3",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "account@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
+ },
+ "time": "2020-09-03T19:13:55+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
+ },
+ "time": "2020-09-17T18:55:26+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.10.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "451c3cd1418cf640de218914901e51b064abb093"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
+ "reference": "451c3cd1418cf640de218914901e51b064abb093",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
+ "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
+ "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^2.5 || ^3.2",
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.10.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "support": {
+ "issues": "https://github.com/phpspec/prophecy/issues",
+ "source": "https://github.com/phpspec/prophecy/tree/v1.10.3"
+ },
+ "time": "2020-03-05T15:02:03+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "5.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "c89677919c5dd6d3b3852f230a663118762218ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac",
+ "reference": "c89677919c5dd6d3b3852f230a663118762218ac",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.0",
+ "phpunit/php-file-iterator": "^1.4.2",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-token-stream": "^2.0.1",
+ "sebastian/code-unit-reverse-lookup": "^1.0.1",
+ "sebastian/environment": "^3.0",
+ "sebastian/version": "^2.0.1",
+ "theseer/tokenizer": "^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-xdebug": "^2.5.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/5.3"
+ },
+ "time": "2018-04-06T15:36:58+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "irc": "irc://irc.freenode.net/phpunit",
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5"
+ },
+ "time": "2017-11-27T13:52:08+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
+ },
+ "time": "2015-06-21T13:50:34+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+ "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/master"
+ },
+ "time": "2017-02-26T11:10:40+00:00"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "791198a2c6254db10131eecfe8c06670700904db"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
+ "reference": "791198a2c6254db10131eecfe8c06670700904db",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.2.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
+ "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master"
+ },
+ "abandoned": true,
+ "time": "2017-11-27T05:48:46+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "6.5.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7",
+ "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "myclabs/deep-copy": "^1.6.1",
+ "phar-io/manifest": "^1.0.1",
+ "phar-io/version": "^1.0",
+ "php": "^7.0",
+ "phpspec/prophecy": "^1.7",
+ "phpunit/php-code-coverage": "^5.3",
+ "phpunit/php-file-iterator": "^1.4.3",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-timer": "^1.0.9",
+ "phpunit/phpunit-mock-objects": "^5.0.9",
+ "sebastian/comparator": "^2.1",
+ "sebastian/diff": "^2.0",
+ "sebastian/environment": "^3.1",
+ "sebastian/exporter": "^3.1",
+ "sebastian/global-state": "^2.0",
+ "sebastian/object-enumerator": "^3.0.3",
+ "sebastian/resource-operations": "^1.0",
+ "sebastian/version": "^2.0.1"
+ },
+ "conflict": {
+ "phpdocumentor/reflection-docblock": "3.0.2",
+ "phpunit/dbunit": "<3.0"
+ },
+ "require-dev": {
+ "ext-pdo": "*"
+ },
+ "suggest": {
+ "ext-xdebug": "*",
+ "phpunit/php-invoker": "^1.1"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.5.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/6.5.14"
+ },
+ "time": "2019-02-01T05:22:47+00:00"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "5.0.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f",
+ "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.5",
+ "php": "^7.0",
+ "phpunit/php-text-template": "^1.2.1",
+ "sebastian/exporter": "^3.1"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.5.11"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues",
+ "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/5.0.10"
+ },
+ "abandoned": true,
+ "time": "2018-08-09T05:50:03+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+ "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T08:15:22+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "2.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9",
+ "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0",
+ "sebastian/diff": "^2.0 || ^3.0",
+ "sebastian/exporter": "^3.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/master"
+ },
+ "time": "2018-02-01T13:46:46+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
+ "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "source": "https://github.com/sebastianbergmann/diff/tree/master"
+ },
+ "time": "2017-08-03T08:09:46+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
+ "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/master"
+ },
+ "time": "2017-07-01T08:51:00+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "3.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
+ "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:47:53+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/2.0.0"
+ },
+ "time": "2017-04-27T15:39:26+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "3.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+ "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:40:27+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+ "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:37:18+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
+ "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-30T07:34:24+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/master"
+ },
+ "time": "2015-07-28T20:34:47+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/master"
+ },
+ "time": "2016-10-03T07:35:21+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-02-19T12:13:01+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2021-07-28T10:34:58+00:00"
+ },
+ {
+ "name": "webmozart/assert",
+ "version": "1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
+ "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<0.12.20",
+ "vimeo/psalm": "<4.6.1 || 4.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.13"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.10-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Webmozart\\Assert\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Assertions to validate method input/output with nice error messages.",
+ "keywords": [
+ "assert",
+ "check",
+ "validate"
+ ],
+ "support": {
+ "issues": "https://github.com/webmozarts/assert/issues",
+ "source": "https://github.com/webmozarts/assert/tree/1.10.0"
+ },
+ "time": "2021-03-09T10:59:23+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": [],
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.0.0"
+}
diff --git a/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE.mo b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE.mo
new file mode 100644
index 000000000..06afcb3b9
Binary files /dev/null and b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE.mo differ
diff --git a/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE.po b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE.po
new file mode 100644
index 000000000..56fe16296
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE.po
@@ -0,0 +1,1166 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: WooCommerce Trusted Shops\n"
+"POT-Creation-Date: 2019-10-30 17:53+0100\n"
+"PO-Revision-Date: 2019-10-30 19:17+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: de_DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.4\n"
+"X-Poedit-Basepath: ../..\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
+"_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;"
+"esc_attr_e;esc_html_e;esc_html__\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-SearchPathExcluded-0: node_modules\n"
+"X-Poedit-SearchPathExcluded-1: vendor\n"
+
+#: includes/admin/settings/class-wc-ts-gzd-settings-tab.php:48
+msgctxt "trusted-shops"
+msgid "Setup your Trusted Shops Integration."
+msgstr "Setze deine Trusted Shops Integration auf."
+
+#: includes/admin/settings/class-wc-ts-gzd-settings-tab.php:52
+#: includes/class-wc-ts-settings-handler.php:23
+msgctxt "trusted-shops"
+msgid "Trusted Shops"
+msgstr "Trusted Shops"
+
+#: includes/admin/views/html-notice-dependencies.php:16
+msgctxt "trusted-shops"
+msgid "Dependencies Missing or Outdated"
+msgstr "Wichtige Plugins fehlen oder sind veraltet"
+
+#: includes/admin/views/html-notice-dependencies.php:24
+msgctxt "trusted-shops"
+msgid ""
+"To use WooCommerce Trusted Shops you may at first install the following "
+"plugins:"
+msgstr ""
+"Um WooCommerce Trusted Shops zuverlässig nutzen zu können, musst du erst "
+"folgende Plugins installieren:"
+
+#: includes/admin/views/html-notice-dependencies.php:28
+#, php-format
+msgctxt "trusted-shops"
+msgid "Install %s"
+msgstr "Install %s"
+
+#: includes/admin/views/html-notice-dependencies.php:36
+msgctxt "trusted-shops"
+msgid ""
+"To use WooCommerce Trusted Shops you may at first update the following "
+"plugins to a newer version:"
+msgstr ""
+"Um WooCommerce Trusted Shops zuverlässig nutzen zu können, update bitte "
+"folgende Plugins:"
+
+#: includes/admin/views/html-notice-dependencies.php:40
+#, php-format
+msgctxt "trusted-shops"
+msgid "%s required in at least version %s"
+msgstr "%s wird mindestens in Version %s benötigt"
+
+#: includes/admin/views/html-notice-dependencies.php:49
+msgctxt "trusted-shops"
+msgid "Check for Updates"
+msgstr "nach Updates suchen"
+
+#: includes/admin/views/html-notice-dependencies.php:50
+msgctxt "trusted-shops"
+msgid "or"
+msgstr "oder"
+
+#: includes/admin/views/html-notice-dependencies.php:51
+msgctxt "trusted-shops"
+msgid "Install an older version"
+msgstr "Installiere eine ältere Version"
+
+#: includes/admin/views/html-notice-update.php:14
+msgctxt "trusted-shops"
+msgid ""
+"WooCommerce Trusted Shops Data Update Required – We "
+"just need to update your installation to the latest version"
+msgstr ""
+"WooCommerce Trusted Shops Datenaktualisierung erforderlich "
+"– Wir müssen deine Installation auf die neueste Version updaten"
+
+#: includes/admin/views/html-notice-update.php:15
+msgctxt "trusted-shops"
+msgid "Run the updater"
+msgstr "Update starten"
+
+#: includes/admin/views/html-notice-update.php:19
+msgctxt "trusted-shops"
+msgid ""
+"It is strongly recommended that you backup your database before proceeding. "
+"Are you sure you wish to run the updater now?"
+msgstr ""
+"Du solltest vor einem Update immer ein Backup deiner Datenbank anlegen. Bist "
+"du sicher das Update jetzt zu installieren?"
+
+#: includes/admin/views/html-wpml-notice.php:11
+msgctxt "trusted-shops"
+msgid "WPML Support"
+msgstr "WPML Unterstützung"
+
+#: includes/admin/views/html-wpml-notice.php:14
+msgctxt "trusted-shops"
+msgid ""
+"These settings serve as default settings for all your languages. To adjust "
+"the settings for a certain language, please switch your admin language "
+"through the WPML language switcher and adjust the corresponding settings."
+msgstr ""
+"Diese Einstellungen werden als Standard für alle deine Sprachen verwendet. "
+"Um die Einstellungen für eine spezielle Sprache zu ändern, wechsle bitte zur "
+"entsprechenden Sprache über den WPML Sprachumschalter."
+
+#: includes/admin/views/html-wpml-notice.php:16
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"These settings apply for your %s shop. To adjust settings for another "
+"language, please switch your admin language through the WPML language "
+"switcher."
+msgstr ""
+"Diese Einstellungen werden für deinen %s Shop verwendet. Um die "
+"Einstellungen für eine andere Sprache anzupassen, wechsle bitte die Sprache "
+"über den WPML Sprachumschalter."
+
+#: includes/class-wc-trusted-shops-admin.php:84
+#: includes/class-wc-trusted-shops-admin.php:121
+msgctxt "trusted-shops"
+msgid "GTIN"
+msgstr "GTIN"
+
+#: includes/class-wc-trusted-shops-admin.php:84
+#: includes/class-wc-trusted-shops-admin.php:121
+msgctxt "trusted-shops"
+msgid ""
+"ID that allows your products to be identified worldwide. If you want to "
+"display your Trusted Shops Product Reviews in Google Shopping and paid "
+"Google adverts, Google needs the GTIN."
+msgstr ""
+"Identifikationsnummer, mit der Produkte weltweit eindeutig identifiziert "
+"werden können. Wenn du deine Trusted Shops Produktbewertungen in Google "
+"Shopping und bezahlten Google Produktanzeigen ausspielen möchtest, benötigt "
+"Google die GTIN."
+
+#: includes/class-wc-trusted-shops-admin.php:88
+#: includes/class-wc-trusted-shops-admin.php:122
+msgctxt "trusted-shops"
+msgid "MPN"
+msgstr "MPN"
+
+#: includes/class-wc-trusted-shops-admin.php:88
+#: includes/class-wc-trusted-shops-admin.php:122
+msgctxt "trusted-shops"
+msgid ""
+"If you don't have a GTIN for your products, you can pass the brand name and "
+"the MPN on to Google to use the Trusted Shops Google Integration."
+msgstr ""
+"Wenn deine Produkte keine GTIN haben, kannst du den Markennamen zusammen mit "
+"der MPN übergeben, um die Trusted Shops Google Integration zu nutzen."
+
+#: includes/class-wc-trusted-shops-admin.php:142
+msgctxt "trusted-shops"
+msgid "Brand"
+msgstr "Marke"
+
+#: includes/class-wc-trusted-shops-admin.php:176
+msgctxt "trusted-shops"
+msgid "This field is mandatory"
+msgstr "Dieses Feld ist ein Pflichtfeld"
+
+#: includes/class-wc-trusted-shops-admin.php:183
+msgctxt "trusted-shops"
+msgid "Trusted Shops Options"
+msgstr "Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:190
+msgctxt "trusted-shops"
+msgid "Arial"
+msgstr "Arial"
+
+#: includes/class-wc-trusted-shops-admin.php:191
+msgctxt "trusted-shops"
+msgid "Geneva"
+msgstr "Geneva"
+
+#: includes/class-wc-trusted-shops-admin.php:192
+msgctxt "trusted-shops"
+msgid "Georgia"
+msgstr "Georgia"
+
+#: includes/class-wc-trusted-shops-admin.php:193
+msgctxt "trusted-shops"
+msgid "Helvetica"
+msgstr "Helvetica"
+
+#: includes/class-wc-trusted-shops-admin.php:194
+msgctxt "trusted-shops"
+msgid "Sans-serif"
+msgstr "Sans-serif"
+
+#: includes/class-wc-trusted-shops-admin.php:195
+msgctxt "trusted-shops"
+msgid "Serif"
+msgstr "Serif"
+
+#: includes/class-wc-trusted-shops-admin.php:196
+msgctxt "trusted-shops"
+msgid "Trebuchet MS"
+msgstr "Trebuchet MS"
+
+#: includes/class-wc-trusted-shops-admin.php:197
+msgctxt "trusted-shops"
+msgid "Verdana"
+msgstr "Verdana"
+
+#: includes/class-wc-trusted-shops-admin.php:220
+msgctxt "trusted-shops"
+msgid "Trusted Shops Integration"
+msgstr "Trusted Shops Integration"
+
+#: includes/class-wc-trusted-shops-admin.php:221
+#, php-format
+msgctxt "trusted-shops"
+msgid "Do you need help with integrating your Trustbadge? %s"
+msgstr "Brauchst du Hilfe bei der Einbindung deines Trustbadges? %s"
+
+#: includes/class-wc-trusted-shops-admin.php:221
+msgctxt "trusted-shops"
+msgid "To the step-by-step instructions"
+msgstr "Zur Schritt-für-Schritt Anleitung"
+
+#: includes/class-wc-trusted-shops-admin.php:227
+msgctxt "trusted-shops"
+msgid "Trusted Shops ID"
+msgstr "Trusted Shops ID"
+
+#: includes/class-wc-trusted-shops-admin.php:228
+msgctxt "trusted-shops"
+msgid ""
+"The Trusted Shops ID is a unique identifier for your shop. You can find your "
+"Trusted Shops ID in your My Trusted Shops account."
+msgstr ""
+"Die Trusted Shops ID identifiziert deinen Shop eindeutig bei Trusted Shops. "
+"Du findest deine Trusted Shops ID in deinem My Trusted Shops Account."
+
+#: includes/class-wc-trusted-shops-admin.php:237
+msgctxt "trusted-shops"
+msgid "Edit Mode"
+msgstr "Bearbeitungsmodus"
+
+#: includes/class-wc-trusted-shops-admin.php:239
+#: includes/class-wc-trusted-shops-admin.php:300
+#: includes/class-wc-trusted-shops-admin.php:423
+msgctxt "trusted-shops"
+msgid ""
+"The advanced configuration is for users with programming skills. Here you "
+"can create even more individual settings."
+msgstr ""
+"Der Expertenmodus ist für fortgeschrittene Nutzer mit Programmierkenntnissen "
+"gedacht. Hier kannst du noch mehr individuelle Einstellungen vornehmen."
+
+#: includes/class-wc-trusted-shops-admin.php:243
+msgctxt "trusted-shops"
+msgid "Standard configuration"
+msgstr "Standardmodus"
+
+#: includes/class-wc-trusted-shops-admin.php:244
+msgctxt "trusted-shops"
+msgid "Advanced configuration"
+msgstr "Expertenmodus"
+
+#: includes/class-wc-trusted-shops-admin.php:252
+msgctxt "trusted-shops"
+msgid "Configure your Trustbadge"
+msgstr "Konfiguriere dein Trustbadge"
+
+#: includes/class-wc-trusted-shops-admin.php:258
+msgctxt "trusted-shops"
+msgid "Display Trustbadge"
+msgstr "Trustbadge anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:260
+msgctxt "trusted-shops"
+msgid "Display the Trustbadge on all the pages of your shop."
+msgstr "Zeige das Trustbadge auf allen Seiten deines Shops an."
+
+#: includes/class-wc-trusted-shops-admin.php:267
+msgctxt "trusted-shops"
+msgid "Variant"
+msgstr "Variante"
+
+#: includes/class-wc-trusted-shops-admin.php:269
+msgctxt "trusted-shops"
+msgid "You can display your Trustbadge with or without Review Stars."
+msgstr "Du kannst dein Trustbadge mit oder ohne Bewertungssterne anzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:273
+#: includes/class-wc-trusted-shops-admin.php:763
+msgctxt "trusted-shops"
+msgid "Display Trustbadge with review stars"
+msgstr "Trustbadge mit Bewertungssternen anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:274
+#: includes/class-wc-trusted-shops-admin.php:767
+msgctxt "trusted-shops"
+msgid "Display Trustbadge without review stars"
+msgstr "Trustbadge ohne Bewertungssterne anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:280
+msgctxt "trusted-shops"
+msgid "Vertical Offset"
+msgstr "Vertikaler Abstand"
+
+#: includes/class-wc-trusted-shops-admin.php:281
+msgctxt "trusted-shops"
+msgid ""
+"Choose the distance that the Trustbadge will appear from the bottom-right "
+"corner of the screen."
+msgstr ""
+"Wähle einen Abstand für dein Trustbadge vom unteren rechten Bildschirmrand."
+
+#: includes/class-wc-trusted-shops-admin.php:284
+#: includes/class-wc-trusted-shops-admin.php:492
+#: includes/class-wc-trusted-shops-admin.php:543
+#: includes/class-wc-trusted-shops-admin.php:558
+msgctxt "trusted-shops"
+msgid "px"
+msgstr "px"
+
+#: includes/class-wc-trusted-shops-admin.php:290
+#: includes/class-wc-trusted-shops-admin.php:499
+#: includes/class-wc-trusted-shops-admin.php:549
+#: includes/class-wc-trusted-shops-admin.php:565
+#, php-format
+msgctxt "trusted-shops"
+msgid "Please choose a non-negative number (at least %d)"
+msgstr "Bitte verwende eine nicht-negative Nummer (mindestens %d)"
+
+#: includes/class-wc-trusted-shops-admin.php:296
+msgctxt "trusted-shops"
+msgid "Trustbadge code"
+msgstr "Trustbadge Code"
+
+#: includes/class-wc-trusted-shops-admin.php:308
+msgctxt "trusted-shops"
+msgid "Configure your Shop Reviews"
+msgstr "Konfiguriere deine Shopbewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:314
+msgctxt "trusted-shops"
+msgid "Display Shop Review Sticker"
+msgstr "Shopbewertungssticker anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:315
+msgctxt "trusted-shops"
+msgid ""
+"To display the Shop Review Sticker, you have to assign the widget \"Trusted "
+"Shops Review Sticker\"."
+msgstr ""
+"Um den Shopbewertungssticker anzuzeigen, musst du das Widget „Trusted Shops "
+"Shopbewertungssticker“ zuweisen."
+
+#: includes/class-wc-trusted-shops-admin.php:316
+#, php-format
+msgctxt "trusted-shops"
+msgid "Assign widget %s"
+msgstr "Widget %s zuweisen"
+
+#: includes/class-wc-trusted-shops-admin.php:316
+#: includes/class-wc-trusted-shops-admin.php:588
+#: includes/class-wc-trusted-shops-admin.php:896
+msgctxt "trusted-shops"
+msgid "here"
+msgstr "hier"
+
+#: includes/class-wc-trusted-shops-admin.php:324
+#: includes/class-wc-trusted-shops-admin.php:472
+msgctxt "trusted-shops"
+msgid "Background color"
+msgstr "Hintergrundfarbe"
+
+#: includes/class-wc-trusted-shops-admin.php:325
+msgctxt "trusted-shops"
+msgid "Choose the background color for your Review Sticker."
+msgstr "Wähle die Hintergrundfarbe für deinen Review Sticker."
+
+#: includes/class-wc-trusted-shops-admin.php:332
+msgctxt "trusted-shops"
+msgid "Font"
+msgstr "Schriftart"
+
+#: includes/class-wc-trusted-shops-admin.php:334
+msgctxt "trusted-shops"
+msgid "Choose the font for your Review Sticker."
+msgstr "Wähle die Schriftart für deinen Review Sticker."
+
+#: includes/class-wc-trusted-shops-admin.php:341
+msgctxt "trusted-shops"
+msgid "Number of reviews displayed"
+msgstr "Anzahl Bewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:342
+msgctxt "trusted-shops"
+msgid ""
+"Display x alternating Shop Reviews in your Shop Review Sticker. You can "
+"display between 1 and 5 alternating Shop Reviews."
+msgstr ""
+"Zeige x Shopbewertungen im Wechsel in deinem Shopbewertungssticker an. Es "
+"können mindestens 1 und maximal 5 Bewertungen im Wechsel angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:345
+msgctxt "trusted-shops"
+msgid "Show x alternating reviews"
+msgstr "x Bewertungen im Wechsel zeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:352
+#: includes/class-wc-trusted-shops-admin.php:369
+#, php-format
+msgctxt "trusted-shops"
+msgid "Please choose a non-negative number between %d and %d"
+msgstr "Bitte verwende eine nicht-negative Nummer zwischen %d und %d"
+
+#: includes/class-wc-trusted-shops-admin.php:358
+msgctxt "trusted-shops"
+msgid "Minimum rating displayed"
+msgstr "Angezeigte Mindestnote"
+
+#: includes/class-wc-trusted-shops-admin.php:359
+msgctxt "trusted-shops"
+msgid "Only show Shop Reviews with a minimum rating of x stars. "
+msgstr "Zeige nur Shopbewertungen mit einer Mindestanzahl von x Sternen."
+
+#: includes/class-wc-trusted-shops-admin.php:362
+msgctxt "trusted-shops"
+msgid "Star(s)"
+msgstr "Stern(e)"
+
+#: includes/class-wc-trusted-shops-admin.php:375
+msgctxt "trusted-shops"
+msgid "Sticker code"
+msgstr "Sticker Code"
+
+#: includes/class-wc-trusted-shops-admin.php:379
+#: includes/class-wc-trusted-shops-admin.php:506
+#: includes/class-wc-trusted-shops-admin.php:571
+msgctxt "trusted-shops"
+msgid ""
+"The advanced configuration is for users with programming skills. Here you "
+"can perform even more individual settings."
+msgstr ""
+"Der Expertenmodus ist für fortgeschrittene Nutzer mit Programmierkenntnissen "
+"gedacht. Hier kannst du noch mehr individuelle Einstellungen vornehmen."
+
+#: includes/class-wc-trusted-shops-admin.php:385
+msgctxt "trusted-shops"
+msgid "Google Organic Search"
+msgstr "Organische Google Suche"
+
+#: includes/class-wc-trusted-shops-admin.php:386
+msgctxt "trusted-shops"
+msgid ""
+"Activate this option to give Google the opportunity to show your Shop "
+"Reviews in Google organic search results."
+msgstr ""
+"Aktiviere diese Funktion, um Google die Möglichkeit zu geben, deine "
+"Shopbewertungen in den organischen Google Suchergebnissen anzuzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:387
+msgctxt "trusted-shops"
+msgid ""
+"By activating this option, rich snippets will be integrated in the selected "
+"pages so your shop review stars may be displayed in Google organic search "
+"results. If you use Product Reviews and already activated rich snippets in "
+"expert mode, we recommend integrating rich snippets for Shop Reviews on "
+"category pages only."
+msgstr ""
+"Wenn du diese Option aktivierst, werden Rich Snippets in die ausgewählten "
+"Seiten eingebunden, sodass deine Shopbewertungssterne in den organischen "
+"Google Suchergebnissen angezeigt werden können. Wenn du Produktbewertungen "
+"aktiviert hast und dort im Expertenmodus bereits Rich Snippets aktiviert "
+"hast, empfehlen wir die Ausgabe der Rich Snippets für Shopbewertungen nur "
+"auf der Kategorieseite."
+
+#: includes/class-wc-trusted-shops-admin.php:394
+msgctxt "trusted-shops"
+msgid "Activate rich snippets on"
+msgstr "Rich Snippets aktivieren auf"
+
+#: includes/class-wc-trusted-shops-admin.php:395
+msgctxt "trusted-shops"
+msgid "category pages"
+msgstr "Kategorieseiten"
+
+#: includes/class-wc-trusted-shops-admin.php:403
+msgctxt "trusted-shops"
+msgid "product pages"
+msgstr "Produktdetailseiten"
+
+#: includes/class-wc-trusted-shops-admin.php:411
+msgctxt "trusted-shops"
+msgid "homepage (not recommended)"
+msgstr "Startseite (nicht empfohlen)"
+
+#: includes/class-wc-trusted-shops-admin.php:419
+msgctxt "trusted-shops"
+msgid "Rich snippets code"
+msgstr "Rich Snippets Code"
+
+#: includes/class-wc-trusted-shops-admin.php:431
+msgctxt "trusted-shops"
+msgid "Configure your Product Reviews "
+msgstr "Konfiguriere deine Produktbewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:432
+#, php-format
+msgctxt "trusted-shops"
+msgid "To use Product Reviews, activate them in your %s first."
+msgstr ""
+"Um Produktbewertungen nutzen zu können, schalte diese zuerst in deinem %s "
+"frei."
+
+#: includes/class-wc-trusted-shops-admin.php:432
+msgctxt "trusted-shops"
+msgid "Trusted Shops package"
+msgstr "Trusted Shops Paket"
+
+#: includes/class-wc-trusted-shops-admin.php:438
+msgctxt "trusted-shops"
+msgid "Collect Product Reviews"
+msgstr "Produktbewertungen sammeln"
+
+#: includes/class-wc-trusted-shops-admin.php:439
+msgctxt "trusted-shops"
+msgid ""
+"Show Product Reviews on the product page in a separate tab, just as shown on "
+"the picture on the right."
+msgstr ""
+"Zeige Produktbewertungen auf der Produktseite in einem separaten Reiter, wie "
+"in der Grafik rechts abgebildet."
+
+#: includes/class-wc-trusted-shops-admin.php:447
+msgctxt "trusted-shops"
+msgid "Reviews"
+msgstr "Bewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:448
+#: includes/class-wc-trusted-shops-admin.php:457
+msgctxt "trusted-shops"
+msgid "You can choose a name for the tab with your Product Reviews."
+msgstr ""
+"Du kannst selbst bestimmen, wie der Reiter in dem deine Produktbewertungen "
+"angezeigt werden, heißen soll."
+
+#: includes/class-wc-trusted-shops-admin.php:449
+msgctxt "trusted-shops"
+msgid "Show Product Reviews on the product detail page in an additional tab."
+msgstr "Produktbewertungen auf der Produktseite in separatem Reiter anzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:456
+msgctxt "trusted-shops"
+msgid "Name of Product Reviews tab"
+msgstr "Bezeichnung Reiter"
+
+#: includes/class-wc-trusted-shops-admin.php:460
+msgctxt "trusted-shops"
+msgid "Product reviews"
+msgstr "Produktbewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:464
+msgctxt "trusted-shops"
+msgid "Border color"
+msgstr "Umrandung"
+
+#: includes/class-wc-trusted-shops-admin.php:465
+msgctxt "trusted-shops"
+msgid "Set the color for the frame around your Product Reviews."
+msgstr "Lege die Farbe für den Rahmen um deine Produktbewertungen fest."
+
+#: includes/class-wc-trusted-shops-admin.php:473
+msgctxt "trusted-shops"
+msgid "Set the background color for your Product Reviews."
+msgstr "Lege die Hintergrundfarbe für deine Produktbewertungen fest."
+
+#: includes/class-wc-trusted-shops-admin.php:480
+#: includes/class-wc-trusted-shops-admin.php:530
+msgctxt "trusted-shops"
+msgid "Star color"
+msgstr "Farbe der Sterne"
+
+#: includes/class-wc-trusted-shops-admin.php:481
+msgctxt "trusted-shops"
+msgid "Set the color for the Product Review stars in your Product Reviews tab."
+msgstr ""
+"Lege die Farbe der Sterne fest, die in deinem Produktbewertungs-Reiter "
+"angezeigt werden. "
+
+#: includes/class-wc-trusted-shops-admin.php:488
+#: includes/class-wc-trusted-shops-admin.php:538
+msgctxt "trusted-shops"
+msgid "Star size"
+msgstr "Größe der Sterne"
+
+#: includes/class-wc-trusted-shops-admin.php:493
+msgctxt "trusted-shops"
+msgid "Set the size for the Product Review stars in your Product Reviews tab."
+msgstr ""
+"Lege die Größe der Sterne fest, die in deinem Produktbewertungs-Reiter "
+"angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:504
+msgctxt "trusted-shops"
+msgid "Product Sticker Code"
+msgstr "Produktbewertungen Code"
+
+#: includes/class-wc-trusted-shops-admin.php:513
+#: includes/class-wc-trusted-shops-admin.php:579
+msgctxt "trusted-shops"
+msgid "jQuerySelector"
+msgstr "jQuerySelector"
+
+#: includes/class-wc-trusted-shops-admin.php:514
+msgctxt "trusted-shops"
+msgid ""
+"Please choose where your Product Reviews shall be displayed on the Product "
+"detail page."
+msgstr ""
+"Wähle, wo die Produktbewertungen auf der Produktdetailseite angezeigt werden "
+"sollen."
+
+#: includes/class-wc-trusted-shops-admin.php:521
+msgctxt "trusted-shops"
+msgid "Rating stars"
+msgstr "Bewertungssterne"
+
+#: includes/class-wc-trusted-shops-admin.php:522
+msgctxt "trusted-shops"
+msgid "Show star ratings on the product detail page below your product name."
+msgstr "Bewertungssterne auf der Produktseite unter dem Produktnamen anzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:523
+msgctxt "trusted-shops"
+msgid ""
+"Display Product Review stars on product pages below the product name, just "
+"as shown in the picture on the right."
+msgstr ""
+"Zeige Bewertungssterne auf der Produktseite unter dem Produktnamen, wie in "
+"der Grafik rechts abgebildet."
+
+#: includes/class-wc-trusted-shops-admin.php:532
+msgctxt "trusted-shops"
+msgid ""
+"Set the color for the review stars, that are displayed on the product page, "
+"below your product name."
+msgstr ""
+"Lege die Farbe der Sterne fest, die auf der Produktseite unter deinem "
+"Produktnamen angezeigt werden. "
+
+#: includes/class-wc-trusted-shops-admin.php:540
+msgctxt "trusted-shops"
+msgid ""
+"Set the size for the review stars that are displayed on the product page, "
+"below your product name."
+msgstr ""
+"Lege die Größe der Sterne fest, die auf der Produktseite unter deinem "
+"Produktnamen angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:554
+msgctxt "trusted-shops"
+msgid "Font size"
+msgstr "Schriftgröße"
+
+#: includes/class-wc-trusted-shops-admin.php:556
+msgctxt "trusted-shops"
+msgid "Set the font size for the text that goes with your review stars."
+msgstr "Lege die Schriftgröße für den Text zu deinen Bewertungssternen fest."
+
+#: includes/class-wc-trusted-shops-admin.php:570
+msgctxt "trusted-shops"
+msgid "Product Review Code"
+msgstr "Produktbewertungen Code"
+
+#: includes/class-wc-trusted-shops-admin.php:580
+msgctxt "trusted-shops"
+msgid ""
+"Please choose where your Product Review Stars shall be displayed on the "
+"Product Detail page."
+msgstr ""
+"Wähle, wo die Produktbewertungssterne auf der Produktdetailseite angezeigt "
+"werden sollen."
+
+#: includes/class-wc-trusted-shops-admin.php:587
+msgctxt "trusted-shops"
+msgid "Brand attribute"
+msgstr "Marken-Produktattribut"
+
+#: includes/class-wc-trusted-shops-admin.php:588
+#, php-format
+msgctxt "trusted-shops"
+msgid "Create brand attribute %s"
+msgstr "Marken-Produktattribut %s erstellen"
+
+#: includes/class-wc-trusted-shops-admin.php:589
+msgctxt "trusted-shops"
+msgid ""
+"Brand name of the product. By passing this information on to Google, you "
+"improve your chances of having Google identify your products. Assign your "
+"brand attribute. If your products don't have a GTIN, you can pass on the "
+"brand name and the MPN to use Google Integration."
+msgstr ""
+"Markenname des Produkts. Durch Übergeben dieser Variable verbesserst du die "
+"Möglichkeit deine Produkte von Google eindeutig identifizieren zu lassen. "
+"Weise dein Marken-Produktattribut zu. Wenn deine Produkte keine GTIN haben, "
+"kannst du den Markennamen zusammen mit der MPN übergeben, um die Google "
+"Integration zu nutzen."
+
+#: includes/class-wc-trusted-shops-admin.php:595
+msgctxt "trusted-shops"
+msgid "None"
+msgstr "Keine"
+
+#: includes/class-wc-trusted-shops-admin.php:606
+msgctxt "trusted-shops"
+msgid "Configure your Review Requests"
+msgstr "Konfiguriere deine Bewertungserinnerungen"
+
+#: includes/class-wc-trusted-shops-admin.php:607
+msgctxt "trusted-shops"
+msgid ""
+"7 days after an order has been placed, Trusted Shops automatically sends an "
+"invite to your customers. If you want to set a different time for sending "
+"automatic Review Requests, please activate the option below. If you want to "
+"send review requests with legal certainty, you need your customers' consent "
+"to receive Review Requests. You also have to include an option to "
+"unsubscribe."
+msgstr ""
+"Trusted Shops versendet 7 Tage nach Bestellung automatisch eine "
+"Bewertungserinnerung an deinen Kunden. Wenn du lieber selbst entscheiden "
+"möchtest, wann Bewertungsanfragen versendet werden, aktiviere die "
+"untenstehende Option. Möchtest du rechtssicher Bewertungs-Erinnerungen "
+"verschicken, so benötigst du die ausdrückliche Einwilligung deiner Kunden "
+"zum Empfang von Bewertungsemails. Du musst deinen Kunden zudem eine "
+"Möglichkeit geben, Bewertungserinnerungen wieder abzubestellen."
+
+#: includes/class-wc-trusted-shops-admin.php:613
+msgctxt "trusted-shops"
+msgid "Enable Review Requests"
+msgstr "Bewertungserinnerungen aktivieren"
+
+#: includes/class-wc-trusted-shops-admin.php:622
+msgctxt "trusted-shops"
+msgid "WooCommerce status"
+msgstr "Bestellstatus"
+
+#: includes/class-wc-trusted-shops-admin.php:623
+msgctxt "trusted-shops"
+msgid ""
+"We recommend choosing the order status that you set when your products have "
+"been shipped."
+msgstr ""
+"Wähle hier am besten den Bestellstatus aus WooCommerce aus, den du "
+"einstellst, wenn deine Ware versendet wurde."
+
+#: includes/class-wc-trusted-shops-admin.php:631
+msgctxt "trusted-shops"
+msgid "Days until Review Request"
+msgstr "Tage bis zur Erinnerung"
+
+#: includes/class-wc-trusted-shops-admin.php:632
+msgctxt "trusted-shops"
+msgid ""
+"Set the number of days to wait after an order has reached the order status "
+"you selected above before having a review request sent to your customers."
+msgstr ""
+"Stelle hier ein nach wie vielen Tagen nach Erreichen des oben ausgewählten "
+"Bestellstatus die Bewertungserinnerung an den Käufer versendet werden soll."
+
+#: includes/class-wc-trusted-shops-admin.php:644
+msgctxt "trusted-shops"
+msgid "Permission via checkbox"
+msgstr "Einwilligung per Checkbox"
+
+#: includes/class-wc-trusted-shops-admin.php:645
+msgctxt "trusted-shops"
+msgid ""
+"If the checkbox is activated, only customers who gave their consent will "
+"receive Review Requests."
+msgstr ""
+"Bei aktivierter Checkbox erhalten nur die Kunden eine Bewertungserinnerung, "
+"die ihr Einverständnis gegeben haben."
+
+#: includes/class-wc-trusted-shops-admin.php:649
+msgctxt "trusted-shops"
+msgid "Edit checkbox"
+msgstr "Checkbox anpassen"
+
+#: includes/class-wc-trusted-shops-admin.php:653
+msgctxt "trusted-shops"
+msgid "Unsubscribe via link"
+msgstr "Abmeldung per Link"
+
+#: includes/class-wc-trusted-shops-admin.php:654
+msgctxt "trusted-shops"
+msgid "Allows the customer to unsubscribe from Review Requests."
+msgstr ""
+"Erlaubt es dem Kunden sich von Bewertungserinnerungen per Link abzumelden."
+
+#: includes/class-wc-trusted-shops-admin.php:755
+msgctxt "trusted-shops"
+msgid "How does Trusted Shops make your shop better?"
+msgstr "Wie macht Trusted Shops deinen Shop besser?"
+
+#: includes/class-wc-trusted-shops-admin.php:757
+msgctxt "trusted-shops"
+msgid "Get your account"
+msgstr "Erstelle deinen Account"
+
+#: includes/class-wc-trusted-shops-admin.php:777
+msgctxt "trusted-shops"
+msgid "Product Reviews on the product detail page in an additional tab"
+msgstr "Produktbewertungen auf der Produktseite in einem separatem Reiter"
+
+#: includes/class-wc-trusted-shops-admin.php:780
+msgctxt "trusted-shops"
+msgid "Show Star-Ratings on the product detail page below your product name"
+msgstr "Bewertungssterne unter dem Produktnamen auf der Produktseite"
+
+#: includes/class-wc-trusted-shops-admin.php:784
+msgctxt "trusted-shops"
+msgid ""
+"Please note: If you want to send review requests through WooCommerce, you "
+"should deactivate automated review requests through Trusted Shops. To do so, "
+"please go to your My Trusted Shops account. Log in and go to Reviews > "
+"Settings and deactivate \"Collect reviews automatically\""
+msgstr ""
+"Bitte beachte: Wenn du Bewertungserinnerungen über WooCommerce versenden "
+"möchtest, solltest du den Versand von automatischen Bewertungserinnerungen "
+"über Trusted Shops deaktivieren. Gehe dazu in deinen My Trusted Shops "
+"Account. Logge dich ein und gehe zu Bewertungen > Konfiguration und "
+"deaktiviere dort „Bewertungen automatisch sammeln“."
+
+#: includes/class-wc-trusted-shops-admin.php:785
+msgctxt "trusted-shops"
+msgid "To your My Trusted Shops account"
+msgstr "Zu deinem My Trusted Shops Account"
+
+#: includes/class-wc-trusted-shops-admin.php:789
+msgctxt "trusted-shops"
+msgid ""
+"Export your customer information here and upload it in the Trusted Shops "
+"Review Collector. To do so go to your My Trusted Shops account. Log in and "
+"go to Reviews > Shop Reviews > Review Collector"
+msgstr ""
+"Exportiere hier die Kundendaten und lade diese im Trusted Shops Review "
+"Collector hoch. Gehe dazu in deinen My Trusted Shops Account. Logge dich ein "
+"und gehe zu Bewertungen > Shopbewertungen > Review Collector"
+
+#: includes/class-wc-trusted-shops-admin.php:790
+msgctxt "trusted-shops"
+msgid "To the Trusted Shops Review Collector"
+msgstr "Zum Trusted Shops Review Collector"
+
+#: includes/class-wc-trusted-shops-admin.php:880
+msgctxt "trusted-shops"
+msgid "Review Collector"
+msgstr "Review Collector"
+
+#: includes/class-wc-trusted-shops-admin.php:882
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"Want to collect reviews for orders that were placed before your Trusted "
+"Shops Integration? No problem. Export old orders here and upload them in "
+"your %s."
+msgstr ""
+"Du möchtest nachträglich Bewertungen zu Bestellungen sammeln, die vor der "
+"Trusted Shops Integration getätigt wurden? Kein Problem. Exportiere alte "
+"Bestellungen hier und lade diese in deinem %s hoch."
+
+#: includes/class-wc-trusted-shops-admin.php:882
+msgctxt "trusted-shops"
+msgid "My Trusted Shops account"
+msgstr "My Trusted Shops Account"
+
+#: includes/class-wc-trusted-shops-admin.php:888
+msgctxt "trusted-shops"
+msgid "Export orders"
+msgstr "Bestellungen exportieren"
+
+#: includes/class-wc-trusted-shops-admin.php:888
+msgctxt "trusted-shops"
+msgid ""
+"Export your customer and order information of the last x days and upload "
+"them in your My Trusted Shops Account."
+msgstr ""
+"Exportiere deine Kunden- und Bestelldaten der letzten x Tage und lade diese "
+"in deinem My Trusted Shops Account hoch."
+
+#: includes/class-wc-trusted-shops-admin.php:892
+msgctxt "trusted-shops"
+msgid "30 days"
+msgstr "30 Tage"
+
+#: includes/class-wc-trusted-shops-admin.php:893
+msgctxt "trusted-shops"
+msgid "60 days"
+msgstr "60 Tage"
+
+#: includes/class-wc-trusted-shops-admin.php:894
+msgctxt "trusted-shops"
+msgid "90 days"
+msgstr "90 Tage"
+
+#: includes/class-wc-trusted-shops-admin.php:896
+#, php-format
+msgctxt "trusted-shops"
+msgid "Upload customer and order information %s."
+msgstr "Kunden- und Bestelldaten %s hochladen"
+
+#: includes/class-wc-trusted-shops-admin.php:899
+msgctxt "trusted-shops"
+msgid "Days until reminder mail"
+msgstr "Tage bis zur Erinnerung"
+
+#: includes/class-wc-trusted-shops-admin.php:899
+msgctxt "trusted-shops"
+msgid ""
+"Set the number of days to wait after the order date before having a Review "
+"Request sent to your customers."
+msgstr ""
+"Stelle hier ein, wie viele Tage zwischen der Bestellung und dem Versand der "
+"Bewertungserinnerung liegen soll."
+
+#: includes/class-wc-trusted-shops-admin.php:903
+msgctxt "trusted-shops"
+msgid "Start export"
+msgstr "Export starten"
+
+#: includes/class-wc-trusted-shops-core.php:55
+#: includes/class-wc-trusted-shops-core.php:64
+#: includes/class-wc-ts-dependencies.php:36
+#: includes/class-wc-ts-dependencies.php:45
+msgctxt "trusted-shops"
+msgid "Cheatin’ huh?"
+msgstr "So geht das leider nicht.."
+
+#: includes/class-wc-trusted-shops-core.php:213
+msgctxt "trusted-shops"
+msgid "Yes"
+msgstr "Ja"
+
+#: includes/class-wc-trusted-shops-core.php:213
+msgctxt "trusted-shops"
+msgid "No"
+msgstr "Nein"
+
+#: includes/class-wc-trusted-shops-core.php:261
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"If the App helped you, please leave a %s★★"
+"★★★%s in the Wordpress plugin repository."
+msgstr ""
+"Wenn dir die App hilft, hinterlasse bitte eine %s★"
+"★★★★%s Bewertung in der WordPress Plugin Bibliothek."
+
+#: includes/class-wc-trusted-shops-core.php:410
+msgctxt "trusted-shops"
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:64
+msgctxt "trusted-shops"
+msgid "Order ID"
+msgstr "Bestellnummer"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:65
+msgctxt "trusted-shops"
+msgid "Order date"
+msgstr "Bestelldatum"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:66
+msgctxt "trusted-shops"
+msgid "# Days"
+msgstr "# Tage"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:67
+msgctxt "trusted-shops"
+msgid "Email"
+msgstr "E-Mail"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:68
+msgctxt "trusted-shops"
+msgid "First name"
+msgstr "Vorname"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:69
+msgctxt "trusted-shops"
+msgid "Last name"
+msgstr "Nachname"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:121
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"Your review reminder e-mail has been cancelled successfully. Return to %s."
+msgstr ""
+"Deine Bewertungserinnerung wurde erfolgreich deaktiviert. Kehre zurück zur "
+"%s."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:121
+msgctxt "trusted-shops"
+msgid "Home"
+msgstr "Startseite"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:193
+msgctxt "trusted-shops"
+msgid ""
+"Yes, I would like to be reminded via e-mail after {days} day(s) to review my "
+"order. I am able to cancel the reminder at any time by clicking on the "
+"\"cancel review reminder\" link within the order confirmation."
+msgstr ""
+"Ja, ich bin damit einverstanden, eine Erinnerung per E-Mail zur Bewertung "
+"nach {days} Tage(n) zu erhalten. Ich kann mich jederzeit davon abmelden bzw. "
+"widersprechen, indem ich dem Link „von der Bewertungserinnerung abmelden“ in "
+"der Bestellbestätigung folge."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:198
+msgctxt "trusted-shops"
+msgid "Please allow us to send a review reminder by e-mail."
+msgstr "Bitte akzeptiere den Erhalt einer Bewertungserinnerung per E-Mail."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:201
+msgctxt "trusted-shops"
+msgid "Review reminder"
+msgstr "Bewertungs Erinnerung"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:202
+msgctxt "trusted-shops"
+msgid "Asks the customer to receive a Trusted Shops review reminder."
+msgstr ""
+"Holt die Erlaubnis zum Senden einer einmaligen Trusted Shops "
+"Bewertungserinnerung ein."
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:24
+msgctxt "trusted-shops"
+msgid "Trusted Shops Review Reminder"
+msgstr "Trusted Shops Bewertungs-Erinnerung"
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:25
+msgctxt "trusted-shops"
+msgid ""
+"This E-Mail is being sent to a customer to remind him about the possibility "
+"to leave a review at Trusted Shops."
+msgstr ""
+"Diese E-Mail wird einmalig an Kunden verschickt, um diese an die Abgabe "
+"einer Bewertung bei Trusted Shops zu erinnern."
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:54
+msgctxt "trusted-shops"
+msgid "Please rate your {site_title} order from {order_date}"
+msgstr "Bitte bewerte deinen Einkauf vom {order_date} bei {site_title}"
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:64
+msgctxt "trusted-shops"
+msgid "Please rate your Order"
+msgstr "Bitte bewerte deinen Einkauf"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:19
+msgctxt "trusted-shops"
+msgid "Show your TS shop review sticker."
+msgstr "Zeige deinen Trusted Shops Review Sticker an."
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:21
+msgctxt "trusted-shops"
+msgid "Trusted Shops Shop Review Sticker"
+msgstr "Trusted Shops Shop Review Sticker"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:25
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:46
+msgctxt "trusted-shops"
+msgid "Trusted Shops Reviews"
+msgstr "Trusted Shops Bewertung"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:26
+msgctxt "trusted-shops"
+msgid "Title"
+msgstr "Bezeichnung"
+
+#: src/Package.php:55
+msgctxt "trusted-shops"
+msgid ""
+"Trustbadge Reviews for WooCommerce needs at least WooCommerce version 3.1 to "
+"run."
+msgstr ""
+"Trustbadge Reviews for WooCommerce benötigt mind. WooCommerce Version 3.1 um "
+"zu starten."
+
+#: templates/emails/customer-trusted-shops.php:17
+#: templates/emails/plain/customer-trusted-shops.php:14
+#, php-format
+msgctxt "trusted-shops"
+msgid "Dear %s %s,"
+msgstr "Hallo %s %s,"
+
+#: templates/emails/customer-trusted-shops.php:18
+#: templates/emails/plain/customer-trusted-shops.php:16
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"You have recently shopped at %s. Thank you! We would be glad if you spent "
+"some time to write a review about your order. To do so please follow follow "
+"the link."
+msgstr ""
+"Du hast vor einiger Zeit bei %s eingekauft. Vielen Dank! Wir wären froh "
+"darüber, wenn du dir die Zeit nimmst und deinen Einkauf bewerten würdest. Um "
+"dies nun zu tun, klicke bitte auf den nachfolgenden Link."
+
+#: templates/emails/customer-trusted-shops.php:22
+msgctxt "trusted-shops"
+msgid "Rate Order now"
+msgstr "Einkauf jetzt bewerten"
+
+#~ msgctxt "trusted-shops"
+#~ msgid "Duplicate Plugin installation"
+#~ msgstr "Doppelte Plugin Installation"
+
+#, php-format
+#~ msgctxt "trusted-shops"
+#~ msgid ""
+#~ "It seems like you've installed WooCommerce Germanized and Trustbadge "
+#~ "Reviews for WooCommerce. Please deactivate Trustbadge Reviews for "
+#~ "WooCommerce as long as you are using WooCommerce Germanized. You can "
+#~ "manage your Trusted Shops configuration within your %s."
+#~ msgstr ""
+#~ "Es scheint als hättest du WooCommerce Germanized und Trustbadge Reviews "
+#~ "for WooCommerce installiert. Bitte deaktiviere das Trustbadge Reviews "
+#~ "Plugin solange du WooCommerce Germanized verwendest. Du kannst deine "
+#~ "Trusted Shops Konfiguration in deinen %s verwalten."
+
+#~ msgctxt "trusted-shops"
+#~ msgid "Germanized settings"
+#~ msgstr "Germanized Einstellungen"
+
+#~ msgctxt "trusted-shops"
+#~ msgid "Deactivate standalone version"
+#~ msgstr "Deaktiviere die Standalone-Version"
+
+#~ msgctxt "trusted-shops"
+#~ msgid "(WooCommerce Product Reviews will be replaced)"
+#~ msgstr "(WooCommerce Produktbewertungen werden ersetzt)"
+
+#, php-format
+#~ msgid ""
+#~ "Please install WooCommerce before "
+#~ "installing WooCommerce Germanized. Thank you!"
+#~ msgstr ""
+#~ "Bitte installiere WooCommerce bevor "
+#~ "du WooCommerce Germanized installierst. Vielen Dank!"
diff --git a/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE_formal.mo b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE_formal.mo
new file mode 100644
index 000000000..4ea14e41a
Binary files /dev/null and b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE_formal.mo differ
diff --git a/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE_formal.po b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE_formal.po
new file mode 100644
index 000000000..1b18aea99
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-de_DE_formal.po
@@ -0,0 +1,1172 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: WooCommerce Trusted Shops\n"
+"POT-Creation-Date: 2019-10-15 12:40+0200\n"
+"PO-Revision-Date: 2019-10-15 12:41+0200\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.4\n"
+"X-Poedit-Basepath: ../..\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
+"_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;"
+"esc_attr_e;esc_html_e;esc_html__\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-SearchPathExcluded-0: node_modules\n"
+"X-Poedit-SearchPathExcluded-1: vendor\n"
+
+#: includes/admin/settings/class-wc-ts-gzd-settings-tab.php:48
+msgctxt "trusted-shops"
+msgid "Setup your Trusted Shops Integration."
+msgstr "Setzen Sie Ihre Trusted Shops Integration auf."
+
+#: includes/admin/settings/class-wc-ts-gzd-settings-tab.php:52
+#: includes/class-wc-ts-settings-handler.php:23
+msgctxt "trusted-shops"
+msgid "Trusted Shops"
+msgstr "Trusted Shops"
+
+#: includes/admin/views/html-notice-dependencies.php:16
+msgctxt "trusted-shops"
+msgid "Dependencies Missing or Outdated"
+msgstr "Wichtige Plugins fehlen oder sind veraltet"
+
+#: includes/admin/views/html-notice-dependencies.php:24
+msgctxt "trusted-shops"
+msgid ""
+"To use WooCommerce Trusted Shops you may at first install the following "
+"plugins:"
+msgstr ""
+"Um WooCommerce Trusted Shops zuverlässig nutzen zu können, müssen Sie zuerst "
+"folgende Plugins installieren:"
+
+#: includes/admin/views/html-notice-dependencies.php:28
+#, php-format
+msgctxt "trusted-shops"
+msgid "Install %s"
+msgstr "Installiere %s"
+
+#: includes/admin/views/html-notice-dependencies.php:36
+msgctxt "trusted-shops"
+msgid ""
+"To use WooCommerce Trusted Shops you may at first update the following "
+"plugins to a newer version:"
+msgstr ""
+"Um WooCommerce Trusted Shops zuverlässig nutzen zu können, updaten Sie bitte "
+"folgende Plugins:"
+
+#: includes/admin/views/html-notice-dependencies.php:40
+#, php-format
+msgctxt "trusted-shops"
+msgid "%s required in at least version %s"
+msgstr "%s wird mindestens in Version %s benötigt"
+
+#: includes/admin/views/html-notice-dependencies.php:49
+msgctxt "trusted-shops"
+msgid "Check for Updates"
+msgstr "nach Updates suchen"
+
+#: includes/admin/views/html-notice-dependencies.php:50
+msgctxt "trusted-shops"
+msgid "or"
+msgstr "oder"
+
+#: includes/admin/views/html-notice-dependencies.php:51
+msgctxt "trusted-shops"
+msgid "Install an older version"
+msgstr "Installiere eine ältere Version"
+
+#: includes/admin/views/html-notice-update.php:14
+msgctxt "trusted-shops"
+msgid ""
+"WooCommerce Trusted Shops Data Update Required – We "
+"just need to update your installation to the latest version"
+msgstr ""
+"WooCommerce Trusted Shops Datenaktualisierung erforderlich "
+"– Wir müssen deine Installation auf die neueste Version updaten"
+
+#: includes/admin/views/html-notice-update.php:15
+msgctxt "trusted-shops"
+msgid "Run the updater"
+msgstr "Update starten"
+
+#: includes/admin/views/html-notice-update.php:19
+msgctxt "trusted-shops"
+msgid ""
+"It is strongly recommended that you backup your database before proceeding. "
+"Are you sure you wish to run the updater now?"
+msgstr ""
+"Sie sollten vor einem Update immer ein Backup der Datenbank anlegen. Sind "
+"Sie sicher das Update jetzt zu installieren?"
+
+#: includes/admin/views/html-wpml-notice.php:11
+msgctxt "trusted-shops"
+msgid "WPML Support"
+msgstr "WPML Unterstützung"
+
+#: includes/admin/views/html-wpml-notice.php:14
+msgctxt "trusted-shops"
+msgid ""
+"These settings serve as default settings for all your languages. To adjust "
+"the settings for a certain language, please switch your admin language "
+"through the WPML language switcher and adjust the corresponding settings."
+msgstr ""
+"Diese Einstellungen werden als Standard für alle Ihre Sprachen verwendet. Um "
+"die Einstellungen für eine spezielle Sprache zu ändern, wechseln Sie bitte "
+"zur entsprechenden Sprache über den WPML Sprachumschalter."
+
+#: includes/admin/views/html-wpml-notice.php:16
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"These settings apply for your %s shop. To adjust settings for another "
+"language, please switch your admin language through the WPML language "
+"switcher."
+msgstr ""
+"Diese Einstellungen werden für Ihren %s Shop verwendet. Um die Einstellungen "
+"für eine andere Sprache anzupassen, wechseln Sie bitte die Sprache über den "
+"WPML Sprachumschalter."
+
+#: includes/class-wc-trusted-shops-admin.php:84
+#: includes/class-wc-trusted-shops-admin.php:121
+msgctxt "trusted-shops"
+msgid "GTIN"
+msgstr "GTIN"
+
+#: includes/class-wc-trusted-shops-admin.php:84
+#: includes/class-wc-trusted-shops-admin.php:121
+msgctxt "trusted-shops"
+msgid ""
+"ID that allows your products to be identified worldwide. If you want to "
+"display your Trusted Shops Product Reviews in Google Shopping and paid "
+"Google adverts, Google needs the GTIN."
+msgstr ""
+"Identifikationsnummer, mit der Produkte weltweit eindeutig identifiziert "
+"werden können. Wenn Sie Ihre Trusted Shops Produktbewertungen in Google "
+"Shopping und bezahlten Google Produktanzeigen ausspielen möchten, benötigt "
+"Google die GTIN."
+
+#: includes/class-wc-trusted-shops-admin.php:88
+#: includes/class-wc-trusted-shops-admin.php:122
+msgctxt "trusted-shops"
+msgid "MPN"
+msgstr "MPN"
+
+#: includes/class-wc-trusted-shops-admin.php:88
+#: includes/class-wc-trusted-shops-admin.php:122
+msgctxt "trusted-shops"
+msgid ""
+"If you don't have a GTIN for your products, you can pass the brand name and "
+"the MPN on to Google to use the Trusted Shops Google Integration."
+msgstr ""
+"Wenn Ihre Produkte keine GTIN haben, können SIe den Markennamen zusammen mit "
+"der MPN übergeben, um die Trusted Shops Google Integration zu nutzen."
+
+#: includes/class-wc-trusted-shops-admin.php:142
+msgctxt "trusted-shops"
+msgid "Brand"
+msgstr "Marke"
+
+#: includes/class-wc-trusted-shops-admin.php:176
+msgctxt "trusted-shops"
+msgid "This field is mandatory"
+msgstr "Dieses Feld ist ein Pflichtfeld"
+
+#: includes/class-wc-trusted-shops-admin.php:183
+msgctxt "trusted-shops"
+msgid "Trusted Shops Options"
+msgstr "Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:190
+msgctxt "trusted-shops"
+msgid "Arial"
+msgstr "Arial"
+
+#: includes/class-wc-trusted-shops-admin.php:191
+msgctxt "trusted-shops"
+msgid "Geneva"
+msgstr "Geneva"
+
+#: includes/class-wc-trusted-shops-admin.php:192
+msgctxt "trusted-shops"
+msgid "Georgia"
+msgstr "Georgia"
+
+#: includes/class-wc-trusted-shops-admin.php:193
+msgctxt "trusted-shops"
+msgid "Helvetica"
+msgstr "Helvetica"
+
+#: includes/class-wc-trusted-shops-admin.php:194
+msgctxt "trusted-shops"
+msgid "Sans-serif"
+msgstr "Sans-serif"
+
+#: includes/class-wc-trusted-shops-admin.php:195
+msgctxt "trusted-shops"
+msgid "Serif"
+msgstr "Serif"
+
+#: includes/class-wc-trusted-shops-admin.php:196
+msgctxt "trusted-shops"
+msgid "Trebuchet MS"
+msgstr "Trebuchet MS"
+
+#: includes/class-wc-trusted-shops-admin.php:197
+msgctxt "trusted-shops"
+msgid "Verdana"
+msgstr "Verdana"
+
+#: includes/class-wc-trusted-shops-admin.php:220
+msgctxt "trusted-shops"
+msgid "Trusted Shops Integration"
+msgstr "Trusted Shops Integration"
+
+#: includes/class-wc-trusted-shops-admin.php:221
+#, php-format
+msgctxt "trusted-shops"
+msgid "Do you need help with integrating your Trustbadge? %s"
+msgstr "Brauchen Sie Hilfe bei der Einbindung Ihres Trustbadges? %s"
+
+#: includes/class-wc-trusted-shops-admin.php:221
+msgctxt "trusted-shops"
+msgid "To the step-by-step instructions"
+msgstr "Zur Schritt-für-Schritt Anleitung"
+
+#: includes/class-wc-trusted-shops-admin.php:227
+msgctxt "trusted-shops"
+msgid "Trusted Shops ID"
+msgstr "Trusted Shops ID"
+
+#: includes/class-wc-trusted-shops-admin.php:228
+msgctxt "trusted-shops"
+msgid ""
+"The Trusted Shops ID is a unique identifier for your shop. You can find your "
+"Trusted Shops ID in your My Trusted Shops account."
+msgstr ""
+"Die Trusted Shops ID identifiziert Ihren Shop eindeutig bei Trusted Shops. "
+"Sie finden die Trusted Shops ID in Ihrem My Trusted Shops Account."
+
+#: includes/class-wc-trusted-shops-admin.php:237
+msgctxt "trusted-shops"
+msgid "Edit Mode"
+msgstr "Bearbeitungsmodus"
+
+#: includes/class-wc-trusted-shops-admin.php:239
+#: includes/class-wc-trusted-shops-admin.php:300
+#: includes/class-wc-trusted-shops-admin.php:423
+msgctxt "trusted-shops"
+msgid ""
+"The advanced configuration is for users with programming skills. Here you "
+"can create even more individual settings."
+msgstr ""
+"Der Expertenmodus ist für fortgeschrittene Nutzer mit Programmierkenntnissen "
+"gedacht. Hier können Sie noch mehr individuelle Einstellungen vornehmen."
+
+#: includes/class-wc-trusted-shops-admin.php:243
+msgctxt "trusted-shops"
+msgid "Standard configuration"
+msgstr "Standardmodus"
+
+#: includes/class-wc-trusted-shops-admin.php:244
+msgctxt "trusted-shops"
+msgid "Advanced configuration"
+msgstr "Expertenmodus"
+
+#: includes/class-wc-trusted-shops-admin.php:252
+msgctxt "trusted-shops"
+msgid "Configure your Trustbadge"
+msgstr "Konfigurieren Sie Ihr Trustbadge"
+
+#: includes/class-wc-trusted-shops-admin.php:258
+msgctxt "trusted-shops"
+msgid "Display Trustbadge"
+msgstr "Trustbadge anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:260
+msgctxt "trusted-shops"
+msgid "Display the Trustbadge on all the pages of your shop."
+msgstr "Zeige das Trustbadge auf allen Seiten des Shops an."
+
+#: includes/class-wc-trusted-shops-admin.php:267
+msgctxt "trusted-shops"
+msgid "Variant"
+msgstr "Variante"
+
+#: includes/class-wc-trusted-shops-admin.php:269
+msgctxt "trusted-shops"
+msgid "You can display your Trustbadge with or without Review Stars."
+msgstr "Sie können Ihr Trustbadge mit oder ohne Bewertungssterne anzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:273
+#: includes/class-wc-trusted-shops-admin.php:763
+msgctxt "trusted-shops"
+msgid "Display Trustbadge with review stars"
+msgstr "Trustbadge mit Bewertungssternen anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:274
+#: includes/class-wc-trusted-shops-admin.php:767
+msgctxt "trusted-shops"
+msgid "Display Trustbadge without review stars"
+msgstr "Trustbadge ohne Bewertungssterne anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:280
+msgctxt "trusted-shops"
+msgid "Vertical Offset"
+msgstr "Vertikaler Abstand"
+
+#: includes/class-wc-trusted-shops-admin.php:281
+msgctxt "trusted-shops"
+msgid ""
+"Choose the distance that the Trustbadge will appear from the bottom-right "
+"corner of the screen."
+msgstr ""
+"Wählen Sie einen Abstand für Ihr Trustbadge vom unteren rechten "
+"Bildschirmrand."
+
+#: includes/class-wc-trusted-shops-admin.php:284
+#: includes/class-wc-trusted-shops-admin.php:492
+#: includes/class-wc-trusted-shops-admin.php:543
+#: includes/class-wc-trusted-shops-admin.php:558
+msgctxt "trusted-shops"
+msgid "px"
+msgstr "px"
+
+#: includes/class-wc-trusted-shops-admin.php:290
+#: includes/class-wc-trusted-shops-admin.php:499
+#: includes/class-wc-trusted-shops-admin.php:549
+#: includes/class-wc-trusted-shops-admin.php:565
+#, php-format
+msgctxt "trusted-shops"
+msgid "Please choose a non-negative number (at least %d)"
+msgstr "Bitte verwenden Sie eine nicht-negative Nummer (mindestens %d)"
+
+#: includes/class-wc-trusted-shops-admin.php:296
+msgctxt "trusted-shops"
+msgid "Trustbadge code"
+msgstr "Trustbadge Code"
+
+#: includes/class-wc-trusted-shops-admin.php:308
+msgctxt "trusted-shops"
+msgid "Configure your Shop Reviews"
+msgstr "Konfigurieren Sie Ihre Shopbewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:314
+msgctxt "trusted-shops"
+msgid "Display Shop Review Sticker"
+msgstr "Shopbewertungssticker anzeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:315
+msgctxt "trusted-shops"
+msgid ""
+"To display the Shop Review Sticker, you have to assign the widget \"Trusted "
+"Shops Review Sticker\"."
+msgstr ""
+"Um den Shopbewertungssticker anzuzeigen, müssen Sie das Widget „Trusted "
+"Shops Shopbewertungssticker“ zuweisen."
+
+#: includes/class-wc-trusted-shops-admin.php:316
+#, php-format
+msgctxt "trusted-shops"
+msgid "Assign widget %s"
+msgstr "Widget %s zuweisen"
+
+#: includes/class-wc-trusted-shops-admin.php:316
+#: includes/class-wc-trusted-shops-admin.php:588
+#: includes/class-wc-trusted-shops-admin.php:896
+msgctxt "trusted-shops"
+msgid "here"
+msgstr "hier"
+
+#: includes/class-wc-trusted-shops-admin.php:324
+#: includes/class-wc-trusted-shops-admin.php:472
+msgctxt "trusted-shops"
+msgid "Background color"
+msgstr "Hintergrundfarbe"
+
+#: includes/class-wc-trusted-shops-admin.php:325
+msgctxt "trusted-shops"
+msgid "Choose the background color for your Review Sticker."
+msgstr "Wählen Sie die Hintergrundfarbe für den Review Sticker."
+
+#: includes/class-wc-trusted-shops-admin.php:332
+msgctxt "trusted-shops"
+msgid "Font"
+msgstr "Schriftart"
+
+#: includes/class-wc-trusted-shops-admin.php:334
+msgctxt "trusted-shops"
+msgid "Choose the font for your Review Sticker."
+msgstr "Wählen Sie die Schriftart für den Review Sticker."
+
+#: includes/class-wc-trusted-shops-admin.php:341
+msgctxt "trusted-shops"
+msgid "Number of reviews displayed"
+msgstr "Anzahl Bewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:342
+msgctxt "trusted-shops"
+msgid ""
+"Display x alternating Shop Reviews in your Shop Review Sticker. You can "
+"display between 1 and 5 alternating Shop Reviews."
+msgstr ""
+"Zeige x Shopbewertungen im Wechsel in Ihrem Shopbewertungssticker an. Es "
+"können mindestens 1 und maximal 5 Bewertungen im Wechsel angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:345
+msgctxt "trusted-shops"
+msgid "Show x alternating reviews"
+msgstr "x Bewertungen im Wechsel zeigen"
+
+#: includes/class-wc-trusted-shops-admin.php:352
+#: includes/class-wc-trusted-shops-admin.php:369
+#, php-format
+msgctxt "trusted-shops"
+msgid "Please choose a non-negative number between %d and %d"
+msgstr "Bitte verwenden Sie eine nicht-negative Nummer zwischen %d und %d"
+
+#: includes/class-wc-trusted-shops-admin.php:358
+msgctxt "trusted-shops"
+msgid "Minimum rating displayed"
+msgstr "Angezeigte Mindestnote"
+
+#: includes/class-wc-trusted-shops-admin.php:359
+msgctxt "trusted-shops"
+msgid "Only show Shop Reviews with a minimum rating of x stars. "
+msgstr "Zeige nur Shopbewertungen mit einer Mindestanzahl von x Sternen."
+
+#: includes/class-wc-trusted-shops-admin.php:362
+msgctxt "trusted-shops"
+msgid "Star(s)"
+msgstr "Stern(e)"
+
+#: includes/class-wc-trusted-shops-admin.php:375
+msgctxt "trusted-shops"
+msgid "Sticker code"
+msgstr "Sticker Code"
+
+#: includes/class-wc-trusted-shops-admin.php:379
+#: includes/class-wc-trusted-shops-admin.php:506
+#: includes/class-wc-trusted-shops-admin.php:571
+msgctxt "trusted-shops"
+msgid ""
+"The advanced configuration is for users with programming skills. Here you "
+"can perform even more individual settings."
+msgstr ""
+"Der Expertenmodus ist für fortgeschrittene Nutzer mit Programmierkenntnissen "
+"gedacht. Hier können Sie noch mehr individuelle Einstellungen vornehmen."
+
+#: includes/class-wc-trusted-shops-admin.php:385
+msgctxt "trusted-shops"
+msgid "Google Organic Search"
+msgstr "Organische Google Suche"
+
+#: includes/class-wc-trusted-shops-admin.php:386
+msgctxt "trusted-shops"
+msgid ""
+"Activate this option to give Google the opportunity to show your Shop "
+"Reviews in Google organic search results."
+msgstr ""
+"Aktivieren Sie diese Funktion, um Google die Möglichkeit zu geben, Ihre "
+"Shopbewertungen in den organischen Google Suchergebnissen anzuzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:387
+msgctxt "trusted-shops"
+msgid ""
+"By activating this option, rich snippets will be integrated in the selected "
+"pages so your shop review stars may be displayed in Google organic search "
+"results. If you use Product Reviews and already activated rich snippets in "
+"expert mode, we recommend integrating rich snippets for Shop Reviews on "
+"category pages only."
+msgstr ""
+"Wenn Sie diese Option aktivieren, werden Rich Snippets in die ausgewählten "
+"Seiten eingebunden, sodass Ihre Shopbewertungssterne in den organischen "
+"Google Suchergebnissen angezeigt werden können. Wenn Sie Produktbewertungen "
+"aktiviert haben und dort im Expertenmodus bereits Rich Snippets aktiviert "
+"haben, empfehlen wir die Ausgabe der Rich Snippets für Shopbewertungen nur "
+"auf der Kategorieseite."
+
+#: includes/class-wc-trusted-shops-admin.php:394
+msgctxt "trusted-shops"
+msgid "Activate rich snippets on"
+msgstr "Rich Snippets aktivieren auf"
+
+#: includes/class-wc-trusted-shops-admin.php:395
+msgctxt "trusted-shops"
+msgid "category pages"
+msgstr "Kategorieseiten"
+
+#: includes/class-wc-trusted-shops-admin.php:403
+msgctxt "trusted-shops"
+msgid "product pages"
+msgstr "Produktdetailseiten"
+
+#: includes/class-wc-trusted-shops-admin.php:411
+msgctxt "trusted-shops"
+msgid "homepage (not recommended)"
+msgstr "Startseite (nicht empfohlen)"
+
+#: includes/class-wc-trusted-shops-admin.php:419
+msgctxt "trusted-shops"
+msgid "Rich snippets code"
+msgstr "Rich Snippets Code"
+
+#: includes/class-wc-trusted-shops-admin.php:431
+msgctxt "trusted-shops"
+msgid "Configure your Product Reviews "
+msgstr "Konfigurieren Sie die Produktbewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:432
+#, php-format
+msgctxt "trusted-shops"
+msgid "To use Product Reviews, activate them in your %s first."
+msgstr ""
+"Um Produktbewertungen nutzen zu können, schalten Sie diese zuerst in Ihrem "
+"%s frei."
+
+#: includes/class-wc-trusted-shops-admin.php:432
+msgctxt "trusted-shops"
+msgid "Trusted Shops package"
+msgstr "Trusted Shops Paket"
+
+#: includes/class-wc-trusted-shops-admin.php:438
+msgctxt "trusted-shops"
+msgid "Collect Product Reviews"
+msgstr "Produktbewertungen sammeln"
+
+#: includes/class-wc-trusted-shops-admin.php:439
+msgctxt "trusted-shops"
+msgid ""
+"Show Product Reviews on the product page in a separate tab, just as shown on "
+"the picture on the right."
+msgstr ""
+"Zeige Produktbewertungen auf der Produktseite in einem separaten Reiter, wie "
+"in der Grafik rechts abgebildet."
+
+#: includes/class-wc-trusted-shops-admin.php:447
+msgctxt "trusted-shops"
+msgid "Reviews"
+msgstr "Bewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:448
+#: includes/class-wc-trusted-shops-admin.php:457
+msgctxt "trusted-shops"
+msgid "You can choose a name for the tab with your Product Reviews."
+msgstr ""
+"Sie können selbst bestimmen, wie der Reiter in dem Ihre Produktbewertungen "
+"angezeigt werden, heißen soll."
+
+#: includes/class-wc-trusted-shops-admin.php:449
+msgctxt "trusted-shops"
+msgid "Show Product Reviews on the product detail page in an additional tab."
+msgstr "Produktbewertungen auf der Produktseite in separatem Reiter anzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:456
+msgctxt "trusted-shops"
+msgid "Name of Product Reviews tab"
+msgstr "Bezeichnung Reiter"
+
+#: includes/class-wc-trusted-shops-admin.php:460
+msgctxt "trusted-shops"
+msgid "Product reviews"
+msgstr "Produktbewertungen"
+
+#: includes/class-wc-trusted-shops-admin.php:464
+msgctxt "trusted-shops"
+msgid "Border color"
+msgstr "Umrandung"
+
+#: includes/class-wc-trusted-shops-admin.php:465
+msgctxt "trusted-shops"
+msgid "Set the color for the frame around your Product Reviews."
+msgstr "Legen Sie die Farbe für den Rahmen um die Produktbewertungen fest."
+
+#: includes/class-wc-trusted-shops-admin.php:473
+msgctxt "trusted-shops"
+msgid "Set the background color for your Product Reviews."
+msgstr "Legen Sie die Hintergrundfarbe für die Produktbewertungen fest."
+
+#: includes/class-wc-trusted-shops-admin.php:480
+#: includes/class-wc-trusted-shops-admin.php:530
+msgctxt "trusted-shops"
+msgid "Star color"
+msgstr "Farbe der Sterne"
+
+#: includes/class-wc-trusted-shops-admin.php:481
+msgctxt "trusted-shops"
+msgid "Set the color for the Product Review stars in your Product Reviews tab."
+msgstr ""
+"Legen Sie die Farbe der Sterne fest, die in Ihrem Produktbewertungs-Reiter "
+"angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:488
+#: includes/class-wc-trusted-shops-admin.php:538
+msgctxt "trusted-shops"
+msgid "Star size"
+msgstr "Größe der Sterne"
+
+#: includes/class-wc-trusted-shops-admin.php:493
+msgctxt "trusted-shops"
+msgid "Set the size for the Product Review stars in your Product Reviews tab."
+msgstr ""
+"Legen Sie die Größe der Sterne fest, die in Ihrem Produktbewertungs-Reiter "
+"angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:504
+msgctxt "trusted-shops"
+msgid "Product Sticker Code"
+msgstr "Produktbewertungen Code"
+
+#: includes/class-wc-trusted-shops-admin.php:513
+#: includes/class-wc-trusted-shops-admin.php:579
+msgctxt "trusted-shops"
+msgid "jQuerySelector"
+msgstr "jQuerySelector"
+
+#: includes/class-wc-trusted-shops-admin.php:514
+msgctxt "trusted-shops"
+msgid ""
+"Please choose where your Product Reviews shall be displayed on the Product "
+"detail page."
+msgstr ""
+"Wählen Sie, wo die Produktbewertungen auf der Produktdetailseite angezeigt "
+"werden sollen."
+
+#: includes/class-wc-trusted-shops-admin.php:521
+msgctxt "trusted-shops"
+msgid "Rating stars"
+msgstr "Bewertungssterne"
+
+#: includes/class-wc-trusted-shops-admin.php:522
+msgctxt "trusted-shops"
+msgid "Show star ratings on the product detail page below your product name."
+msgstr "Bewertungssterne auf der Produktseite unter dem Produktnamen anzeigen."
+
+#: includes/class-wc-trusted-shops-admin.php:523
+msgctxt "trusted-shops"
+msgid ""
+"Display Product Review stars on product pages below the product name, just "
+"as shown in the picture on the right."
+msgstr ""
+"Zeige Bewertungssterne auf der Produktseite unter dem Produktnamen, wie in "
+"der Grafik rechts abgebildet."
+
+#: includes/class-wc-trusted-shops-admin.php:532
+msgctxt "trusted-shops"
+msgid ""
+"Set the color for the review stars, that are displayed on the product page, "
+"below your product name."
+msgstr ""
+"Legen Sie die Farbe der Sterne fest, die auf der Produktseite unter dem "
+"Produktnamen angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:540
+msgctxt "trusted-shops"
+msgid ""
+"Set the size for the review stars that are displayed on the product page, "
+"below your product name."
+msgstr ""
+"Legen Sie die Größe der Sterne fest, die auf der Produktseite unter dem "
+"Produktnamen angezeigt werden."
+
+#: includes/class-wc-trusted-shops-admin.php:554
+msgctxt "trusted-shops"
+msgid "Font size"
+msgstr "Schriftgröße"
+
+#: includes/class-wc-trusted-shops-admin.php:556
+msgctxt "trusted-shops"
+msgid "Set the font size for the text that goes with your review stars."
+msgstr "Legen Sie die Schriftgröße für den Text zu den Bewertungssternen fest."
+
+#: includes/class-wc-trusted-shops-admin.php:570
+msgctxt "trusted-shops"
+msgid "Product Review Code"
+msgstr "Produktbewertungen Code"
+
+#: includes/class-wc-trusted-shops-admin.php:580
+msgctxt "trusted-shops"
+msgid ""
+"Please choose where your Product Review Stars shall be displayed on the "
+"Product Detail page."
+msgstr ""
+"Wählen Sie, wo die Produktbewertungssterne auf der Produktdetailseite "
+"angezeigt werden sollen."
+
+#: includes/class-wc-trusted-shops-admin.php:587
+msgctxt "trusted-shops"
+msgid "Brand attribute"
+msgstr "Marken-Produktattribut"
+
+#: includes/class-wc-trusted-shops-admin.php:588
+#, php-format
+msgctxt "trusted-shops"
+msgid "Create brand attribute %s"
+msgstr "Marken-Produktattribut %s erstellen"
+
+#: includes/class-wc-trusted-shops-admin.php:589
+msgctxt "trusted-shops"
+msgid ""
+"Brand name of the product. By passing this information on to Google, you "
+"improve your chances of having Google identify your products. Assign your "
+"brand attribute. If your products don't have a GTIN, you can pass on the "
+"brand name and the MPN to use Google Integration."
+msgstr ""
+"Markenname des Produkts. Durch Übergeben dieser Variable verbessern Sie die "
+"Möglichkeit Ihre Produkte von Google eindeutig identifizieren zu lassen. "
+"Weisen Sie ein Marken-Produktattribut zu. Wenn Ihre Produkte keine GTIN "
+"haben, können Sie den Markennamen zusammen mit der MPN übergeben, um die "
+"Google Integration zu nutzen."
+
+#: includes/class-wc-trusted-shops-admin.php:595
+msgctxt "trusted-shops"
+msgid "None"
+msgstr "Keine"
+
+#: includes/class-wc-trusted-shops-admin.php:606
+msgctxt "trusted-shops"
+msgid "Configure your Review Requests"
+msgstr "Konfigurieren Sie die Bewertungserinnerungen"
+
+#: includes/class-wc-trusted-shops-admin.php:607
+msgctxt "trusted-shops"
+msgid ""
+"7 days after an order has been placed, Trusted Shops automatically sends an "
+"invite to your customers. If you want to set a different time for sending "
+"automatic Review Requests, please activate the option below. If you want to "
+"send review requests with legal certainty, you need your customers' consent "
+"to receive Review Requests. You also have to include an option to "
+"unsubscribe."
+msgstr ""
+"Trusted Shops versendet 7 Tage nach Bestellung automatisch eine "
+"Bewertungserinnerung an Ihre Kunden. Wenn Sie lieber selbst entscheiden "
+"möchten, wann Bewertungsanfragen versendet werden, aktivieren Sie die "
+"untenstehende Option. Möchten Sie rechtssicher Bewertungs-Erinnerungen "
+"verschicken, so benötigen Sie die ausdrückliche Einwilligung Ihrer Kunden "
+"zum Empfang von Bewertungsemails. Sie müssen Ihren Kunden zudem eine "
+"Möglichkeit geben, Bewertungserinnerungen wieder abzubestellen."
+
+#: includes/class-wc-trusted-shops-admin.php:613
+msgctxt "trusted-shops"
+msgid "Enable Review Requests"
+msgstr "Bewertungserinnerungen aktivieren"
+
+#: includes/class-wc-trusted-shops-admin.php:622
+msgctxt "trusted-shops"
+msgid "WooCommerce status"
+msgstr "Bestellstatus"
+
+#: includes/class-wc-trusted-shops-admin.php:623
+msgctxt "trusted-shops"
+msgid ""
+"We recommend choosing the order status that you set when your products have "
+"been shipped."
+msgstr ""
+"Wählen Sie hier am besten den Bestellstatus aus WooCommerce aus, den Sie "
+"einstellen, wenn Ihre Ware versendet wurde."
+
+#: includes/class-wc-trusted-shops-admin.php:631
+msgctxt "trusted-shops"
+msgid "Days until Review Request"
+msgstr "Tage bis zur Erinnerung"
+
+#: includes/class-wc-trusted-shops-admin.php:632
+msgctxt "trusted-shops"
+msgid ""
+"Set the number of days to wait after an order has reached the order status "
+"you selected above before having a review request sent to your customers."
+msgstr ""
+"Stellen Sie hier ein nach wie vielen Tagen nach Erreichen des oben "
+"ausgewählten Bestellstatus die Bewertungserinnerung an den Käufer versendet "
+"werden soll."
+
+#: includes/class-wc-trusted-shops-admin.php:644
+msgctxt "trusted-shops"
+msgid "Permission via checkbox"
+msgstr "Einwilligung per Checkbox"
+
+#: includes/class-wc-trusted-shops-admin.php:645
+msgctxt "trusted-shops"
+msgid ""
+"If the checkbox is activated, only customers who gave their consent will "
+"receive Review Requests."
+msgstr ""
+"Bei aktivierter Checkbox erhalten nur die Kunden eine Bewertungserinnerung, "
+"die ihr Einverständnis gegeben haben."
+
+#: includes/class-wc-trusted-shops-admin.php:649
+msgctxt "trusted-shops"
+msgid "Edit checkbox"
+msgstr "Checkbox anpassen"
+
+#: includes/class-wc-trusted-shops-admin.php:653
+msgctxt "trusted-shops"
+msgid "Unsubscribe via link"
+msgstr "Abmeldung per Link"
+
+#: includes/class-wc-trusted-shops-admin.php:654
+msgctxt "trusted-shops"
+msgid "Allows the customer to unsubscribe from Review Requests."
+msgstr ""
+"Erlaubt es dem Kunden sich von Bewertungserinnerungen per Link abzumelden."
+
+#: includes/class-wc-trusted-shops-admin.php:755
+msgctxt "trusted-shops"
+msgid "How does Trusted Shops make your shop better?"
+msgstr "Wie macht Trusted Shops Ihren Shop besser?"
+
+#: includes/class-wc-trusted-shops-admin.php:757
+msgctxt "trusted-shops"
+msgid "Get your account"
+msgstr "Erstellen Sie Ihren Account"
+
+#: includes/class-wc-trusted-shops-admin.php:777
+msgctxt "trusted-shops"
+msgid "Product Reviews on the product detail page in an additional tab"
+msgstr "Produktbewertungen auf der Produktseite in einem separatem Reiter"
+
+#: includes/class-wc-trusted-shops-admin.php:780
+msgctxt "trusted-shops"
+msgid "Show Star-Ratings on the product detail page below your product name"
+msgstr "Bewertungssterne unter dem Produktnamen auf der Produktseite"
+
+#: includes/class-wc-trusted-shops-admin.php:784
+msgctxt "trusted-shops"
+msgid ""
+"Please note: If you want to send review requests through WooCommerce, you "
+"should deactivate automated review requests through Trusted Shops. To do so, "
+"please go to your My Trusted Shops account. Log in and go to Reviews > "
+"Settings and deactivate \"Collect reviews automatically\""
+msgstr ""
+"Bitte beachten Sie: Wenn Sie Bewertungserinnerungen über WooCommerce "
+"versenden möchtest, sollten Sie den Versand von automatischen "
+"Bewertungserinnerungen über Trusted Shops deaktivieren. Gehen Sie dazu in "
+"Ihren My Trusted Shops Account. Loggen Sie sich ein und gehen Sie zu "
+"Bewertungen > Konfiguration und deaktivieren Sie dort „Bewertungen "
+"automatisch sammeln“."
+
+#: includes/class-wc-trusted-shops-admin.php:785
+msgctxt "trusted-shops"
+msgid "To your My Trusted Shops account"
+msgstr "Zu Ihrem My Trusted Shops Account"
+
+#: includes/class-wc-trusted-shops-admin.php:789
+msgctxt "trusted-shops"
+msgid ""
+"Export your customer information here and upload it in the Trusted Shops "
+"Review Collector. To do so go to your My Trusted Shops account. Log in and "
+"go to Reviews > Shop Reviews > Review Collector"
+msgstr ""
+"Exportieren Sie hier die Kundendaten und laden Sie diese im Trusted Shops "
+"Review Collector hoch. Gehen Sie dazu in Ihren My Trusted Shops Account. "
+"Loggen Sie sich ein und gehen Sie zu Bewertungen > Shopbewertungen > Review "
+"Collector"
+
+#: includes/class-wc-trusted-shops-admin.php:790
+msgctxt "trusted-shops"
+msgid "To the Trusted Shops Review Collector"
+msgstr "Zum Trusted Shops Review Collector"
+
+#: includes/class-wc-trusted-shops-admin.php:880
+msgctxt "trusted-shops"
+msgid "Review Collector"
+msgstr "Review Collector"
+
+#: includes/class-wc-trusted-shops-admin.php:882
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"Want to collect reviews for orders that were placed before your Trusted "
+"Shops Integration? No problem. Export old orders here and upload them in "
+"your %s."
+msgstr ""
+"Sie möchten nachträglich Bewertungen zu Bestellungen sammeln, die vor der "
+"Trusted Shops Integration getätigt wurden? Kein Problem. Exportieren Sie "
+"alte Bestellungen hier und laden Sie diese in Ihrem %s hoch."
+
+#: includes/class-wc-trusted-shops-admin.php:882
+msgctxt "trusted-shops"
+msgid "My Trusted Shops account"
+msgstr "My Trusted Shops Account"
+
+#: includes/class-wc-trusted-shops-admin.php:888
+msgctxt "trusted-shops"
+msgid "Export orders"
+msgstr "Bestellungen exportieren"
+
+#: includes/class-wc-trusted-shops-admin.php:888
+msgctxt "trusted-shops"
+msgid ""
+"Export your customer and order information of the last x days and upload "
+"them in your My Trusted Shops Account."
+msgstr ""
+"Exportieren Sie Ihre Kunden- und Bestelldaten der letzten x Tage und laden "
+"Sie diese in Ihrem My Trusted Shops Account hoch."
+
+#: includes/class-wc-trusted-shops-admin.php:892
+msgctxt "trusted-shops"
+msgid "30 days"
+msgstr "30 Tage"
+
+#: includes/class-wc-trusted-shops-admin.php:893
+msgctxt "trusted-shops"
+msgid "60 days"
+msgstr "60 Tage"
+
+#: includes/class-wc-trusted-shops-admin.php:894
+msgctxt "trusted-shops"
+msgid "90 days"
+msgstr "90 Tage"
+
+#: includes/class-wc-trusted-shops-admin.php:896
+#, php-format
+msgctxt "trusted-shops"
+msgid "Upload customer and order information %s."
+msgstr "Kunden- und Bestelldaten %s hochladen"
+
+#: includes/class-wc-trusted-shops-admin.php:899
+msgctxt "trusted-shops"
+msgid "Days until reminder mail"
+msgstr "Tage bis zur Erinnerung"
+
+#: includes/class-wc-trusted-shops-admin.php:899
+msgctxt "trusted-shops"
+msgid ""
+"Set the number of days to wait after the order date before having a Review "
+"Request sent to your customers."
+msgstr ""
+"Stellen Sie hier ein, wie viele Tage zwischen der Bestellung und dem Versand "
+"der Bewertungserinnerung liegen soll."
+
+#: includes/class-wc-trusted-shops-admin.php:903
+msgctxt "trusted-shops"
+msgid "Start export"
+msgstr "Export starten"
+
+#: includes/class-wc-trusted-shops-core.php:55
+#: includes/class-wc-trusted-shops-core.php:64
+#: includes/class-wc-ts-dependencies.php:36
+#: includes/class-wc-ts-dependencies.php:45
+msgctxt "trusted-shops"
+msgid "Cheatin’ huh?"
+msgstr "So geht das leider nicht.."
+
+#: includes/class-wc-trusted-shops-core.php:210
+msgctxt "trusted-shops"
+msgid "Yes"
+msgstr "Ja"
+
+#: includes/class-wc-trusted-shops-core.php:210
+msgctxt "trusted-shops"
+msgid "No"
+msgstr "Nein"
+
+#: includes/class-wc-trusted-shops-core.php:258
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"If the App helped you, please leave a %s★★"
+"★★★%s in the Wordpress plugin repository."
+msgstr ""
+"Wenn Ihnen die App hilft, hinterlassen Sie bitte eine "
+"%s★★★★★%s Bewertung in der WordPress Plugin "
+"Bibliothek."
+
+#: includes/class-wc-trusted-shops-core.php:407
+msgctxt "trusted-shops"
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:64
+msgctxt "trusted-shops"
+msgid "Order ID"
+msgstr "Bestellnummer"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:65
+msgctxt "trusted-shops"
+msgid "Order date"
+msgstr "Bestelldatum"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:66
+msgctxt "trusted-shops"
+msgid "# Days"
+msgstr "# Tage"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:67
+msgctxt "trusted-shops"
+msgid "Email"
+msgstr "E-Mail"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:68
+msgctxt "trusted-shops"
+msgid "First name"
+msgstr "Vorname"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:69
+msgctxt "trusted-shops"
+msgid "Last name"
+msgstr "Nachname"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:121
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"Your review reminder e-mail has been cancelled successfully. Return to %s."
+msgstr ""
+"Ihre Bewertungserinnerung wurde erfolgreich deaktiviert. Kehren Sie zurück "
+"zur %s."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:121
+msgctxt "trusted-shops"
+msgid "Home"
+msgstr "Startseite"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:193
+msgctxt "trusted-shops"
+msgid ""
+"Yes, I would like to be reminded via e-mail after {days} day(s) to review my "
+"order. I am able to cancel the reminder at any time by clicking on the "
+"\"cancel review reminder\" link within the order confirmation."
+msgstr ""
+"Ja, ich bin damit einverstanden, eine Erinnerung per E-Mail zur Bewertung "
+"nach {days} Tage(n) zu erhalten. Ich kann mich jederzeit davon abmelden bzw. "
+"widersprechen, indem ich dem Link „von der Bewertungserinnerung abmelden“ in "
+"der Bestellbestätigung folge."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:198
+msgctxt "trusted-shops"
+msgid "Please allow us to send a review reminder by e-mail."
+msgstr ""
+"Bitte akzeptieren Sie den Erhalt einer Bewertungserinnerung per E-Mail."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:201
+msgctxt "trusted-shops"
+msgid "Review reminder"
+msgstr "Bewertungs Erinnerung"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:202
+msgctxt "trusted-shops"
+msgid "Asks the customer to receive a Trusted Shops review reminder."
+msgstr ""
+"Holt die Erlaubnis zum Senden einer einmaligen Trusted Shops "
+"Bewertungserinnerung ein."
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:24
+msgctxt "trusted-shops"
+msgid "Trusted Shops Review Reminder"
+msgstr "Trusted Shops Bewertungs-Erinnerung"
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:25
+msgctxt "trusted-shops"
+msgid ""
+"This E-Mail is being sent to a customer to remind him about the possibility "
+"to leave a review at Trusted Shops."
+msgstr ""
+"Diese E-Mail wird einmalig an Kunden verschickt, um diese an die Abgabe "
+"einer Bewertung bei Trusted Shops zu erinnern."
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:54
+msgctxt "trusted-shops"
+msgid "Please rate your {site_title} order from {order_date}"
+msgstr "Bitte bewerten Sie Ihren Einkauf vom {order_date} bei {site_title}"
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:64
+msgctxt "trusted-shops"
+msgid "Please rate your Order"
+msgstr "Bitte bewerten Sie Ihren Einkauf"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:19
+msgctxt "trusted-shops"
+msgid "Show your TS shop review sticker."
+msgstr "Zeige den Trusted Shops Review Sticker an."
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:21
+msgctxt "trusted-shops"
+msgid "Trusted Shops Shop Review Sticker"
+msgstr "Trusted Shops Shop Review Sticker"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:25
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:46
+msgctxt "trusted-shops"
+msgid "Trusted Shops Reviews"
+msgstr "Trusted Shops Bewertung"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:26
+msgctxt "trusted-shops"
+msgid "Title"
+msgstr "Bezeichnung"
+
+#: src/Package.php:53
+msgctxt "trusted-shops"
+msgid ""
+"Trustbadge Reviews for WooCommerce needs at least WooCommerce version 3.1 to "
+"run."
+msgstr ""
+"Trustbadge Reviews for WooCommerce benötigt mind. WooCommerce Version 3.1 um "
+"zu starten."
+
+#: templates/emails/customer-trusted-shops.php:18
+#: templates/emails/plain/customer-trusted-shops.php:12
+#, php-format
+msgctxt "trusted-shops"
+msgid "Dear %s %s,"
+msgstr "Sehr geehrte(r) %s %s,"
+
+#: templates/emails/customer-trusted-shops.php:19
+#: templates/emails/plain/customer-trusted-shops.php:14
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"You have recently shopped at %s. Thank you! We would be glad if you spent "
+"some time to write a review about your order. To do so please follow follow "
+"the link."
+msgstr ""
+"Sie haben vor einiger Zeit bei %s eingekauft. Vielen Dank! Wir wären froh "
+"darüber, wenn Sie sich die Zeit nehmen und Ihren Einkauf bewerten würden. Um "
+"dies nun zu tun, klicken Sie bitte auf den nachfolgenden Link."
+
+#: templates/emails/customer-trusted-shops.php:22
+msgctxt "trusted-shops"
+msgid "Rate Order now"
+msgstr "Einkauf jetzt bewerten"
+
+#~ msgctxt "trusted-shops"
+#~ msgid "Duplicate Plugin installation"
+#~ msgstr "Doppelte Plugin Installation"
+
+#, php-format
+#~ msgctxt "trusted-shops"
+#~ msgid ""
+#~ "It seems like you've installed WooCommerce Germanized and Trustbadge "
+#~ "Reviews for WooCommerce. Please deactivate Trustbadge Reviews for "
+#~ "WooCommerce as long as you are using WooCommerce Germanized. You can "
+#~ "manage your Trusted Shops configuration within your %s."
+#~ msgstr ""
+#~ "Es scheint als hättest du WooCommerce Germanized und Trustbadge Reviews "
+#~ "for WooCommerce installiert. Bitte deaktiviere das Trustbadge Reviews "
+#~ "Plugin solange du WooCommerce Germanized verwendest. Du kannst deine "
+#~ "Trusted Shops Konfiguration in deinen %s verwalten."
+
+#~ msgctxt "trusted-shops"
+#~ msgid "Germanized settings"
+#~ msgstr "Germanized Einstellungen"
+
+#~ msgctxt "trusted-shops"
+#~ msgid "Deactivate standalone version"
+#~ msgstr "Deaktiviere die Standalone-Version"
+
+#~ msgctxt "trusted-shops"
+#~ msgid "(WooCommerce Product Reviews will be replaced)"
+#~ msgstr "(WooCommerce Produktbewertungen werden ersetzt)"
+
+#, php-format
+#~ msgid ""
+#~ "Please install WooCommerce before "
+#~ "installing WooCommerce Germanized. Thank you!"
+#~ msgstr ""
+#~ "Bitte installiere WooCommerce bevor "
+#~ "du WooCommerce Germanized installierst. Vielen Dank!"
diff --git a/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-fr_FR.mo b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-fr_FR.mo
new file mode 100644
index 000000000..19ef22d77
Binary files /dev/null and b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-fr_FR.mo differ
diff --git a/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-fr_FR.po b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-fr_FR.po
new file mode 100644
index 000000000..7e00b75f8
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/i18n/languages/woocommerce-trusted-shops-fr_FR.po
@@ -0,0 +1,1165 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: WooCommerce Trusted Shops\n"
+"POT-Creation-Date: 2019-01-14 15:41+0100\n"
+"PO-Revision-Date: 2019-02-18 15:10+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.1\n"
+"X-Poedit-Basepath: ../..\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-KeywordsList: __;_e;__ngettext:1,2;_n:1,2;__ngettext_noop:1,2;"
+"_n_noop:1,2;_c,_nc:4c,1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;"
+"esc_attr_e;esc_html_e;esc_html__\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-SearchPathExcluded-0: node_modules\n"
+
+#: includes/admin/views/html-duplicate-plugin-notice.php:19
+msgctxt "trusted-shops"
+msgid "Duplicate Plugin installation"
+msgstr "Plugins installés en double"
+
+#: includes/admin/views/html-duplicate-plugin-notice.php:21
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"It seems like you've installed WooCommerce Germanized and Trustbadge Reviews "
+"for WooCommerce. Please deactivate Trustbadge Reviews for WooCommerce as "
+"long as you are using WooCommerce Germanized. You can manage your Trusted "
+"Shops configuration within your %s."
+msgstr ""
+"Il semblerait que vous ayez installé WooCommerce Germanized et Trustbadge "
+"Reviews for WooCommerce. Veuillez s’il vous plait désactiver Trustbadge "
+"Reviews for WooCommerce tant que vous utilisez WooCommerce Germanized. Vous "
+"pouvez modifier votre configuration Trusted Shops au sein de vos %s."
+
+#: includes/admin/views/html-duplicate-plugin-notice.php:21
+msgctxt "trusted-shops"
+msgid "Germanized settings"
+msgstr "Germanized paramètres"
+
+#: includes/admin/views/html-duplicate-plugin-notice.php:23
+msgctxt "trusted-shops"
+msgid "Deactivate standalone version"
+msgstr "Désactivez la version standalone"
+
+#: includes/admin/views/html-notice-dependencies.php:16
+msgctxt "trusted-shops"
+msgid "Dependencies Missing or Outdated"
+msgstr "Composants manquantes ou obsolètes"
+
+#: includes/admin/views/html-notice-dependencies.php:24
+msgctxt "trusted-shops"
+msgid ""
+"To use WooCommerce Trusted Shops you may at first install the following "
+"plugins:"
+msgstr ""
+"Pour utiliser Trusted Shops for WooCommerce, il vous faut d’abord installer "
+"les plugins suivants :"
+
+#: includes/admin/views/html-notice-dependencies.php:28
+#, php-format
+msgctxt "trusted-shops"
+msgid "Install %s"
+msgstr "Installer %s"
+
+#: includes/admin/views/html-notice-dependencies.php:36
+msgctxt "trusted-shops"
+msgid ""
+"To use WooCommerce Trusted Shops you may at first update the following "
+"plugins to a newer version:"
+msgstr ""
+"Pour utiliser Trusted Shops for WooCommerce, vous devez d’abord actualiser "
+"les plugins suivants et installer une version plus récente:"
+
+#: includes/admin/views/html-notice-dependencies.php:40
+#, php-format
+msgctxt "trusted-shops"
+msgid "%s required in at least version %s"
+msgstr "%s requiert la version %s ou supérieure"
+
+#: includes/admin/views/html-notice-dependencies.php:49
+msgctxt "trusted-shops"
+msgid "Check for Updates"
+msgstr "Rechercher de mises à jour"
+
+#: includes/admin/views/html-notice-dependencies.php:50
+msgctxt "trusted-shops"
+msgid "or"
+msgstr "ou"
+
+#: includes/admin/views/html-notice-dependencies.php:51
+msgctxt "trusted-shops"
+msgid "Install an older version"
+msgstr "Installer une version antérieure"
+
+#: includes/admin/views/html-notice-update.php:12
+msgctxt "trusted-shops"
+msgid ""
+"WooCommerce Trusted Shops Data Update Required – We "
+"just need to update your install to the latest version"
+msgstr ""
+"Mise à jour de la base de données Trusted Shops WooCommerce requise"
+"strong> – Nous devons mettre à jour votre installation vers la version "
+"la plus récente."
+
+#: includes/admin/views/html-notice-update.php:13
+msgctxt "trusted-shops"
+msgid "Run the updater"
+msgstr "Démarrer l’actualisation"
+
+#: includes/admin/views/html-notice-update.php:17
+msgctxt "trusted-shops"
+msgid ""
+"It is strongly recommended that you backup your database before proceeding. "
+"Are you sure you wish to run the updater now?"
+msgstr ""
+"Nous vous recommandons fortement de sauvegarder votre base de données avant "
+"de procéder à la mise à jour. Êtes-vous sûr de vouloir effectuer la mise à "
+"jour maintenant?"
+
+#: includes/admin/views/html-wpml-notice.php:11
+msgctxt "trusted-shops"
+msgid "WPML Support"
+msgstr "Prise en charge WPML"
+
+#: includes/admin/views/html-wpml-notice.php:14
+msgctxt "trusted-shops"
+msgid ""
+"These settings serve as default settings for all your languages. To adjust "
+"the settings for a certain language, please switch your admin language "
+"through the WPML language switcher and adjust the corresponding settings."
+msgstr ""
+"Ces paramètres sont les paramètres par défaut pour toutes vos langues. Afin "
+"d’ajuster ces paramètres à une certaine langue, il vous faut changer votre "
+"langue système à l’aide du plug-in multilingue WPML et adapter les "
+"paramètres correspondants."
+
+#: includes/admin/views/html-wpml-notice.php:16
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"These settings apply for your %s shop. To adjust settings for another "
+"language, please switch your admin language through the WPML language "
+"switcher."
+msgstr ""
+"Ces paramètres s’appliquent pour votre boutique %s. Pour adapter les "
+"paramètres à une autre langue, veuillez changer votre langue système à "
+"l’aide du plug-in multilingue WPML."
+
+#: includes/class-wc-trusted-shops-admin.php:90
+#: includes/class-wc-trusted-shops-admin.php:127
+msgctxt "trusted-shops"
+msgid "GTIN"
+msgstr "GTIN"
+
+#: includes/class-wc-trusted-shops-admin.php:90
+#: includes/class-wc-trusted-shops-admin.php:127
+msgctxt "trusted-shops"
+msgid ""
+"ID that allows your products to be identified worldwide. If you want to "
+"display your Trusted Shops Product Reviews in Google Shopping and paid "
+"Google adverts, Google needs the GTIN."
+msgstr ""
+"Le GTIN est le numéro d’identification qui permet d’identifier sans "
+"équivoque les produits dans le monde entier. Google a besoin de l’attribut "
+"GTIN pour afficher vos avis produits dans Google Shopping et dans les "
+"annonces payantes Google."
+
+#: includes/class-wc-trusted-shops-admin.php:94
+#: includes/class-wc-trusted-shops-admin.php:128
+msgctxt "trusted-shops"
+msgid "MPN"
+msgstr "MPN"
+
+#: includes/class-wc-trusted-shops-admin.php:94
+#: includes/class-wc-trusted-shops-admin.php:128
+msgctxt "trusted-shops"
+msgid ""
+"If you don't have a GTIN for your products, you can pass the brand name and "
+"the MPN on to Google to use the Trusted Shops Google Integration."
+msgstr ""
+"Si vos produits n’ont pas de GTIN, vous pouvez indiquer le nom de la marque "
+"ainsi que le MPN pour utiliser l’intégration Google."
+
+#: includes/class-wc-trusted-shops-admin.php:152
+msgctxt "trusted-shops"
+msgid "Brand"
+msgstr "Marque"
+
+#: includes/class-wc-trusted-shops-admin.php:192
+msgctxt "trusted-shops"
+msgid "This field is mandatory"
+msgstr "Ce champ est obligatoire"
+
+#: includes/class-wc-trusted-shops-admin.php:199
+msgctxt "trusted-shops"
+msgid "Trusted Shops Options"
+msgstr "Options Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:206
+msgctxt "trusted-shops"
+msgid "Arial"
+msgstr "Arial"
+
+#: includes/class-wc-trusted-shops-admin.php:207
+msgctxt "trusted-shops"
+msgid "Geneva"
+msgstr "Geneva"
+
+#: includes/class-wc-trusted-shops-admin.php:208
+msgctxt "trusted-shops"
+msgid "Georgia"
+msgstr "Georgia"
+
+#: includes/class-wc-trusted-shops-admin.php:209
+msgctxt "trusted-shops"
+msgid "Helvetica"
+msgstr "Helvetica"
+
+#: includes/class-wc-trusted-shops-admin.php:210
+msgctxt "trusted-shops"
+msgid "Sans-serif"
+msgstr "Sans-serif"
+
+#: includes/class-wc-trusted-shops-admin.php:211
+msgctxt "trusted-shops"
+msgid "Serif"
+msgstr "Serif"
+
+#: includes/class-wc-trusted-shops-admin.php:212
+msgctxt "trusted-shops"
+msgid "Trebuchet MS"
+msgstr "Trebuchet MS"
+
+#: includes/class-wc-trusted-shops-admin.php:213
+msgctxt "trusted-shops"
+msgid "Verdana"
+msgstr "Verdana"
+
+#: includes/class-wc-trusted-shops-admin.php:236
+msgctxt "trusted-shops"
+msgid "Trusted Shops Integration"
+msgstr "Intégration Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:237
+#, php-format
+msgctxt "trusted-shops"
+msgid "Do you need help with integrating your Trustbadge? %s"
+msgstr "Avez-vous besoin d’aide pour intégrer le Trustbadge? %s"
+
+#: includes/class-wc-trusted-shops-admin.php:237
+msgctxt "trusted-shops"
+msgid "To the step-by-step instructions"
+msgstr "Accéder au guide"
+
+#: includes/class-wc-trusted-shops-admin.php:243
+msgctxt "trusted-shops"
+msgid "Trusted Shops ID"
+msgstr "Identifiants Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:244
+msgctxt "trusted-shops"
+msgid ""
+"The Trusted Shops ID is a unique identifier for your shop. You can find your "
+"Trusted Shops ID in your My Trusted Shops account."
+msgstr ""
+"Les identifiants Trusted Shops servent à identifier votre boutique en ligne "
+"sur Trusted Shops. Vous trouverez vos identifiants Trusted Shops (TSID) dans "
+"votre compte My Trusted Shops."
+
+#: includes/class-wc-trusted-shops-admin.php:253
+msgctxt "trusted-shops"
+msgid "Edit Mode"
+msgstr "Mode édition"
+
+#: includes/class-wc-trusted-shops-admin.php:255
+#: includes/class-wc-trusted-shops-admin.php:316
+#: includes/class-wc-trusted-shops-admin.php:439
+msgctxt "trusted-shops"
+msgid ""
+"The advanced configuration is for users with programming skills. Here you "
+"can create even more individual settings."
+msgstr ""
+"Le mode expert est conçu pour des utilisateurs ayant de bonnes connaissances "
+"en programmation. Cliquer ici, pour avoir accès à des réglages personnalisés."
+
+#: includes/class-wc-trusted-shops-admin.php:259
+msgctxt "trusted-shops"
+msgid "Standard configuration"
+msgstr "Mode standard"
+
+#: includes/class-wc-trusted-shops-admin.php:260
+msgctxt "trusted-shops"
+msgid "Advanced configuration"
+msgstr "Mode expert"
+
+#: includes/class-wc-trusted-shops-admin.php:268
+msgctxt "trusted-shops"
+msgid "Configure your Trustbadge"
+msgstr "Configurer le Trustbadge"
+
+#: includes/class-wc-trusted-shops-admin.php:274
+msgctxt "trusted-shops"
+msgid "Display Trustbadge"
+msgstr "Afficher le Trustbadge"
+
+#: includes/class-wc-trusted-shops-admin.php:276
+msgctxt "trusted-shops"
+msgid "Display the Trustbadge on all the pages of your shop."
+msgstr ""
+"Afficher le Trustbadge sur toutes les pages de votre boutique en ligne."
+
+#: includes/class-wc-trusted-shops-admin.php:283
+msgctxt "trusted-shops"
+msgid "Variant"
+msgstr "Variante"
+
+#: includes/class-wc-trusted-shops-admin.php:285
+msgctxt "trusted-shops"
+msgid "You can display your Trustbadge with or without Review Stars."
+msgstr ""
+"Vous pouvez afficher le Trustbadge avec ou sans les étoiles d’évaluation."
+
+#: includes/class-wc-trusted-shops-admin.php:289
+#: includes/class-wc-trusted-shops-admin.php:780
+msgctxt "trusted-shops"
+msgid "Display Trustbadge with review stars"
+msgstr "Afficher le Trustbadge avec les étoiles d’évaluation"
+
+#: includes/class-wc-trusted-shops-admin.php:290
+#: includes/class-wc-trusted-shops-admin.php:784
+msgctxt "trusted-shops"
+msgid "Display Trustbadge without review stars"
+msgstr "Afficher le Trustbadge sans les étoiles d’évaluation"
+
+#: includes/class-wc-trusted-shops-admin.php:296
+msgctxt "trusted-shops"
+msgid "Vertical Offset"
+msgstr "Décalage vertical"
+
+#: includes/class-wc-trusted-shops-admin.php:297
+msgctxt "trusted-shops"
+msgid ""
+"Choose the distance that the Trustbadge will appear from the bottom-right "
+"corner of the screen."
+msgstr ""
+"Choisissez l’espacement du Trustbadge® par rapport au bord inferieur droit "
+"de l’écran."
+
+#: includes/class-wc-trusted-shops-admin.php:300
+#: includes/class-wc-trusted-shops-admin.php:509
+#: includes/class-wc-trusted-shops-admin.php:560
+#: includes/class-wc-trusted-shops-admin.php:575
+msgid "px"
+msgstr "px"
+
+#: includes/class-wc-trusted-shops-admin.php:306
+#: includes/class-wc-trusted-shops-admin.php:516
+#: includes/class-wc-trusted-shops-admin.php:566
+#: includes/class-wc-trusted-shops-admin.php:582
+#, php-format
+msgctxt "trusted-shops"
+msgid "Please choose a non-negative number (at least %d)"
+msgstr "Veuillez choisir un nombre positif (> %d)"
+
+#: includes/class-wc-trusted-shops-admin.php:312
+msgctxt "trusted-shops"
+msgid "Trustbadge code"
+msgstr "Trustbadge code"
+
+#: includes/class-wc-trusted-shops-admin.php:324
+msgctxt "trusted-shops"
+msgid "Configure your Shop Reviews"
+msgstr "Configurer les avis site"
+
+#: includes/class-wc-trusted-shops-admin.php:330
+msgctxt "trusted-shops"
+msgid "Display Shop Review Sticker"
+msgstr "Afficher la vignette avis site"
+
+#: includes/class-wc-trusted-shops-admin.php:331
+msgctxt "trusted-shops"
+msgid ""
+"To display the Shop Review Sticker, you have to assign the widget \"Trusted "
+"Shops Review Sticker\"."
+msgstr ""
+"Pour afficher la vignette avis site, veuillez sélectionner le widget "
+"« Vignette avis site Trusted Shops »."
+
+#: includes/class-wc-trusted-shops-admin.php:332
+#, php-format
+msgctxt "trusted-shops"
+msgid "Assign widget %s"
+msgstr "Assigner le widget %s"
+
+#: includes/class-wc-trusted-shops-admin.php:332
+#: includes/class-wc-trusted-shops-admin.php:605
+#: includes/class-wc-trusted-shops-admin.php:905
+msgctxt "trusted-shops"
+msgid "here"
+msgstr "ici"
+
+#: includes/class-wc-trusted-shops-admin.php:340
+#: includes/class-wc-trusted-shops-admin.php:489
+msgctxt "trusted-shops"
+msgid "Background color"
+msgstr "Couleur de l’arrière-plan"
+
+#: includes/class-wc-trusted-shops-admin.php:341
+msgctxt "trusted-shops"
+msgid "Choose the background color for your Review Sticker."
+msgstr "Choisir la couleur de l’arrière-plan de la vignette d’avis."
+
+#: includes/class-wc-trusted-shops-admin.php:348
+msgctxt "trusted-shops"
+msgid "Font"
+msgstr "Police"
+
+#: includes/class-wc-trusted-shops-admin.php:350
+msgctxt "trusted-shops"
+msgid "Choose the font for your Review Sticker."
+msgstr "Choisir la police de la vignette d’avis."
+
+#: includes/class-wc-trusted-shops-admin.php:357
+msgctxt "trusted-shops"
+msgid "Number of reviews displayed"
+msgstr "Nombre d’avis"
+
+#: includes/class-wc-trusted-shops-admin.php:358
+msgctxt "trusted-shops"
+msgid ""
+"Display x alternating Shop Reviews in your Shop Review Sticker. You can "
+"display between 1 and 5 alternating Shop Reviews."
+msgstr ""
+"Afficher x avis site en alternance sur votre vignette avis site. Vous pouvez "
+"afficher en alternance de 1 à 5 avis."
+
+#: includes/class-wc-trusted-shops-admin.php:361
+msgctxt "trusted-shops"
+msgid "Show x alternating reviews"
+msgstr "Afficher x avis en alternance"
+
+#: includes/class-wc-trusted-shops-admin.php:368
+#: includes/class-wc-trusted-shops-admin.php:385
+#, php-format
+msgctxt "trusted-shops"
+msgid "Please choose a non-negative number between %d and %d"
+msgstr "Veuillez choisir un nombre positif entre %d et %d"
+
+#: includes/class-wc-trusted-shops-admin.php:374
+msgctxt "trusted-shops"
+msgid "Minimum rating displayed"
+msgstr "Note minimale affichée"
+
+#: includes/class-wc-trusted-shops-admin.php:375
+msgctxt "trusted-shops"
+msgid "Only show Shop Reviews with a minimum rating of x stars. "
+msgstr "N’afficher que les avis site avec x étoiles ou plus. "
+
+#: includes/class-wc-trusted-shops-admin.php:378
+msgctxt "trusted-shops"
+msgid "Star(s)"
+msgstr "étoile(s)"
+
+#: includes/class-wc-trusted-shops-admin.php:391
+msgctxt "trusted-shops"
+msgid "Sticker code"
+msgstr "Code des vignettes avis clients"
+
+#: includes/class-wc-trusted-shops-admin.php:395
+#: includes/class-wc-trusted-shops-admin.php:523
+#: includes/class-wc-trusted-shops-admin.php:588
+msgctxt "trusted-shops"
+msgid ""
+"The advanced configuration is for users with programming skills. Here you "
+"can perform even more individual settings."
+msgstr ""
+"Le mode expert est conçu pour des utilisateurs ayant de bonnes connaissances "
+"en programmation. Cliquer ici, pour avoir accès à des réglages personnalisés."
+
+#: includes/class-wc-trusted-shops-admin.php:401
+msgctxt "trusted-shops"
+msgid "Google Organic Search"
+msgstr "Résultats de recherche organiques"
+
+#: includes/class-wc-trusted-shops-admin.php:402
+msgctxt "trusted-shops"
+msgid ""
+"Activate this option to give Google the opportunity to show your Shop "
+"Reviews in Google organic search results."
+msgstr ""
+"Veuillez activer cette fonctionnalité pour permettre à Google d’afficher vos "
+"avis site dans les résultats de recherche organiques."
+
+#: includes/class-wc-trusted-shops-admin.php:403
+msgctxt "trusted-shops"
+msgid ""
+"By activating this option, rich snippets will be integrated in the selected "
+"pages so your shop review stars may be displayed in Google organic search "
+"results. If you use Product Reviews and already activated rich snippets in "
+"expert mode, we recommend integrating rich snippets for Shop Reviews on "
+"category pages only."
+msgstr ""
+"En activant cette option, les rich snippets seront intégrés dans les pages "
+"sélectionnées, de façon à ce que vos étoiles d’évaluation puissent être "
+"affichées dans les résultats de recherche organiques de Google. Si vous avez "
+"activé les avis produits et vous avez déjà activé les rich snippets dans le "
+"mode expert, nous vous conseillons de n’activer les rich snippets pour les "
+"avis site que sur la page de catégorie."
+
+#: includes/class-wc-trusted-shops-admin.php:410
+msgctxt "trusted-shops"
+msgid "Activate rich snippets on"
+msgstr "Activer les rich snippets sur"
+
+#: includes/class-wc-trusted-shops-admin.php:411
+msgctxt "trusted-shops"
+msgid "category pages"
+msgstr "les pages de catégories"
+
+#: includes/class-wc-trusted-shops-admin.php:419
+msgctxt "trusted-shops"
+msgid "product pages"
+msgstr "les pages produit"
+
+#: includes/class-wc-trusted-shops-admin.php:427
+msgctxt "trusted-shops"
+msgid "homepage (not recommended)"
+msgstr "la page d’accueil (non conseillé)"
+
+#: includes/class-wc-trusted-shops-admin.php:435
+msgctxt "trusted-shops"
+msgid "Rich snippets code"
+msgstr "Code pour les Rich Snippets"
+
+#: includes/class-wc-trusted-shops-admin.php:447
+msgctxt "trusted-shops"
+msgid "Configure your Product Reviews "
+msgstr "Configurer les avis produits "
+
+#: includes/class-wc-trusted-shops-admin.php:448
+#, php-format
+msgctxt "trusted-shops"
+msgid "To use Product Reviews, activate them in your %s first."
+msgstr ""
+"Pour pouvoir utiliser les avis produits, veuillez d’abord activer l’option "
+"%s."
+
+#: includes/class-wc-trusted-shops-admin.php:448
+msgctxt "trusted-shops"
+msgid "Trusted Shops package"
+msgstr "Services Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:454
+msgctxt "trusted-shops"
+msgid "Collect Product Reviews"
+msgstr "Collecter des avis produits"
+
+#: includes/class-wc-trusted-shops-admin.php:455
+msgctxt "trusted-shops"
+msgid "(WooCommerce Product Reviews will be replaced)"
+msgstr "(Les avis produits WooCommerce seront remplacés)"
+
+#: includes/class-wc-trusted-shops-admin.php:456
+msgctxt "trusted-shops"
+msgid ""
+"Show Product Reviews on the product page in a separate tab, just as shown on "
+"the picture on the right."
+msgstr "Afficher les avis produits dans un onglet séparé sur la page produit."
+
+#: includes/class-wc-trusted-shops-admin.php:464
+msgctxt "trusted-shops"
+msgid "Reviews"
+msgstr "Avis clients"
+
+#: includes/class-wc-trusted-shops-admin.php:465
+#: includes/class-wc-trusted-shops-admin.php:474
+msgctxt "trusted-shops"
+msgid "You can choose a name for the tab with your Product Reviews."
+msgstr ""
+"Vous pouvez choisir le nom de l’onglet dans lequel vous souhaitez afficher "
+"vos avis produits."
+
+#: includes/class-wc-trusted-shops-admin.php:466
+msgctxt "trusted-shops"
+msgid "Show Product Reviews on the product detail page in an additional tab."
+msgstr "Afficher les avis produits dans un onglet séparé sur la page produit."
+
+#: includes/class-wc-trusted-shops-admin.php:473
+msgctxt "trusted-shops"
+msgid "Name of Product Reviews tab"
+msgstr "Nom de l’onglet"
+
+#: includes/class-wc-trusted-shops-admin.php:477
+msgctxt "trusted-shops"
+msgid "Product reviews"
+msgstr "Avis produits"
+
+#: includes/class-wc-trusted-shops-admin.php:481
+msgctxt "trusted-shops"
+msgid "Border color"
+msgstr "Couleur du cadre"
+
+#: includes/class-wc-trusted-shops-admin.php:482
+msgctxt "trusted-shops"
+msgid "Set the color for the frame around your Product Reviews."
+msgstr "Choisir la couleur du cadre des avis produits."
+
+#: includes/class-wc-trusted-shops-admin.php:490
+msgctxt "trusted-shops"
+msgid "Set the background color for your Product Reviews."
+msgstr "Choisir la couleur de l’arrière-plan des avis produits."
+
+#: includes/class-wc-trusted-shops-admin.php:497
+#: includes/class-wc-trusted-shops-admin.php:547
+msgctxt "trusted-shops"
+msgid "Star color"
+msgstr "Couleur des étoiles"
+
+#: includes/class-wc-trusted-shops-admin.php:498
+msgctxt "trusted-shops"
+msgid "Set the color for the Product Review stars in your Product Reviews tab."
+msgstr ""
+"Choisi la couleur des étoiles qui seront affichées dans l’onglet avec les "
+"avis produits."
+
+#: includes/class-wc-trusted-shops-admin.php:505
+#: includes/class-wc-trusted-shops-admin.php:555
+msgctxt "trusted-shops"
+msgid "Star size"
+msgstr "Dimension des étoiles"
+
+#: includes/class-wc-trusted-shops-admin.php:510
+msgctxt "trusted-shops"
+msgid "Set the size for the Product Review stars in your Product Reviews tab."
+msgstr ""
+"Choisir la dimension des étoiles qui seront affichées dans l’onglet avec les "
+"avis produits."
+
+#: includes/class-wc-trusted-shops-admin.php:521
+msgctxt "trusted-shops"
+msgid "Product Sticker Code"
+msgstr "Code des vignettes avis produits"
+
+#: includes/class-wc-trusted-shops-admin.php:530
+#: includes/class-wc-trusted-shops-admin.php:596
+msgctxt "trusted-shops"
+msgid "jQuerySelector"
+msgstr "jQuerySelector"
+
+#: includes/class-wc-trusted-shops-admin.php:531
+msgctxt "trusted-shops"
+msgid ""
+"Please choose where your Product Reviews shall be displayed on the Product "
+"detail page."
+msgstr ""
+"Veuillez choisir l’endroit où les Avis Produits seront affichés sur la page "
+"produit"
+
+#: includes/class-wc-trusted-shops-admin.php:538
+msgctxt "trusted-shops"
+msgid "Rating stars"
+msgstr "Étoiles d’évaluation"
+
+#: includes/class-wc-trusted-shops-admin.php:539
+msgctxt "trusted-shops"
+msgid "Show star ratings on the product detail page below your product name."
+msgstr ""
+"Afficher les étoiles d’évaluation sur la page produit en dessous du nom du "
+"produit."
+
+#: includes/class-wc-trusted-shops-admin.php:540
+msgctxt "trusted-shops"
+msgid ""
+"Display Product Review stars on product pages below the product name, just "
+"as shown in the picture on the right."
+msgstr ""
+"Afficher les étoiles d’évaluation sur la page produit en dessous du nom du "
+"produit, comme illustré par l’image ci-contre."
+
+#: includes/class-wc-trusted-shops-admin.php:549
+msgctxt "trusted-shops"
+msgid ""
+"Set the color for the review stars, that are displayed on the product page, "
+"below your product name."
+msgstr ""
+"Choisir la couleur des étoiles qui seront affichées sur la page produit en "
+"dessous du nom du produit."
+
+#: includes/class-wc-trusted-shops-admin.php:557
+msgctxt "trusted-shops"
+msgid ""
+"Set the size for the review stars that are displayed on the product page, "
+"below your product name."
+msgstr ""
+"Choisir la dimension des étoiles qui seront affichées sur la page produit en "
+"dessous du nom du produit."
+
+#: includes/class-wc-trusted-shops-admin.php:571
+msgctxt "trusted-shops"
+msgid "Font size"
+msgstr "Taille de la police"
+
+#: includes/class-wc-trusted-shops-admin.php:573
+msgctxt "trusted-shops"
+msgid "Set the font size for the text that goes with your review stars."
+msgstr ""
+"Choisir la taille de la police du texte correspondant à vos étoiles "
+"d’évaluation."
+
+#: includes/class-wc-trusted-shops-admin.php:587
+msgctxt "trusted-shops"
+msgid "Product Review Code"
+msgstr "Code pour les étoiles d’évaluation"
+
+#: includes/class-wc-trusted-shops-admin.php:597
+msgctxt "trusted-shops"
+msgid ""
+"Please choose where your Product Review Stars shall be displayed on the "
+"Product Detail page."
+msgstr ""
+"Veuillez choisir l’endroit où les étoiles des Avis Produit seront affichées "
+"sur la page produit"
+
+#: includes/class-wc-trusted-shops-admin.php:604
+msgctxt "trusted-shops"
+msgid "Brand attribute"
+msgstr "Attribut marque"
+
+#: includes/class-wc-trusted-shops-admin.php:605
+#, php-format
+msgctxt "trusted-shops"
+msgid "Create brand attribute %s"
+msgstr "Créer %s un attribut marque"
+
+#: includes/class-wc-trusted-shops-admin.php:606
+msgctxt "trusted-shops"
+msgid ""
+"Brand name of the product. By passing this information on to Google, you "
+"improve your chances of having Google identify your products. Assign your "
+"brand attribute. If your products don't have a GTIN, you can pass on the "
+"brand name and the MPN to use Google Integration."
+msgstr ""
+"Nom de la marque du produit. En indiquant cette variable, il est plus "
+"probable que Google identifie clairement vos produits. Veuillez assigner "
+"l’attribut marque ici. Si vos produits n’ont pas de GTIN, vous pouvez "
+"indiquer le nom de la marque ainsi que le MPN pour utiliser l’intégration "
+"Google."
+
+#: includes/class-wc-trusted-shops-admin.php:612
+msgctxt "trusted-shops"
+msgid "None"
+msgstr "Aucun"
+
+#: includes/class-wc-trusted-shops-admin.php:623
+msgctxt "trusted-shops"
+msgid "Configure your Review Requests"
+msgstr "Configurer vos rappels d’avis"
+
+#: includes/class-wc-trusted-shops-admin.php:624
+msgctxt "trusted-shops"
+msgid ""
+"7 days after an order has been placed, Trusted Shops automatically sends an "
+"invite to your customers. If you want to set a different time for sending "
+"automatic Review Requests, please activate the option below. If you want to "
+"send review requests with legal certainty, you need your customers' consent "
+"to receive Review Requests. You also have to include an option to "
+"unsubscribe."
+msgstr ""
+"Trusted Shops envoie automatiquement un rappel d’avis à vos clients 7 jours "
+"après leur commande. Si vous préférez décider vous-même du moment auquel "
+"vous souhaitez envoyer vos demandes d’avis, cochez l’option ci-dessous. Pour "
+"envoyer des rappels d’avis en conformité avec la loi, il faut obtenir le "
+"consentement exprès des clients à la réception d’e-mails de demande d’avis. "
+"Vous devez également donner à vos clients un moyen de se désabonner des "
+"rappels d’avis."
+
+#: includes/class-wc-trusted-shops-admin.php:630
+msgctxt "trusted-shops"
+msgid "Enable Review Requests"
+msgstr "Activer les demandes d’avis"
+
+#: includes/class-wc-trusted-shops-admin.php:639
+msgctxt "trusted-shops"
+msgid "WooCommerce status"
+msgstr "État de la commande"
+
+#: includes/class-wc-trusted-shops-admin.php:640
+msgctxt "trusted-shops"
+msgid ""
+"We recommend choosing the order status that you set when your products have "
+"been shipped."
+msgstr ""
+"Sélectionner l’état de la commande que vous avez défini au moment de l’envoi "
+"de la commande."
+
+#: includes/class-wc-trusted-shops-admin.php:648
+msgctxt "trusted-shops"
+msgid "Days until Review Request"
+msgstr "Jours avant envoi du rappel"
+
+#: includes/class-wc-trusted-shops-admin.php:649
+msgctxt "trusted-shops"
+msgid ""
+"Set the number of days to wait after an order has reached the order status "
+"you selected above before having a review request sent to your customers."
+msgstr ""
+"Indiquer ici après combien de jours le rappel d’évaluation est envoyé à "
+"l’acheteur après avoir atteint l’état de la commande sélectionné ci-dessus."
+
+#: includes/class-wc-trusted-shops-admin.php:661
+msgctxt "trusted-shops"
+msgid "Permission via checkbox"
+msgstr "Consentement par case à cocher"
+
+#: includes/class-wc-trusted-shops-admin.php:662
+msgctxt "trusted-shops"
+msgid ""
+"If the checkbox is activated, only customers who gave their consent will "
+"receive Review Requests."
+msgstr ""
+"Si la case est cochée, seuls les clients ayant donné leur consentement "
+"recevront un rappel d’avis par e-mail."
+
+#: includes/class-wc-trusted-shops-admin.php:666
+msgctxt "trusted-shops"
+msgid "Edit checkbox"
+msgstr "Modifier la case à cocher"
+
+#: includes/class-wc-trusted-shops-admin.php:670
+msgctxt "trusted-shops"
+msgid "Unsubscribe via link"
+msgstr "Désinscription via lien"
+
+#: includes/class-wc-trusted-shops-admin.php:671
+msgctxt "trusted-shops"
+msgid "Allows the customer to unsubscribe from Review Requests."
+msgstr ""
+"Cette option permet aux clients de se désabonner des rappels d’avis en "
+"cliquant sur un lien."
+
+#: includes/class-wc-trusted-shops-admin.php:772
+msgctxt "trusted-shops"
+msgid "How does Trusted Shops make your shop better?"
+msgstr ""
+"En quoi les produits Trusted Shops contribuent-ils à optimiser votre "
+"boutique en ligne?"
+
+#: includes/class-wc-trusted-shops-admin.php:774
+msgctxt "trusted-shops"
+msgid "Get your account"
+msgstr "Créer un compte"
+
+#: includes/class-wc-trusted-shops-admin.php:794
+msgctxt "trusted-shops"
+msgid "Product Reviews on the product detail page in an additional tab"
+msgstr "Avis produits dans l’onglet sur la page produit"
+
+#: includes/class-wc-trusted-shops-admin.php:797
+msgctxt "trusted-shops"
+msgid "Show Star-Ratings on the product detail page below your product name"
+msgstr "Étoiles d’évaluation sur la page produit en dessous du nom du produit"
+
+#: includes/class-wc-trusted-shops-admin.php:801
+msgctxt "trusted-shops"
+msgid ""
+"Please note: If you want to send review requests through WooCommerce, you "
+"should deactivate automated review requests through Trusted Shops. To do so, "
+"please go to your My Trusted Shops account. Log in and go to Reviews > "
+"Settings and deactivate \"Collect reviews automatically\""
+msgstr ""
+"Veuillez noter : Si vous souhaitez envoyer des rappels d’avis via "
+"WooCommerce, vous devez désactiver l’envoi de rappels d’avis automatiques "
+"via Trusted Shops. Pour ce faire, rendez-vous dans votre compte My Trusted "
+"Shops. Connectez-vous à votre compte, cliquez sur Avis clients > "
+"Configuration > Collecter les avis et désactivez l’option « Collecter des "
+"avis automatiquement »."
+
+#: includes/class-wc-trusted-shops-admin.php:802
+msgctxt "trusted-shops"
+msgid "To your My Trusted Shops account"
+msgstr "Aller au compte My Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:806
+msgctxt "trusted-shops"
+msgid ""
+"Export your customer information here and upload it in the Trusted Shops "
+"Review Collector. To do so go to your My Trusted Shops account. Log in and "
+"go to Reviews > Shop Reviews > Review Collector"
+msgstr ""
+"Exporter les données de vos clients et de vos commandes des x derniers jours "
+"et importez-les dans votre compte My Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:807
+msgctxt "trusted-shops"
+msgid "To the Trusted Shops Review Collector"
+msgstr "Aller au collecteur d’avis Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:889
+msgctxt "trusted-shops"
+msgid "Review Collector"
+msgstr "Collecteur d’avis Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:891
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"Want to collect reviews for orders that were placed before your Trusted "
+"Shops Integration? No problem. Export old orders here and upload them in "
+"your %s."
+msgstr ""
+"Vous souhaitez demander à des clients de laisser un avis sur des commandes "
+"qu’ils ont passées avant l’intégration de Trusted Shops sur votre site ? Pas "
+"de problème! Exportez les commandes passées et importez-les dans votre %s."
+
+#: includes/class-wc-trusted-shops-admin.php:891
+msgctxt "trusted-shops"
+msgid "My Trusted Shops account"
+msgstr "Compte My Trusted Shops"
+
+#: includes/class-wc-trusted-shops-admin.php:897
+msgctxt "trusted-shops"
+msgid "Export orders"
+msgstr "Exporter les commandes"
+
+#: includes/class-wc-trusted-shops-admin.php:897
+msgctxt "trusted-shops"
+msgid ""
+"Export your customer and order information of the last x days and upload "
+"them in your My Trusted Shops Account."
+msgstr ""
+"Exporter les données de vos clients et de vos commandes des x derniers jours "
+"et importez-les dans votre compte My Trusted Shops."
+
+#: includes/class-wc-trusted-shops-admin.php:901
+msgctxt "trusted-shops"
+msgid "30 days"
+msgstr "30 jours"
+
+#: includes/class-wc-trusted-shops-admin.php:902
+msgctxt "trusted-shops"
+msgid "60 days"
+msgstr "60 jours"
+
+#: includes/class-wc-trusted-shops-admin.php:903
+msgctxt "trusted-shops"
+msgid "90 days"
+msgstr "90 jours"
+
+#: includes/class-wc-trusted-shops-admin.php:905
+#, php-format
+msgctxt "trusted-shops"
+msgid "Upload customer and order information %s."
+msgstr "Cliquez %s pour importer les données de vos clients."
+
+#: includes/class-wc-trusted-shops-admin.php:908
+msgctxt "trusted-shops"
+msgid "Days until reminder mail"
+msgstr "Jours avant envoi du rappel"
+
+#: includes/class-wc-trusted-shops-admin.php:908
+msgctxt "trusted-shops"
+msgid ""
+"Set the number of days to wait after the order date before having a Review "
+"Request sent to your customers."
+msgstr ""
+"Indiquer ici après combien de jours le rappel d’évaluation est envoyé à "
+"l’acheteur après avoir atteint l’état de la commande sélectionné ci-dessus."
+
+#: includes/class-wc-trusted-shops-admin.php:912
+msgctxt "trusted-shops"
+msgid "Start export"
+msgstr "Débuter l’exportation"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:64
+msgctxt "trusted-shops"
+msgid "Order ID"
+msgstr "Réf. commande"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:65
+msgctxt "trusted-shops"
+msgid "Order date"
+msgstr "Date de la commande"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:66
+msgctxt "trusted-shops"
+msgid "# Days"
+msgstr "# Jours"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:67
+msgctxt "trusted-shops"
+msgid "Email"
+msgstr "Email"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:68
+msgctxt "trusted-shops"
+msgid "First name"
+msgstr "Prénom"
+
+#: includes/class-wc-trusted-shops-review-exporter.php:69
+msgctxt "trusted-shops"
+msgid "Last name"
+msgstr "Nom"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:121
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"Your review reminder e-mail has been cancelled successfully. Return to %s."
+msgstr ""
+"Votre e-mail de demande d’avis a été annulé avec succès. Retourner à %s."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:121
+msgctxt "trusted-shops"
+msgid "Home"
+msgstr "Accueil"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:193
+msgctxt "trusted-shops"
+msgid ""
+"Yes, I would like to be reminded via e-mail after {days} day(s) to review my "
+"order. I am able to cancel the reminder at any time by clicking on the "
+"\"cancel review reminder\" link within the order confirmation."
+msgstr ""
+"Oui, je souhaite recevoir un demande d’avis par e-mail au bout de {days} "
+"jour(s) pour évaluer ma commande. Je peux annuler le rappel à tout moment en "
+"cliquant sur le lien Annuler le rappel d’évaluation dans l’email de "
+"confirmation de commande."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:198
+msgctxt "trusted-shops"
+msgid "Please allow us to send a review reminder by e-mail."
+msgstr ""
+"Veuillez nous autoriser à vous envoyer un rappel d’évaluation par e-mail."
+
+#: includes/class-wc-trusted-shops-template-hooks.php:201
+msgctxt "trusted-shops"
+msgid "Review reminder"
+msgstr "Demande d’avis"
+
+#: includes/class-wc-trusted-shops-template-hooks.php:202
+msgctxt "trusted-shops"
+msgid "Asks the customer to receive a Trusted Shops review reminder."
+msgstr "Demande au client s’il veut recevoir une demande d’avis Trusted Shops."
+
+#: includes/class-wc-ts-dependencies.php:36
+#: includes/class-wc-ts-dependencies.php:45 woocommerce-trusted-shops.php:67
+#: woocommerce-trusted-shops.php:76
+msgid "Cheatin’ huh?"
+msgstr "Cheatin’ huh?"
+
+#: includes/class-wc-ts-install.php:72
+#, php-format
+msgid ""
+"Please install WooCommerce before "
+"installing WooCommerce Germanized. Thank you!"
+msgstr ""
+"Please install WooCommerce before "
+"installing WooCommerce Germanized. Thank you!"
+
+#: includes/class-wc-ts-settings-handler.php:23
+msgctxt "trusted-shops"
+msgid "Trusted Shops"
+msgstr "Trusted Shops"
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:25
+msgctxt "trusted-shops"
+msgid "Trusted Shops Review Reminder"
+msgstr " demande d’avis Trusted Shops"
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:26
+msgctxt "trusted-shops"
+msgid ""
+"This E-Mail is being sent to a customer to remind him about the possibility "
+"to leave a review at Trusted Shops."
+msgstr ""
+"Cet e-mail est envoyé à un client pour lui rappeler qu’il peut laisser un "
+"avis via Trusted Shops."
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:53
+msgctxt "trusted-shops"
+msgid "Please rate your {site_title} order from {order_date}"
+msgstr ""
+"Veuillez évaluer votre commande sur la boutique {site_title} datant du "
+"{order_date}"
+
+#: includes/emails/class-wc-ts-email-customer-trusted-shops.php:63
+msgctxt "trusted-shops"
+msgid "Please rate your Order"
+msgstr "Veuillez évaluer votre commande"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:19
+msgctxt "trusted-shops"
+msgid "Show your TS shop review sticker."
+msgstr "Afficher la vignette avis site"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:21
+msgctxt "trusted-shops"
+msgid "Trusted Shops Shop Review Sticker"
+msgstr "Vignette avis site"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:25
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:46
+msgctxt "trusted-shops"
+msgid "Trusted Shops Reviews"
+msgstr "avis clients Trusted Shops"
+
+#: includes/widgets/class-wc-trusted-shops-widget-review-sticker.php:26
+msgctxt "trusted-shops"
+msgid "Title"
+msgstr "Titre"
+
+#: templates/emails/customer-trusted-shops.php:18
+#: templates/emails/plain/customer-trusted-shops.php:12
+#, php-format
+msgctxt "trusted-shops"
+msgid "Dear %s %s,"
+msgstr "Bonjour %s %s,"
+
+#: templates/emails/customer-trusted-shops.php:19
+#: templates/emails/plain/customer-trusted-shops.php:14
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"You have recently shopped at %s. Thank you! We would be glad if you spent "
+"some time to write a review about your order. To do so please follow follow "
+"the link."
+msgstr ""
+"Vous avez récemment passé commande sur %s. Merci beaucoup! Nous serions "
+"ravis si vous preniez quelques minutes afin de donner votre avis sur votre "
+"commande. Veuillez pour cela cliquer sur le lien suivant."
+
+#: templates/emails/customer-trusted-shops.php:22
+msgctxt "trusted-shops"
+msgid "Rate Order now"
+msgstr "Évaluer ma commande maintenant"
+
+#: woocommerce-trusted-shops.php:220
+msgctxt "trusted-shops"
+msgid "Yes"
+msgstr "Oui"
+
+#: woocommerce-trusted-shops.php:220
+msgctxt "trusted-shops"
+msgid "No"
+msgstr "Non"
+
+#: woocommerce-trusted-shops.php:265
+#, php-format
+msgctxt "trusted-shops"
+msgid ""
+"If the App helped you, please leave a %s★★"
+"★★★%s in the Wordpress plugin repository."
+msgstr ""
+"If the App helped you, please leave a %s★★"
+"★★★%s in the Wordpress plugin repository."
+
+#: woocommerce-trusted-shops.php:419
+msgctxt "trusted-shops"
+msgid "Settings"
+msgstr "Paramètres"
diff --git a/packages/woocommerce-trusted-shops/includes/abstracts/abstract-wc-ts-compatibility.php b/packages/woocommerce-trusted-shops/includes/abstracts/abstract-wc-ts-compatibility.php
new file mode 100644
index 000000000..eb7f12628
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/abstracts/abstract-wc-ts-compatibility.php
@@ -0,0 +1,82 @@
+ '1.0.0',
+ 'requires_at_least' => '',
+ 'tested_up_to' => '',
+ ) );
+
+ if ( empty( $version_data[ 'requires_at_least' ] ) && empty( $version_data[ 'tested_up_to' ] ) ) {
+ $version_data[ 'requires_at_least' ] = $version_data[ 'version' ];
+ $version_data[ 'tested_up_to' ] = $version_data[ 'version' ];
+ } elseif ( empty( $version_data[ 'tested_up_to' ] ) ) {
+ $version_data[ 'tested_up_to' ] = $version_data[ 'requires_at_least' ];
+ if ( WC_TS_Dependencies::instance()->compare_versions( $version_data[ 'version' ], $version_data[ 'requires_at_least' ], '>' ) )
+ $version_data[ 'tested_up_to' ] = $version_data[ 'version' ];
+ } elseif ( empty( $version_data[ 'requires_at_least' ] ) ) {
+ $version_data[ 'requires_at_least' ] = $version_data[ 'tested_up_to' ];
+ if ( WC_TS_Dependencies::instance()->compare_versions( $version_data[ 'version' ], $version_data[ 'requires_at_least' ], '<' ) )
+ $version_data[ 'requires_at_least' ] = $version_data[ 'version' ];
+ }
+
+ $this->version_data = $version_data;
+
+ $this->plugin_name = $plugin_name;
+ $this->plugin_file = $plugin_file;
+
+ if ( ! $this->is_applicable() )
+ return;
+
+ add_action( 'init', array( $this, 'early_execution' ), 0 );
+ add_action( 'init', array( $this, 'load' ), 15 );
+
+ $this->after_plugins_loaded();
+ }
+
+ public function early_execution() {}
+
+ public function after_plugins_loaded() {}
+
+ public function is_applicable() {
+ return $this->is_activated() && $this->is_supported();
+ }
+
+ public function is_activated() {
+ return WC_TS_Dependencies::instance()->is_plugin_activated( $this->plugin_file );
+ }
+
+ public function is_supported() {
+ return
+ WC_TS_Dependencies::instance()->compare_versions( $this->version_data[ 'version' ], $this->version_data[ 'requires_at_least' ], '>=' ) &&
+ WC_TS_Dependencies::instance()->compare_versions( $this->version_data[ 'version' ], $this->version_data[ 'tested_up_to' ], '<=' );
+ }
+
+ public function get_name() {
+ return $this->plugin_name;
+ }
+
+ public function get_version_data() {
+ return $this->version_data;
+ }
+
+ abstract function load();
+
+}
diff --git a/packages/woocommerce-trusted-shops/includes/admin/settings/class-wc-ts-gzd-settings-tab.php b/packages/woocommerce-trusted-shops/includes/admin/settings/class-wc-ts-gzd-settings-tab.php
new file mode 100644
index 000000000..4f90fe35d
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/admin/settings/class-wc-ts-gzd-settings-tab.php
@@ -0,0 +1,99 @@
+trusted_shops->get_dependency( 'admin' );
+
+ return $admin->get_trusted_url( 'https://support.trustedshops.com/en/apps/woocommerce' );
+ }
+
+ public function before_output() {
+ do_action( 'woocommerce_ts_admin_settings_before', $this->get_settings_for_section_core( '' ) );
+ }
+
+ public function review_exporter() {
+ $admin = WC_trusted_shops()->trusted_shops->get_dependency( 'admin' );
+ $admin->review_collector_export();
+ }
+
+ public function register_scripts() {
+ if ( isset( $_GET['tab'] ) && 'germanized-trusted_shops' === $_GET['tab'] ) {
+ do_action( 'woocommerce_trusted_shops_load_admin_scripts' );
+ }
+ }
+
+ public function get_description() {
+ return _x( 'Setup your Trusted Shops Integration.', 'trusted-shops', 'woocommerce-germanized' );
+ }
+
+ public function get_label() {
+ return _x( 'Trusted Shops', 'trusted-shops', 'woocommerce-germanized' );
+ }
+
+ public function get_name() {
+ return 'trusted_shops';
+ }
+
+ protected function output_description() {}
+
+ public function get_tab_settings( $current_section = '' ) {
+ $admin = WC_trusted_shops()->trusted_shops->get_dependency( 'admin' );
+ $settings = $admin->get_settings();
+
+ return $settings;
+ }
+
+ public function get_sidebar( $current_section = '' ) {
+ $admin = WC_trusted_shops()->trusted_shops->get_dependency( 'admin' );
+ $sidebar = $admin->get_sidebar();
+
+ return $sidebar;
+ }
+
+ protected function get_enable_option_name() {
+ $option_prefix = Package::is_integration() ? 'gzd_' : '';
+ $option_name = 'woocommerce_' . $option_prefix . 'trusted_shops_id';
+
+ return $option_name;
+ }
+
+ public function is_enabled() {
+ $value = get_option( $this->get_enable_option_name() );
+
+ return ( ! empty( $value ) ? true : false );
+ }
+
+ protected function before_save( $settings, $current_section = '' ) {
+ do_action( 'woocommerce_ts_before_save', $settings );
+
+ parent::before_save( $settings, $current_section );
+ }
+
+ protected function after_save( $settings, $current_section = '' ) {
+ do_action( 'woocommerce_ts_after_save', $settings );
+
+ parent::after_save( $settings, $current_section );
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/admin/views/html-notice-dependencies.php b/packages/woocommerce-trusted-shops/includes/admin/views/html-notice-dependencies.php
new file mode 100644
index 000000000..d41c01c7f
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/admin/views/html-notice-dependencies.php
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ plugins_required as $plugin => $data ) : ?>
+
+ is_plugin_activated( $plugin ) ) : $missing_count++ ?>
+
+
+
+
+
+
+
+
">
+
+
+
+ is_plugin_outdated( $plugin ) ) : $version_count ++ ?>
+
+
+
+
+
+
+
+
- ' . $data[ 'version' ] . '' ); ?>
+
+
+
+
+
+ 0 ) : ?>
+
+
+ ">
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/admin/views/html-notice-update.php b/packages/woocommerce-trusted-shops/includes/admin/views/html-notice-update.php
new file mode 100644
index 000000000..fc8124c5d
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/admin/views/html-notice-update.php
@@ -0,0 +1,21 @@
+
+
+
WooCommerce Trusted Shops Data Update Required – We just need to update your installation to the latest version', 'trusted-shops', 'woocommerce-germanized' ); ?>
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/admin/views/html-settings-section.php b/packages/woocommerce-trusted-shops/includes/admin/views/html-settings-section.php
new file mode 100644
index 000000000..f56ffca44
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/admin/views/html-settings-section.php
@@ -0,0 +1,44 @@
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/admin/views/html-wpml-notice.php b/packages/woocommerce-trusted-shops/includes/admin/views/html-wpml-notice.php
new file mode 100644
index 000000000..091bc55ca
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/admin/views/html-wpml-notice.php
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+ ' . $current_language . '' ); ?>
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-admin.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-admin.php
new file mode 100644
index 000000000..5cacfd0ac
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-admin.php
@@ -0,0 +1,955 @@
+base = $base;
+
+ add_action( 'woocommerce_ts_before_save', array( $this, 'before_save' ), 0, 1 );
+ add_action( 'woocommerce_ts_after_save', array( $this, 'after_save' ), 0, 1 );
+
+ add_action( 'woocommerce_ts_admin_settings_before', array( $this, 'wpml_notice' ) );
+
+ // Default settings
+ add_filter( 'woocommerce_gzd_installation_default_settings', array( $this, 'set_installation_settings' ), 10, 1 );
+
+ // After Install
+ add_action( 'woocommerce_trusted_shops_installed', array( $this, 'create_attribute' ) );
+
+ // Review Collector
+ add_action( 'admin_init', array( $this, 'review_collector_export_csv' ) );
+ add_action( 'woocommerce_trusted_shops_load_admin_scripts', array( $this, 'load_scripts' ) );
+
+ add_action( 'woocommerce_product_options_general_product_data', array( $this, 'output_fields' ) );
+ add_action( 'woocommerce_product_after_variable_attributes', array( $this, 'output_variation_fields' ), 20, 3 );
+ add_action( 'woocommerce_save_product_variation', array( $this, 'save_variation_fields' ) , 0, 2 );
+
+ if ( ! wc_ts_woocommerce_supports_crud() ) {
+ add_action( 'woocommerce_process_product_meta', array( $this, 'save_fields' ), 20, 2 );
+ } else {
+ add_action( 'woocommerce_admin_process_product_object', array( $this, 'save_fields' ), 10, 1 );
+ }
+ }
+
+ public function set_installation_settings( $settings ) {
+ return array_merge( $settings, $this->get_settings() );
+ }
+
+ public function wpml_notice() {
+ if ( $this->base->is_multi_language_setup() ) {
+ $is_default_language = false;
+ $compatibility = $this->base->get_multi_language_compatibility();
+ $default_language = strtoupper( $compatibility->get_default_language() );
+ $current_language = strtoupper( $compatibility->get_current_language() );
+
+ if ( $current_language == $default_language ) {
+ $is_default_language = true;
+ }
+
+ include_once( 'admin/views/html-wpml-notice.php' );
+ }
+ }
+
+ public function output_variation_fields( $loop, $variation_data, $variation ) {
+ $_product = wc_get_product( $variation );
+ $_parent = wc_get_product( wc_ts_get_crud_data( $_product, 'parent' ) );
+ $variation_id = wc_ts_get_crud_data( $_product, 'id' );
+ $variation_meta = get_post_meta( $variation_id );
+ $variation_data = array();
+
+ $variation_fields = array(
+ '_ts_gtin' => '',
+ '_ts_mpn' => '',
+ );
+
+ foreach ( $variation_fields as $field => $value ) {
+ $variation_data[ $field ] = isset( $variation_meta[ $field ][0] ) ? maybe_unserialize( $variation_meta[ $field ][0] ) : $value;
+ }
+
+ ?>
+
+
+
+ '',
+ '_ts_mpn' => '',
+ );
+
+ foreach ( $data as $k => $v ) {
+ $data_k = 'variable' . ( substr( $k, 0, 1) === '_' ? '' : '_' ) . $k;
+ $data[ $k ] = ( isset( $_POST[ $data_k ][$i] ) ? $_POST[ $data_k ][$i] : null );
+ }
+
+ if ( $product = wc_get_product( $variation_id ) ) {
+ foreach( $data as $key => $value ) {
+ $product = wc_ts_set_crud_data( $product, $key, $value );
+ }
+
+ if ( wc_ts_woocommerce_supports_crud() ) {
+ $product->save();
+ }
+ }
+ }
+
+ public function output_fields() {
+ echo '';
+
+ woocommerce_wp_text_input( array( 'id' => '_ts_gtin', 'label' => _x( 'GTIN', 'trusted-shops', 'woocommerce-germanized' ), 'data_type' => 'text', 'desc_tip' => true, 'description' => _x( 'ID that allows your products to be identified worldwide. If you want to display your Trusted Shops Product Reviews in Google Shopping and paid Google adverts, Google needs the GTIN.', 'trusted-shops', 'woocommerce-germanized' ) ) );
+ woocommerce_wp_text_input( array( 'id' => '_ts_mpn', 'label' => _x( 'MPN', 'trusted-shops', 'woocommerce-germanized' ), 'data_type' => 'text', 'desc_tip' => true, 'description' => _x( 'If you don\'t have a GTIN for your products, you can pass the brand name and the MPN on to Google to use the Trusted Shops Google Integration.', 'trusted-shops', 'woocommerce-germanized' ) ) );
+
+ echo '
';
+ }
+
+ public function save_fields( $product ) {
+ if ( is_numeric( $product ) )
+ $product = wc_get_product( $product );
+
+ if ( isset( $_POST['_ts_gtin'] ) ) {
+ $product = wc_ts_set_crud_data( $product, '_ts_gtin', wc_clean( $_POST['_ts_gtin'] ) );
+ }
+
+ if ( isset( $_POST['_ts_mpn'] ) ) {
+ $product = wc_ts_set_crud_data( $product, '_ts_mpn', wc_clean( $_POST['_ts_mpn'] ) );
+ }
+ }
+
+ public function create_attribute() {
+ $attributes = array(
+ 'brand' => _x( 'Brand', 'trusted-shops', 'woocommerce-germanized' ),
+ );
+
+ // Create the taxonomy
+ global $wpdb;
+ delete_transient( 'wc_attribute_taxonomies' );
+
+ foreach ( $attributes as $attribute_name => $title ) {
+ if ( ! in_array( 'pa_' . $attribute_name, wc_get_attribute_taxonomy_names() ) ) {
+ $attribute = array(
+ 'attribute_label' => $title,
+ 'attribute_name' => $attribute_name,
+ 'attribute_type' => 'text',
+ 'attribute_orderby' => 'menu_order',
+ 'attribute_public' => 0
+ );
+
+ $wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute );
+ delete_transient( 'wc_attribute_taxonomies' );
+ }
+ }
+ }
+
+ public function load_scripts() {
+ $screen = get_current_screen();
+ $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+ $assets_path = $this->base->plugin->plugin_url() . '/assets/';
+
+ wp_register_style( 'woocommerce-trusted-shops-admin', $assets_path . 'css/admin' . $suffix . '.css', false, $this->base->plugin->version );
+ wp_enqueue_style( 'woocommerce-trusted-shops-admin' );
+
+ wp_register_script( 'wc-admin-trusted-shops', $assets_path . 'js/admin' . $suffix . '.js', array( 'jquery', 'woocommerce_settings' ), $this->base->plugin->version, true );
+ wp_localize_script( 'wc-admin-trusted-shops', 'trusted_shops_params', array(
+ 'option_prefix' => $this->base->option_prefix,
+ 'i18n_error_mandatory' => _x( 'This field is mandatory', 'trusted-shops', 'woocommerce-germanized' ),
+ ) );
+
+ wp_enqueue_script( 'wc-admin-trusted-shops' );
+ }
+
+ public function register_section( $sections ) {
+ $sections['trusted_shops'] = _x( 'Trusted Shops Options', 'trusted-shops', 'woocommerce-germanized' );
+
+ return $sections;
+ }
+
+ public function get_font_families() {
+ return array(
+ 'Arial' => _x( 'Arial', 'trusted-shops', 'woocommerce-germanized' ),
+ 'Geneva' => _x( 'Geneva', 'trusted-shops', 'woocommerce-germanized' ),
+ 'Georgia' => _x( 'Georgia', 'trusted-shops', 'woocommerce-germanized' ),
+ 'Helvetica' => _x( 'Helvetica', 'trusted-shops', 'woocommerce-germanized' ),
+ 'Sans-serif' => _x( 'Sans-serif', 'trusted-shops', 'woocommerce-germanized' ),
+ 'Serif' => _x( 'Serif', 'trusted-shops', 'woocommerce-germanized' ),
+ 'Trebuchet MS' => _x( 'Trebuchet MS', 'trusted-shops', 'woocommerce-germanized' ),
+ 'Verdana' => _x( 'Verdana', 'trusted-shops', 'woocommerce-germanized' ),
+ );
+ }
+
+ public function get_order_statuses() {
+ return wc_get_order_statuses();
+ }
+
+ public function get_translatable_settings() {
+ $translatable = array();
+
+ foreach( $this->get_settings_array() as $setting ) {
+ if ( isset( $setting['id'] ) && ! in_array( $setting['type'], array( 'title', 'sectionend' ) ) ) {
+ $translatable[ $setting['id'] ] = '';
+ }
+ }
+
+ return $translatable;
+ }
+
+ protected function get_settings_array( $defaults = array() ) {
+ $settings = array(
+ array(
+ 'title' => _x( 'Trusted Shops Integration', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => sprintf( _x( 'Do you need help with integrating your Trustbadge? %s', 'trusted-shops', 'woocommerce-germanized' ), '' . _x( 'To the step-by-step instructions', 'trusted-shops', 'woocommerce-germanized' ) .' ' ),
+ 'type' => 'title',
+ 'id' => 'trusted_shops_options'
+ ),
+
+ array(
+ 'title' => _x( 'Trusted Shops ID', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( 'The Trusted Shops ID is a unique identifier for your shop. You can find your Trusted Shops ID in your My Trusted Shops account.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => true,
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_id',
+ 'type' => 'text',
+ 'custom_attributes' => array( 'data-sidebar' => 'wc-ts-sidebar-default' ),
+ 'css' => 'min-width:300px;',
+ ),
+
+ array(
+ 'title' => _x( 'Edit Mode', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_integration_mode',
+ 'desc_tip' => _x( 'The advanced configuration is for users with programming skills. Here you can create even more individual settings.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'select',
+ 'class' => 'chosen_select',
+ 'options' => array(
+ 'standard' => _x( 'Standard configuration', 'trusted-shops', 'woocommerce-germanized' ),
+ 'expert' => _x( 'Advanced configuration', 'trusted-shops', 'woocommerce-germanized' ),
+ ),
+ 'default' => 'standard',
+ ),
+
+ array( 'type' => 'sectionend', 'id' => 'trusted_shops_options' ),
+
+ array(
+ 'title' => _x( 'Configure your Trustbadge', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'title',
+ 'id' => 'trusted_shops_badge_options',
+ ),
+
+ array(
+ 'title' => _x( 'Display Trustbadge', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_trustbadge_enable',
+ 'desc_tip' => _x( 'Display the Trustbadge on all the pages of your shop.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'ts_toggle',
+ 'custom_attributes' => array( 'data-sidebar' => 'wc-ts-sidebar-trustbadge' ),
+ 'default' => 'no'
+ ),
+
+ array(
+ 'title' => _x( 'Variant', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_trustbadge_variant',
+ 'desc_tip' => _x( 'You can display your Trustbadge with or without Review Stars.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'select',
+ 'class' => 'chosen_select',
+ 'options' => array(
+ 'standard' => _x( 'Display Trustbadge with review stars', 'trusted-shops', 'woocommerce-germanized' ),
+ 'hide_reviews' => _x( 'Display Trustbadge without review stars', 'trusted-shops', 'woocommerce-germanized' ),
+ ),
+ 'default' => 'standard'
+ ),
+
+ array(
+ 'title' => _x( 'Vertical Offset', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Choose the distance that the Trustbadge will appear from the bottom-right corner of the screen.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_trustbadge_y',
+ 'type' => 'number',
+ 'desc' => _x( 'px', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => '0',
+ 'custom_attributes' => array(
+ 'step' => '1',
+ 'min' => 0,
+ 'data-validate' => 'integer',
+ 'data-validate-msg' => sprintf( _x( 'Please choose a non-negative number (at least %d)', 'trusted-shops', 'woocommerce-germanized' ), 0 ),
+ ),
+ 'css' => 'max-width:60px;',
+ ),
+
+ array(
+ 'title' => _x( 'Trustbadge code', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_trustbadge_code',
+ 'type' => 'textarea',
+ 'desc_tip' => true,
+ 'desc' => _x( 'The advanced configuration is for users with programming skills. Here you can create even more individual settings.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'css' => 'width: 100%; min-height: 150px',
+ 'default' => '',
+ ),
+
+ array( 'type' => 'sectionend', 'id' => 'trusted_shops_badge_options' ),
+
+ array(
+ 'title' => _x( 'Configure your Shop Reviews', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'title',
+ 'id' => 'trusted_shops_review_sticker_options'
+ ),
+
+ array(
+ 'title' => _x( 'Display Shop Review Sticker', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'To display the Shop Review Sticker, you have to assign the widget "Trusted Shops Review Sticker".', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => sprintf( _x( 'Assign widget %s', 'trusted-shops', 'woocommerce-germanized' ), '' . _x( 'here', 'trusted-shops', 'woocommerce-germanized' ) . ' ' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_enable',
+ 'type' => 'ts_toggle',
+ 'custom_attributes' => array( 'data-sidebar' => 'wc-ts-sidebar-shop-reviews' ),
+ 'default' => 'no'
+ ),
+
+ array(
+ 'title' => _x( 'Background color', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Choose the background color for your Review Sticker.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_bg_color',
+ 'type' => 'color',
+ 'default' => '#FFDC0F',
+ ),
+
+ array(
+ 'title' => _x( 'Font', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_font',
+ 'desc_tip' => _x( 'Choose the font for your Review Sticker.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'select',
+ 'class' => 'chosen_select',
+ 'default' => 'arial'
+ ),
+
+ array(
+ 'title' => _x( 'Number of reviews displayed', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Display x alternating Shop Reviews in your Shop Review Sticker. You can display between 1 and 5 alternating Shop Reviews.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_number',
+ 'type' => 'number',
+ 'desc' => _x( 'Show x alternating reviews', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => '5',
+ 'custom_attributes' => array(
+ 'step' => '1',
+ 'min' => 1,
+ 'max' => 5,
+ 'data-validate' => 'integer',
+ 'data-validate-msg' => sprintf( _x( 'Please choose a non-negative number between %d and %d', 'trusted-shops', 'woocommerce-germanized' ),1, 5 ),
+ ),
+ 'css' => 'max-width:60px;',
+ ),
+
+ array(
+ 'title' => _x( 'Minimum rating displayed', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Only show Shop Reviews with a minimum rating of x stars. ', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_better_than',
+ 'type' => 'number',
+ 'desc' => _x( 'Star(s)', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => '3',
+ 'custom_attributes' => array(
+ 'step' => '1',
+ 'min' => 1,
+ 'max' => 5,
+ 'data-validate' => 'integer',
+ 'data-validate-msg' => sprintf( _x( 'Please choose a non-negative number between %d and %d', 'trusted-shops', 'woocommerce-germanized' ), 1, 5 ),
+ ),
+ 'css' => 'max-width:60px;',
+ ),
+
+ array(
+ 'title' => _x( 'Sticker code', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_code',
+ 'type' => 'textarea',
+ 'desc_tip' => true,
+ 'desc' => _x( 'The advanced configuration is for users with programming skills. Here you can perform even more individual settings.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'css' => 'width: 100%; min-height: 150px',
+ 'default' => '',
+ ),
+
+ array(
+ 'title' => _x( 'Google Organic Search', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Activate this option to give Google the opportunity to show your Shop Reviews in Google organic search results.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( 'By activating this option, rich snippets will be integrated in the selected pages so your shop review stars may be displayed in Google organic search results. If you use Product Reviews and already activated rich snippets in expert mode, we recommend integrating rich snippets for Shop Reviews on category pages only.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_rich_snippets_enable',
+ 'type' => 'ts_toggle',
+ 'default' => 'no'
+ ),
+
+ array(
+ 'title' => _x( 'Activate rich snippets on', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( 'category pages', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_rich_snippets_category',
+ 'type' => 'checkbox',
+ 'default' => 'no',
+ 'checkboxgroup' => 'start',
+ ),
+
+ array(
+ 'desc' => _x( 'product pages', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_rich_snippets_product',
+ 'type' => 'checkbox',
+ 'default' => 'no',
+ 'checkboxgroup' => '',
+ ),
+
+ array(
+ 'desc' => _x( 'homepage (not recommended)', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_rich_snippets_home',
+ 'type' => 'checkbox',
+ 'default' => 'no',
+ 'checkboxgroup' => 'end',
+ ),
+
+ array(
+ 'title' => _x( 'Rich snippets code', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_rich_snippets_code',
+ 'type' => 'textarea',
+ 'desc_tip' => true,
+ 'desc' => _x( 'The advanced configuration is for users with programming skills. Here you can create even more individual settings.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'css' => 'width: 100%; min-height: 150px',
+ 'default' => '',
+ ),
+
+ array( 'type' => 'sectionend', 'id' => 'trusted_shops_review_sticker_options' ),
+
+ array(
+ 'title' => _x( 'Configure your Product Reviews ', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => sprintf( _x( 'To use Product Reviews, activate them in your %s first.', 'trusted-shops', 'woocommerce-germanized' ), '' . _x( 'Trusted Shops package', 'trusted-shops', 'woocommerce-germanized' ) .' ' ),
+ 'type' => 'title',
+ 'id' => 'trusted_shops_reviews_options'
+ ),
+
+ array(
+ 'title' => _x( 'Collect Product Reviews', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( 'Show Product Reviews on the product page in a separate tab, just as shown on the picture on the right.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_reviews_enable',
+ 'type' => 'ts_toggle',
+ 'custom_attributes' => array( 'data-sidebar' => 'wc-ts-sidebar-product-reviews' ),
+ 'default' => 'no'
+ ),
+
+ array(
+ 'title' => _x( 'Reviews', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'You can choose a name for the tab with your Product Reviews.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( 'Show Product Reviews on the product detail page in an additional tab.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_enable',
+ 'type' => 'ts_toggle',
+ 'default' => 'no'
+ ),
+
+ array(
+ 'title' => _x( 'Name of Product Reviews tab', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'You can choose a name for the tab with your Product Reviews.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_tab_text',
+ 'type' => 'text',
+ 'default' => _x( 'Product reviews', 'trusted-shops', 'woocommerce-germanized' ),
+ ),
+
+ array(
+ 'title' => _x( 'Border color', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Set the color for the frame around your Product Reviews.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_border_color',
+ 'type' => 'color',
+ 'default' => '#FFDC0F',
+ ),
+
+ array(
+ 'title' => _x( 'Background color', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Set the background color for your Product Reviews.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_bg_color',
+ 'type' => 'color',
+ 'default' => '#FFFFFF',
+ ),
+
+ array(
+ 'title' => _x( 'Star color', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Set the color for the Product Review stars in your Product Reviews tab.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_star_color',
+ 'type' => 'color',
+ 'default' => '#C0C0C0',
+ ),
+
+ array(
+ 'title' => _x( 'Star size', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_star_size',
+ 'type' => 'number',
+ 'default' => '15',
+ 'desc' => _x( 'px', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Set the size for the Product Review stars in your Product Reviews tab.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'css' => 'max-width:60px;',
+ 'custom_attributes' => array(
+ 'step' => '1',
+ 'min' => 0,
+ 'data-validate' => 'integer',
+ 'data-validate-msg' => sprintf( _x( 'Please choose a non-negative number (at least %d)', 'trusted-shops', 'woocommerce-germanized' ), 0 ),
+ ),
+ ),
+
+ array(
+ 'title' => _x( 'Product Sticker Code', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_code',
+ 'desc_tip' => _x( 'The advanced configuration is for users with programming skills. Here you can perform even more individual settings.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'textarea',
+ 'css' => 'width: 100%; min-height: 150px',
+ 'default' => '',
+ ),
+
+ array(
+ 'title' => _x( 'jQuerySelector', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Please choose where your Product Reviews shall be displayed on the Product detail page.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_selector',
+ 'type' => 'text',
+ 'default' => '#ts_product_sticker',
+ ),
+
+ array(
+ 'title' => _x( 'Rating stars', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( 'Show star ratings on the product detail page below your product name.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Display Product Review stars on product pages below the product name, just as shown in the picture on the right.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_enable',
+ 'type' => 'ts_toggle',
+ 'default' => 'no'
+ ),
+
+ array(
+ 'title' => _x( 'Star color', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_star_color',
+ 'desc_tip' => _x( 'Set the color for the review stars, that are displayed on the product page, below your product name.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'color',
+ 'default' => '#FFDC0F',
+ ),
+
+ array(
+ 'title' => _x( 'Star size', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_star_size',
+ 'desc_tip' => _x( 'Set the size for the review stars that are displayed on the product page, below your product name.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'number',
+ 'default' => '14',
+ 'desc' => _x( 'px', 'trusted-shops', 'woocommerce-germanized' ),
+ 'css' => 'max-width:60px;',
+ 'custom_attributes' => array(
+ 'step' => '1',
+ 'min' => 0,
+ 'data-validate' => 'integer',
+ 'data-validate-msg' => sprintf( _x( 'Please choose a non-negative number (at least %d)', 'trusted-shops', 'woocommerce-germanized' ), 0 ),
+ ),
+ ),
+
+ array(
+ 'title' => _x( 'Font size', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_font_size',
+ 'desc_tip' => _x( 'Set the font size for the text that goes with your review stars.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'number',
+ 'desc' => _x( 'px', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => '12',
+ 'css' => 'max-width:60px;',
+ 'custom_attributes' => array(
+ 'step' => '1',
+ 'min' => 0,
+ 'data-validate' => 'integer',
+ 'data-validate-msg' => sprintf( _x( 'Please choose a non-negative number (at least %d)', 'trusted-shops', 'woocommerce-germanized' ), 0 ),
+ ),
+ ),
+
+ array(
+ 'title' => _x( 'Product Review Code', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'The advanced configuration is for users with programming skills. Here you can perform even more individual settings.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_code',
+ 'type' => 'textarea',
+ 'css' => 'width: 100%; min-height: 150px',
+ 'default' => '',
+ ),
+
+ array(
+ 'title' => _x( 'jQuerySelector', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Please choose where your Product Review Stars shall be displayed on the Product Detail page.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_selector',
+ 'type' => 'text',
+ 'default' => '#ts_product_widget',
+ ),
+
+ array(
+ 'title' => _x( 'Brand attribute', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => sprintf( _x( 'Create brand attribute %s', 'trusted-shops', 'woocommerce-germanized' ), '' . _x( 'here', 'trusted-shops', 'woocommerce-germanized' ) . ' ' ),
+ 'desc_tip' => _x( 'Brand name of the product. By passing this information on to Google, you improve your chances of having Google identify your products. Assign your brand attribute. If your products don\'t have a GTIN, you can pass on the brand name and the MPN to use Google Integration.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_brand_attribute',
+ 'css' => 'min-width:250px;',
+ 'default' => 'brand',
+ 'type' => 'select',
+ 'class' => 'chosen_select_nostd',
+ 'custom_attributes' => array( 'data-placeholder' => _x( 'None', 'trusted-shops', 'woocommerce-germanized' ) ),
+ ),
+
+ array( 'type' => 'sectionend', 'id' => 'trusted_shops_reviews_options' ),
+ );
+
+ if ( $this->base->supports( 'reminder' ) ) {
+
+ $settings = array_merge( $settings, array(
+
+ array(
+ 'title' => _x( 'Configure your Review Requests', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( '7 days after an order has been placed, Trusted Shops automatically sends an invite to your customers. If you want to set a different time for sending automatic Review Requests, please activate the option below. If you want to send review requests with legal certainty, you need your customers\' consent to receive Review Requests. You also have to include an option to unsubscribe.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'type' => 'title',
+ 'id' => 'trusted_shops_review_reminder_options',
+ ),
+
+ array(
+ 'title' => _x( 'Enable Review Requests', 'trusted-shops', 'woocommerce-germanized' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_reminder_enable',
+ 'type' => 'ts_toggle',
+ 'default' => 'no',
+ 'custom_attributes' => array( 'data-sidebar' => 'wc-ts-sidebar-review-reminder' ),
+ 'autoload' => false
+ ),
+
+ array(
+ 'title' => _x( 'WooCommerce status', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'We recommend choosing the order status that you set when your products have been shipped.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => array( 'wc-completed' ),
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_reminder_status',
+ 'type' => 'multiselect',
+ 'class' => 'chosen_select',
+ ),
+
+ array(
+ 'title' => _x( 'Days until Review Request', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'Set the number of days to wait after an order has reached the order status you selected above before having a review request sent to your customers.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => 7,
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_reminder_days',
+ 'type' => 'number',
+ 'custom_attributes' => array(
+ 'step' => '1',
+ 'min' => 0,
+ 'data-validate' => 'integer',
+ ),
+ ),
+
+ array(
+ 'title' => _x( 'Permission via checkbox', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc_tip' => _x( 'If the checkbox is activated, only customers who gave their consent will receive Review Requests.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => '',
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_reminder_checkbox',
+ 'type' => 'html',
+ 'html' => '' . _x( 'Edit checkbox', 'trusted-shops', 'woocommerce-germanized' ) . ' ',
+ ),
+
+ array(
+ 'title' => _x( 'Unsubscribe via link', 'trusted-shops', 'woocommerce-germanized' ),
+ 'desc' => _x( 'Allows the customer to unsubscribe from Review Requests.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'default' => 'yes',
+ 'id' => 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_reminder_opt_out',
+ 'type' => 'ts_toggle',
+ ),
+
+ array( 'type' => 'sectionend', 'id' => 'trusted_shops_review_reminder_options' ),
+
+ ) );
+ }
+
+ if ( ! empty( $defaults ) ) {
+ foreach( $settings as $key => $setting ) {
+ if ( isset( $setting['id'] ) ) {
+ foreach( $defaults as $setting_id => $default ) {
+ if ( $setting_id === $setting['id'] ) {
+ $settings[ $key ] = array_replace_recursive( $setting, $default );
+ }
+ }
+ }
+ }
+ }
+
+ return $settings;
+ }
+
+ /**
+ * Get Trusted Shops related Settings for Admin Interface
+ *
+ * @return array
+ */
+ public function get_settings() {
+
+ $attributes = wc_get_attribute_taxonomies();
+ $linked_attributes = array();
+
+ // Set attributes
+ foreach ( $attributes as $attribute ) {
+ $linked_attributes[ $attribute->attribute_name ] = $attribute->attribute_label;
+ }
+
+ // Add empty option placeholder to allow clearing
+ $linked_attributes = array_merge( array( '' => '' ), $linked_attributes );
+
+ $update_settings = array(
+ 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_trustbadge_code' => array(
+ 'default' => $this->base->get_trustbadge_code( false ),
+ ),
+ 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_font' => array(
+ 'options' => $this->get_font_families(),
+ ),
+ 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_code' => array(
+ 'default' => $this->base->get_review_sticker_code( false ),
+ ),
+ 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_rich_snippets_code' => array(
+ 'default' => $this->base->get_rich_snippets_code( false ),
+ ),
+ 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_code' => array(
+ 'default' => $this->base->get_product_sticker_code( false ),
+ ),
+ 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_code' => array(
+ 'default' => $this->base->get_product_widget_code( false ),
+ ),
+ 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_brand_attribute' => array(
+ 'options' => $linked_attributes,
+ ),
+ );
+
+ if ( $this->base->supports( 'reminder' ) ) {
+ $update_settings['woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_reminder_status'] = array(
+ 'options' => $this->get_order_statuses(),
+ );
+ }
+
+ $settings = $this->get_settings_array( $update_settings );
+
+ return $settings;
+ }
+
+ public function get_image( $img ) {
+ $language = $this->base->get_language();
+ $endings = array( '.jpg', '.png' );
+ $last = substr( $img, -4 );
+ $ending = '';
+
+ if ( in_array( $last, $endings ) ) {
+ $ending = $last;
+ $img = substr( $img, 0, -4 );
+ }
+
+ $new_img = $img . '_' . $language . $ending;
+
+ return $this->base->plugin->plugin_url() . '/assets/images/ts/' . $new_img;
+ }
+
+ public function get_sidebar() {
+ ob_start();
+ ?>
+
+ base->option_prefix . 'trusted_shops_id'] ) && $_POST['woocommerce_' . $this->base->option_prefix . 'trusted_shops_id'] != $this->base->id ) {
+ update_option( '_woocommerce_' . $this->base->option_prefix . 'trusted_shops_update_reviews', 1 );
+ }
+ }
+
+ public function after_save( $settings ) {
+ $this->base->refresh();
+
+ if ( get_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_integration_mode' ) === 'standard' ) {
+ // Delete code snippets
+ delete_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_trustbadge_code' );
+ delete_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_code' );
+ delete_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_code' );
+ delete_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_rich_snippets_code' );
+ delete_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_sticker_code' );
+ update_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_widget_selector', '#ts_product_widget' );
+ update_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_product_sticker_selector', '#ts_product_sticker' );
+ }
+
+ // Disable Reviews if Trusted Shops review collection has been enabled
+ if ( get_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_reviews_enable' ) === 'yes' ) {
+ update_option( 'woocommerce_enable_review_rating', 'no' );
+ }
+
+ if ( get_option( '_woocommerce_' . $this->base->option_prefix . 'trusted_shops_update_reviews' ) ) {
+ $this->base->get_dependency( 'schedule' )->update_reviews();
+ }
+
+ delete_option( '_woocommerce_' . $this->base->option_prefix . 'trusted_shops_update_reviews' );
+ }
+
+ public function review_collector_export_csv() {
+ if ( ! current_user_can( 'manage_woocommerce' ) )
+ return;
+
+ if ( ! isset( $_GET['action'] ) || $_GET['action'] != 'wc_' . $this->base->option_prefix . 'trusted-shops-export' || ( isset( $_GET['action'] ) && $_GET['action'] == 'wc_' . $this->base->option_prefix . 'trusted-shops-export' && ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'wc_' . $this->base->option_prefix . 'trusted-shops-export' ) ) )
+ return;
+
+ $interval_d = ( ( isset( $_GET['interval'] ) && ! empty( $_GET['interval'] ) ) ? absint( $_GET['interval'] ) : 30 );
+ $days_to_send = ( ( isset( $_GET['days'] ) && ! empty( $_GET['days'] ) ) ? absint( $_GET['days'] ) : 5 );
+ $status = ( ( isset( $_GET['status'] ) && ! empty( $_GET['status'] ) ) ? wc_clean( $_GET['status'] ) : '' );
+
+ update_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_collector_days_to_send', $days_to_send );
+ update_option( 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_review_collector', $interval_d );
+
+ if ( wc_ts_woocommerce_supports_crud() ) {
+ include_once( 'class-wc-trusted-shops-review-exporter.php' );
+
+ $exporter = new WC_Trusted_Shops_Review_Exporter();
+ $exporter->set_days_until_send( $days_to_send );
+ $exporter->set_interval_days( $interval_d );
+
+ if ( ! empty( $status ) ) {
+ $exporter->set_statuses( array( $status ) );
+ }
+
+ if ( isset( $_GET['lang'] ) && ! empty( $_GET['lang'] ) ) {
+ $exporter->set_lang( wc_clean( $_GET['lang'] ) );
+ }
+
+ $exporter->export();
+ }
+ }
+
+ public function review_collector_export() {
+
+ $href_org = admin_url();
+ $href_org = add_query_arg( array(
+ 'action' => 'wc_' . $this->base->option_prefix . 'trusted-shops-export',
+ '_wpnonce' => wp_create_nonce( 'wc_' . $this->base->option_prefix . 'trusted-shops-export' ),
+ 'lang' => $this->base->is_multi_language_setup() ? $this->base->get_multi_language_compatibility()->get_current_language() : '',
+ ), $href_org );
+
+ if ( ! wc_ts_woocommerce_supports_crud() )
+ return;
+
+ $days_interval = get_option( 'woocommerce_gzd_trusted_shops_review_collector', 30 );
+ $days_to_send = get_option( 'woocommerce_gzd_trusted_shops_review_collector_days_to_send', 5 );
+ ?>
+
+
+
get_trusted_url( 'https://www.trustedshops.com/tsb2b/sa/ratings/reviewCollector/reviewCollector.seam' ) . '" target="_blank">' . _x( 'My Trusted Shops account', 'trusted-shops', 'woocommerce-germanized' ) . '' ); ?>
+
+
+ false,
+ ) );
+
+ $url = empty( $url ) ? $this->base->signup_url : $url;
+
+ return $this->get_trusted_url( $url, $args );
+ }
+
+ public function get_trusted_url( $url, $args = array() ) {
+ $param_args = $this->base->et_params;
+ $args = wp_parse_args( $args, array(
+ 'utm_term' => substr( get_locale(), 0, 2 ),
+ 'shop_id' => $this->base->ID,
+ 'params' => false,
+ 'lang_mapping' => array(),
+ ) );
+
+ $current_lang = $this->base->get_language();
+
+ $base_lang = isset( $args['lang_mapping']['en'] ) ? $args['lang_mapping']['en'] : 'en';
+ $current_lang = isset( $args['lang_mapping'][ $current_lang ] ) ? $args['lang_mapping'][ $current_lang ] : $current_lang;
+ $url = str_replace( "/{$base_lang}/", '/' . $current_lang . '/', $url );
+
+ if ( 'gzd_' === $this->base->option_prefix && substr( $url, -11 ) === 'woocommerce' ) {
+ $url = str_replace( 'woocommerce', 'woocommerce_germanized', $url );
+ }
+
+ if ( $args['params'] ) {
+ $param_args = array_replace_recursive( $param_args, array(
+ 'utm_term' => $args['utm_term'],
+ 'shop_id' => $args['shop_id'],
+ ) );
+
+ return add_query_arg( $param_args, $url );
+ } else {
+ return $url;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-core.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-core.php
new file mode 100644
index 000000000..231c89121
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-core.php
@@ -0,0 +1,464 @@
+version = Package::get_version();
+
+ // Define constants
+ $this->define_constants();
+
+ // Include required files
+ $this->includes();
+ $this->setup_compatibility();
+
+ // Hooks
+ add_filter( 'plugin_action_links_' . plugin_basename( WC_TRUSTED_SHOPS_PLUGIN_FILE ), array( $this, 'action_links' ) );
+ add_action( 'init', array( $this, 'init' ), 1 );
+ add_filter( 'woocommerce_locate_template', array( $this, 'filter_templates' ), 0, 3 );
+
+ // Initialize Trusted Shops module
+ $this->trusted_shops = new WC_Trusted_Shops( $this, array(
+ 'supports' => Package::is_integration() ? array( 'reminder' ) : array(),
+ 'prefix' => Package::is_integration() ? 'GZD_' : '',
+ 'signup_url' => 'http://www.trustbadge.com/de/Preise/',
+ 'path' => WC_TRUSTED_SHOPS_ABSPATH . 'includes/',
+ ) );
+
+ // Loaded action
+ do_action( 'woocommerce_trusted_shops_loaded' );
+ }
+
+ /**
+ * Init Trusted Shops when WordPress initializes.
+ */
+ public function init() {
+ // Before init action
+ do_action( 'before_woocommerce_trusted_shops_init' );
+
+ if ( ! Package::is_integration() ) {
+ $this->load_plugin_textdomain();
+ add_filter( 'woocommerce_get_settings_pages', array( $this, 'add_settings' ) );
+ } else {
+ add_filter( 'woocommerce_email_classes', array( $this, 'add_emails' ), 10 );
+ add_filter( 'woocommerce_gzd_wpml_email_ids', array( $this, 'add_wpml_emails' ), 10 );
+ add_filter( 'woocommerce_gzd_admin_settings_tabs', array( $this, 'add_germanized_settings_tab' ), 10, 1 );
+ }
+
+ add_action( 'admin_enqueue_scripts', array( $this, 'add_admin_styles' ) );
+ add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ), 15 );
+ add_action( 'admin_print_styles', array( $this, 'add_notices' ), 1 );
+
+ // Change email template path if is germanized email template
+ add_filter( 'woocommerce_template_directory', array( $this, 'set_woocommerce_template_dir' ), 10, 2 );
+ add_filter( 'woocommerce_locate_core_template', array( $this, 'set_woocommerce_core_template_dir' ), 10, 3 );
+
+ add_action( 'woocommerce_admin_field_ts_toggle', array( $this, 'toggle_input' ), 10 );
+ add_filter( 'woocommerce_admin_settings_sanitize_option', array( $this, 'save_toggle_input_field' ), 0, 3 );
+
+ // Init action
+ do_action( 'woocommerce_trusted_shops_init' );
+ }
+
+ public function add_germanized_settings_tab( $tabs ) {
+ include_once dirname( __FILE__ ) . '/admin/settings/class-wc-ts-gzd-settings-tab.php';
+ $tabs['trusted_shops'] = 'WC_TS_GZD_Settings_Tab';
+ return $tabs;
+ }
+
+ /**
+ * Add notices + styles if needed.
+ */
+ public function add_notices() {
+ $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false;
+ $screen_id = $screen ? $screen->id : '';
+ $show_on_screens = array(
+ 'dashboard',
+ 'plugins',
+ );
+
+ $wc_screen_ids = function_exists( 'wc_get_screen_ids' ) ? wc_get_screen_ids() : array();
+
+ // Notices should only show on WooCommerce screens, the main dashboard, and on the plugins screen.
+ if ( ! in_array( $screen_id, $wc_screen_ids, true ) && ! in_array( $screen_id, $show_on_screens, true ) ) {
+ return;
+ }
+
+ if ( get_option( '_wc_ts_needs_update' ) == 1 ) {
+
+ if ( current_user_can( 'manage_woocommerce' ) ) {
+ wp_enqueue_style( 'woocommerce-activation', plugins_url( '/assets/css/activation.css', WC_PLUGIN_FILE ) );
+ wp_enqueue_style( 'woocommerce-ts-activation', plugins_url( '/assets/css/activation.css', WC_TRUSTED_SHOPS_PLUGIN_FILE ) );
+
+ add_action( 'admin_notices', array( $this, 'install_notice' ) );
+ }
+ }
+ }
+
+ /**
+ * Show the install notices
+ */
+ public function install_notice() {
+
+ // If we need to update, include a message with the update button
+ if ( get_option( '_wc_ts_needs_update' ) == 1 ) {
+ include( WC_TRUSTED_SHOPS_ABSPATH . 'includes/admin/views/html-notice-update.php' );
+ }
+ }
+
+ public function toggle_input( $value ) {
+ // Custom attribute handling.
+ $custom_attributes = array();
+
+ if ( ! empty( $value['custom_attributes'] ) && is_array( $value['custom_attributes'] ) ) {
+ foreach ( $value['custom_attributes'] as $attribute => $attribute_value ) {
+ $custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"';
+ }
+ }
+
+ // Description handling.
+ $field_description = WC_Admin_Settings::get_field_description( $value );
+ $description = $field_description['description'];
+ $tooltip_html = $field_description['tooltip_html'];
+ $option_value = WC_Admin_Settings::get_option( $value['id'], $value['default'] );
+ ?>
+
+
+
+
+
+
+
+
+
+ />
+
+
+ plugin_path() . '/templates/' . $template ) ) {
+ $core_file = $this->plugin_path() . '/templates/' . $template;
+ }
+
+ return $core_file;
+ }
+
+ public function set_woocommerce_template_dir( $dir, $template ) {
+ if ( file_exists( WC_trusted_shops()->plugin_path() . '/templates/' . $template ) ) {
+ return 'woocommerce-trusted-shops';
+ }
+
+ return $dir;
+ }
+
+ public function admin_footer_text( $footer_text ) {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ return;
+ }
+
+ // Check to make sure we're on a WooCommerce admin page
+ if ( isset( $_GET['tab'] ) && 'trusted-shops' === $_GET['tab'] ) {
+ $footer_text = sprintf( _x( 'If the App helped you, please leave a %s★★★★★%s in the Wordpress plugin repository.', 'trusted-shops', 'woocommerce-germanized' ), '', ' ' );
+ }
+
+ return $footer_text;
+ }
+
+ /**
+ * Auto-load WC_Trusted_Shops classes on demand to reduce memory consumption.
+ *
+ * @param mixed $class
+ * @return void
+ */
+ public function autoload( $class ) {
+ $class = strtolower( $class );
+ $path = $this->plugin_path() . '/includes/';
+
+ if ( 0 !== strpos( $class, 'wc_ts_' ) && 0 !== strpos( $class, 'wc_trusted_shops' ) ) {
+ return;
+ }
+
+ $file = 'class-' . str_replace( '_', '-', $class ) . '.php';
+
+ if ( strpos( $class, 'wc_ts_compatibility' ) !== false ) {
+ $path = $this->plugin_path() . '/includes/compatibility/';
+ }
+
+ if ( $path && is_readable( $path . $file ) ) {
+ include_once $path . $file;
+ return;
+ }
+ }
+
+ /**
+ * Get the plugin url.
+ *
+ * @return string
+ */
+ public function plugin_url() {
+ return Package::get_url();
+ }
+
+ /**
+ * Get the plugin path.
+ *
+ * @return string
+ */
+ public function plugin_path() {
+ return untrailingslashit( Package::get_path() );
+ }
+
+ /**
+ * Get the language path
+ *
+ * @return string
+ */
+ public function language_path() {
+ return $this->plugin_path() . '/i18n/languages';
+ }
+
+ /**
+ * Define WC_Germanized Constants
+ */
+ private function define_constants() {
+ define( 'WC_TRUSTED_SHOPS_PLUGIN_FILE', trailingslashit( Package::get_path() ) . 'woocommerce-trusted-shops.php' );
+ define( 'WC_TRUSTED_SHOPS_ABSPATH', trailingslashit( Package::get_path() ) );
+ define( 'WC_TRUSTED_SHOPS_VERSION', $this->version );
+ }
+
+ public function setup_compatibility() {
+ $plugins = apply_filters( 'woocommerce_ts_compatibilities',
+ array(
+ 'wpml-string-translation',
+ )
+ );
+ foreach ( $plugins as $comp ) {
+ $classname = str_replace( ' ', '_', 'WC_TS_Compatibility_' . ucwords( str_replace( '-', ' ', $comp ) ) );
+ if ( class_exists( $classname ) ) {
+ $this->compatibilities[ $comp ] = new $classname();
+ }
+ }
+ }
+
+ public function get_compatibility( $name ) {
+ return ( isset( $this->compatibilities[ $name ] ) ? $this->compatibilities[ $name ] : false );
+ }
+
+ /**
+ * Include required core files used in admin and on the frontend.
+ */
+ private function includes() {
+ include_once WC_TRUSTED_SHOPS_ABSPATH . 'includes/abstracts/abstract-wc-ts-compatibility.php';
+ include_once WC_TRUSTED_SHOPS_ABSPATH . 'includes/class-wc-ts-install.php';
+ }
+
+ /**
+ * Filter WooCommerce Templates to look into /templates before looking within theme folder
+ *
+ * @param string $template
+ * @param string $template_name
+ * @param string $template_path
+ * @return string
+ */
+ public function filter_templates( $template, $template_name, $template_path ) {
+ if ( ! $template_path ) {
+ $template_path = WC()->template_path();
+ }
+
+ // Make filter gzd_compatible
+ $template_name = apply_filters( 'woocommerce_trusted_shops_template_name', $template_name );
+
+ // Check Theme
+ $theme_template = locate_template(
+ array(
+ trailingslashit( $template_path ) . $template_name,
+ $template_name
+ )
+ );
+
+ // Load Default
+ if ( ! $theme_template ) {
+ if ( file_exists( $this->plugin_path() . '/templates/' . $template_name ) ) {
+ $template = $this->plugin_path() . '/templates/' . $template_name;
+ }
+ } else {
+ $template = $theme_template;
+ }
+
+ return apply_filters( 'woocommerce_trusted_shops_filter_template', $template, $template_name, $template_path );
+ }
+
+ /**
+ * Load Localisation files for WooCommerce Germanized.
+ */
+ public function load_plugin_textdomain() {
+ $locale = is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
+ $locale = apply_filters( 'plugin_locale', $locale, 'woocommerce-germanized' );
+ unload_textdomain( 'woocommerce-trusted-shops' );
+ load_textdomain( 'woocommerce-trusted-shops', trailingslashit( WP_LANG_DIR ) . 'woocommerce-trusted-shops/woocommerce-trusted-shops-' . $locale . '.mo' );
+ load_plugin_textdomain( 'woocommerce-trusted-shops', false, plugin_basename( dirname( __FILE__ ) ) . '/i18n/languages/' );
+ }
+
+ /**
+ * Show action links on the plugin screen
+ *
+ * @param mixed $links
+ * @return array
+ */
+ public function action_links( $links ) {
+ return array_merge( array(
+ '' . _x( 'Settings', 'trusted-shops', 'woocommerce-germanized' ) . ' ',
+ ), $links );
+ }
+
+ /**
+ * Add custom styles to Admin
+ */
+ public function add_admin_styles() {
+ $screen = get_current_screen();
+
+ if ( isset( $_GET['tab'] ) && 'trusted-shops' === $_GET['tab'] ) {
+ do_action( 'woocommerce_trusted_shops_load_admin_scripts' );
+ }
+ }
+
+ /**
+ * Add WooCommerce Germanized Settings Tab
+ *
+ * @param array $integrations
+ * @return array
+ */
+ public function add_settings( $integrations ) {
+ $integrations[] = new WC_TS_Settings_Handler();
+
+ return $integrations;
+ }
+
+ /**
+ * Add Custom Email templates
+ *
+ * @param array $mails
+ * @return array
+ */
+ public function add_emails( $mails ) {
+ $mails['WC_TS_Email_Customer_Trusted_Shops'] = include_once $this->plugin_path() . '/includes/emails/class-wc-ts-email-customer-trusted-shops.php';
+
+ return $mails;
+ }
+
+ public function add_wpml_emails( $mails ) {
+ $mails['WC_TS_Email_Customer_Trusted_Shops'] = 'customer_trusted_shops';
+
+ return $mails;
+ }
+}
+
+endif;
+
+/**
+ * Returns the global instance of WooCommerce Germanized
+ */
+function WC_trusted_shops() {
+ return WooCommerce_Trusted_Shops::instance();
+}
+
+$GLOBALS['woocommerce_trusted_shops'] = WC_trusted_shops();
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-review-exporter.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-review-exporter.php
new file mode 100644
index 000000000..01e5e9a31
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-review-exporter.php
@@ -0,0 +1,168 @@
+statuses = array_keys( wc_get_order_statuses() );
+ $this->column_names = $this->get_default_column_names();
+ }
+
+ /**
+ * Return an array of columns to export.
+ *
+ * @since 3.1.0
+ * @return array
+ */
+ public function get_default_column_names() {
+ return apply_filters( "woocommerce_gzd_{$this->export_type}_default_columns", array(
+ 'id' => 'reference',
+ 'date' => 'date',
+ 'days' => 'days',
+ 'billing_email' => 'email',
+ 'billing_first_name' => 'firstName',
+ 'billing_last_name' => 'lastName',
+ ) );
+ }
+
+ public function get_interval_days() {
+ return absint( $this->days_interval );
+ }
+
+ public function set_interval_days( $days ) {
+ $this->days_interval = absint( $days );
+ }
+
+ public function get_days_until_send() {
+ return $this->days_to_send;
+ }
+
+ public function set_days_until_send( $days ) {
+ $this->days_to_send = absint( $days );
+ }
+
+ public function get_statuses() {
+ return $this->statuses;
+ }
+
+ public function set_statuses( $statuses ) {
+ $this->statuses = (array) $statuses;
+ }
+
+ public function set_lang( $lang ) {
+ $this->lang = $lang;
+ }
+
+ public function get_lang() {
+ return $this->lang;
+ }
+
+ /**
+ * Prepare data that will be exported.
+ */
+ public function prepare_data_to_export() {
+ $columns = $this->get_column_names();
+ $date = date( 'Y-m-d', strtotime( '-' . $this->get_interval_days() . ' days' ) );
+ $args = array(
+ 'post_type' => 'shop_order',
+ 'post_status' => $this->get_statuses(),
+ 'showposts' => -1,
+ 'date_query' => array(
+ array(
+ 'after' => $date,
+ ),
+ ),
+ );
+
+ if ( $this->get_lang() !== '' && 'all' !== $this->get_lang() ) {
+ $args['meta_query'] = array();
+ $args['meta_query']['wpml'] = array(
+ 'key' => 'wpml_language',
+ 'compare' => '=',
+ 'value' => $this->get_lang(),
+ );
+ }
+
+ $order_query = new WP_Query( apply_filters( "woocommerce_gzd_{$this->export_type}_query_args", $args ) );
+ $this->total_rows = $order_query->found_posts;
+ $this->row_data = array();
+
+ while ( $order_query->have_posts() ) {
+ $order_query->next_post();
+
+ $order = wc_get_order( $order_query->post->ID );
+ $row = array();
+
+ foreach ( $columns as $column_id => $column_name ) {
+ $column_id = strstr( $column_id, ':' ) ? current( explode( ':', $column_id ) ) : $column_id;
+ $value = '';
+
+ if ( is_callable( array( $this, "get_column_value_{$column_id}" ) ) ) {
+ // Handle special columns which don't map 1:1 to order data.
+ $value = $this->{"get_column_value_{$column_id}"}( $order );
+
+ } elseif ( wc_ts_get_crud_data( $order, $column_id ) ) {
+ // Default and custom handling.
+ $value = wc_ts_get_crud_data( $order, $column_id );
+ }
+
+ $row[ $column_id ] = $value;
+ }
+
+ $this->row_data[] = apply_filters( 'woocommerce_gzd_trusted_shops_review_export_row_data', $row, $order );
+ }
+ }
+
+ public function get_column_value_date( $order ) {
+ return wc_ts_get_order_date( $order, 'd.m.Y' );
+ }
+
+ public function get_column_value_days( $order ) {
+ return $this->get_days_until_send();
+ }
+}
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-schedule.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-schedule.php
new file mode 100644
index 000000000..d6b779ef6
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-schedule.php
@@ -0,0 +1,246 @@
+base = $base;
+
+ add_action( 'woocommerce_gzd_trusted_shops_reviews', array( $this, 'update_reviews' ) );
+ add_action( 'admin_init', array( $this, 'update_default_reviews' ), 10 );
+ add_action( 'woocommerce_gzd_trusted_shops_reviews', array( $this, 'send_mails' ) );
+ }
+
+ public function update_default_reviews() {
+ // Generate reviews for the first time
+ $option_key = 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_reviews_cache';
+
+ $section = isset( $_GET['section'] ) ? wc_clean( $_GET['section'] ) : '';
+
+ // Do only update default reviews if the admin user open the settings page.
+ if ( 'trusted_shops' !== $section ) {
+ return;
+ }
+
+ if ( ! get_option( $option_key, false ) ) {
+ $this->_update_reviews();
+ }
+
+ if ( $this->base->is_multi_language_setup() ) {
+ $compatibility = $this->base->get_multi_language_compatibility();
+ $current_language = $compatibility->get_current_language();
+
+ global $wc_ts_original_lang;
+
+ $wc_ts_original_lang = $current_language;
+
+ foreach( $compatibility->get_languages() as $language ) {
+
+ if ( $compatibility->get_default_language() == $language ) {
+ continue;
+ }
+
+ $option_key .= '_' . $language;
+
+ if ( ! get_option( $option_key, false ) ) {
+ $this->_update_reviews( $language );
+ }
+ }
+ }
+ }
+
+ /**
+ * Update Review Cache by grabbing information from xml file
+ */
+ public function update_reviews() {
+ $this->_update_reviews();
+
+ if ( $this->base->is_multi_language_setup() ) {
+
+ $compatibility = $this->base->get_multi_language_compatibility();
+ $current_language = $compatibility->get_current_language();
+
+ global $wc_ts_original_lang;
+
+ $wc_ts_original_lang = $current_language;
+
+ foreach( $compatibility->get_languages() as $language ) {
+ if ( $compatibility->get_default_language() == $language ) {
+ continue;
+ }
+
+ $this->_update_reviews( $language );
+ }
+ }
+ }
+
+ protected function _update_reviews( $lang = '' ) {
+ if ( ! empty( $lang ) ) {
+ wc_ts_switch_language( $lang );
+ }
+
+ if ( ! $this->base->is_rich_snippets_enabled() ) {
+ if ( ! empty( $lang ) ) {
+ wc_ts_restore_language();
+ }
+
+ return;
+ }
+
+ $update = array();
+
+ if ( $this->base->is_enabled() ) {
+
+ $response = wp_remote_post( $this->base->api_url );
+
+ if ( is_array( $response ) ) {
+ $output = json_decode( $response['body'], true );
+
+ if ( isset( $output['response']['data'] ) ) {
+ $reviews = $output['response']['data']['shop']['qualityIndicators']['reviewIndicator'];
+ $update['count'] = (string) $reviews['activeReviewCount'];
+ $update['avg'] = (float) $reviews['overallMark'];
+ $update['max'] = '5.00';
+ }
+ }
+ }
+
+ $option_key = 'woocommerce_' . $this->base->option_prefix . 'trusted_shops_reviews_cache';
+
+ if ( ! empty( $lang ) ) {
+ $option_key .= '_' . $lang;
+ }
+
+ update_option( $option_key, $update );
+
+ if ( ! empty( $lang ) ) {
+ wc_ts_restore_language();
+ }
+ }
+
+ /**
+ * Placeholder to avoid fatal errors within scheduled actions.
+ *
+ * @deprecated 2.2.5
+ */
+ public function update_review_widget() {}
+
+ /**
+ * Send review reminder mails after x days
+ */
+ public function send_mails() {
+ if ( $this->base->is_multi_language_setup() ) {
+ $compatibility = $this->base->get_multi_language_compatibility();
+ $current_language = $compatibility->get_current_language();
+
+ global $wc_ts_original_lang;
+
+ $wc_ts_original_lang = $current_language;
+
+ foreach ( $compatibility->get_languages() as $language ) {
+ $this->_send_mails( $language );
+ }
+
+ } else {
+ $this->_send_mails();
+ }
+ }
+
+ protected function _send_mails( $lang = '' ) {
+ if ( ! empty( $lang ) ) {
+ wc_ts_switch_language( $lang );
+ }
+
+ if ( ! $this->base->is_review_reminder_enabled() ) {
+ if ( ! empty( $lang ) ) {
+ wc_ts_restore_language();
+ }
+
+ return;
+ }
+
+ $order_statuses = $this->base->review_reminder_status;
+
+ if ( ! is_array( $order_statuses ) ) {
+ $order_statuses = array( $order_statuses );
+ }
+
+ $args = array(
+ 'post_type' => 'shop_order',
+ 'post_status' => apply_filters( 'woocommerce_trusted_shops_review_reminder_valid_order_statuses', $order_statuses ),
+ 'showposts' => -1,
+ 'meta_query' => array(
+ 'relation' => 'AND',
+ 'is_sent' => array(
+ 'key' => '_trusted_shops_review_mail_sent',
+ 'compare' => 'NOT EXISTS',
+ ),
+ 'opted_in' => array(
+ 'key' => '_ts_review_reminder_opted_in',
+ 'compare' => '=',
+ 'value' => 'yes'
+ ),
+ ),
+ );
+
+ if ( ! empty( $lang ) ) {
+ $args['meta_query']['wpml'] = array(
+ 'key' => 'wpml_language',
+ 'compare' => '=',
+ 'value' => $lang,
+ );
+ }
+
+ $order_query = new WP_Query( apply_filters( 'woocommerce_trusted_shops_review_reminder_order_args', $args, $lang ) );
+
+ while ( $order_query->have_posts() ) {
+
+ $order_query->next_post();
+
+ if ( ! $order = wc_get_order( $order_query->post->ID ) ) {
+ continue;
+ }
+
+ $completed_date = apply_filters( 'woocommerce_trusted_shops_review_reminder_order_completed_date', $order->get_date_completed(), $order );
+
+ if ( ! $completed_date ) {
+ continue;
+ }
+
+ $now = new DateTime();
+ $diff = $now->diff( $completed_date );
+ $min_days = (int) $this->base->review_reminder_days;
+
+ if ( $diff->days >= $min_days ) {
+
+ if ( apply_filters( 'woocommerce_trusted_shops_send_review_reminder_email', true, $order ) ) {
+
+ $mails = WC()->mailer()->get_emails();
+
+ foreach ( $mails as $mail ) {
+
+ if ( 'customer_trusted_shops' === $mail->id ) {
+ $mail->trigger( wc_ts_get_crud_data( $order, 'id' ) );
+
+ update_post_meta( wc_ts_get_crud_data( $order, 'id' ), '_trusted_shops_review_mail_sent', 1 );
+ }
+ }
+ }
+ }
+ }
+
+ if ( ! empty( $lang ) ) {
+ wc_ts_restore_language();
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-shortcodes.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-shortcodes.php
new file mode 100644
index 000000000..154ae4251
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-shortcodes.php
@@ -0,0 +1,84 @@
+base = $base;
+
+ add_action( 'init', array( $this, 'init' ), 3 );
+ }
+
+ public function init() {
+
+ // Define shortcodes
+ $shortcodes = array(
+ 'trusted_shops_rich_snippets' => array( $this, 'trusted_shops_rich_snippets' ),
+ 'trusted_shops_review_sticker' => array( $this, 'trusted_shops_review_sticker' ),
+ 'trusted_shops_badge' => array( $this, 'trusted_shops_badge' ),
+ );
+
+ foreach ( $shortcodes as $shortcode => $function ) {
+ add_shortcode( apply_filters( "{$shortcode}_shortcode_tag", $shortcode ), $function );
+ }
+
+ }
+
+ /**
+ * Returns Trusted Shops rich snippet review html
+ *
+ * @param array $atts
+ * @return string
+ */
+ public function trusted_shops_rich_snippets( $atts ) {
+ ob_start();
+ wc_get_template( 'trusted-shops/rich-snippets.php', array(
+ 'plugin' => $this->base
+ ) );
+ $html = ob_get_clean();
+
+ return $this->base->is_enabled() ? $html : '';
+ }
+
+ /**
+ * Returns Trusted Shops reviews graphic
+ *
+ * @param array $atts
+ * @return string
+ */
+ public function trusted_shops_review_sticker( $atts ) {
+ $atts = wp_parse_args( $atts, array(
+ 'element' => '#ts_review_sticker',
+ ) );
+
+ ob_start();
+ wc_get_template( 'trusted-shops/review-sticker.php', array(
+ 'plugin' => $this->base,
+ 'element' => $atts['element'],
+ ) );
+ $html = ob_get_clean();
+ return $this->base->is_enabled() ? '' . $html . '
' : '';
+ }
+
+ /**
+ * Returns Trusted Shops Badge html
+ *
+ * @param array $atts
+ * @return string
+ */
+ public function trusted_shops_badge( $atts ) {
+ extract( shortcode_atts( array('width' => ''), $atts ) );
+
+ return $this->base->is_enabled() ? ' ' : '';
+ }
+
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-template-hooks.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-template-hooks.php
new file mode 100644
index 000000000..88e4ac196
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-template-hooks.php
@@ -0,0 +1,251 @@
+base = $base;
+
+ // Load hooks on init so that language-specific settings are loaded
+ add_action( 'init', array( $this, 'init' ), 10 );
+
+ // Always register checkbox to avoid language problems
+ add_action( 'woocommerce_gzd_register_legal_core_checkboxes', array( $this, 'review_reminder_checkbox' ), 30 );
+ }
+
+ public function init() {
+ if ( $this->base->is_enabled() ) {
+ add_action( 'woocommerce_thankyou', array( $this, 'template_thankyou' ), 10, 1 );
+ add_action( 'wp_footer', array( $this, 'template_trustbadge' ), 250 );
+ }
+
+ if ( $this->base->product_reviews_visible() ) {
+ add_filter( 'woocommerce_product_tabs', array( $this, 'remove_review_tab' ), 40, 1 );
+ }
+
+ if ( $this->base->is_product_sticker_enabled() ) {
+ add_filter( 'woocommerce_product_tabs', array( $this, 'review_tab' ), 50, 1 );
+ }
+
+ if ( $this->base->is_product_widget_enabled() ) {
+ add_filter( 'woocommerce_trusted_shops_template_name', array( $this, 'set_product_widget_template' ), 50, 1 );
+ }
+
+ if ( $this->base->is_rich_snippets_enabled() ) {
+ add_action( 'wp_footer', array( $this, 'insert_rich_snippets' ), 20 );
+ }
+
+ // Save Fields on order
+ if ( $this->base->is_review_reminder_enabled() ) {
+ add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'update_order_meta' ) );
+
+ if ( 'yes' === $this->base->review_reminder_opt_out ) {
+ // Email notices right beneath order table
+ add_action( 'woocommerce_email_after_order_table', array( $this, 'email_cancel_review_reminder' ), 8, 3 );
+ add_filter( 'woocommerce_email_styles', array( $this, 'email_styles' ) );
+
+ // Check for customer activation
+ add_action( 'template_redirect', array( $this, 'cancel_review_reminder_check' ) );
+ }
+ }
+ }
+
+ public function insert_rich_snippets() {
+ $insert = false;
+
+ if ( in_array( 'category', $this->base->get_rich_snippets_locations() ) ) {
+ if ( is_product_category() ) {
+ $insert = true;
+ }
+ }
+
+ if ( in_array( 'home', $this->base->get_rich_snippets_locations() ) ) {
+ if ( function_exists( 'is_shop' ) && is_shop() ) {
+ $insert = true;
+ }
+ }
+
+ if ( in_array( 'product', $this->base->get_rich_snippets_locations() ) ) {
+ if ( is_product() ) {
+ $insert = true;
+ }
+ }
+
+ if ( $insert ) {
+ echo do_shortcode( '[trusted_shops_rich_snippets]' );
+ }
+ }
+
+ public function cancel_review_reminder_check() {
+ if ( isset( $_GET['disable-review-reminder'] ) && isset( $_GET['order-id'] ) ) {
+
+ $order_id = absint( $_GET['order-id'] );
+ $code = wc_clean( $_GET['disable-review-reminder'] );
+
+ if ( ! empty( $code ) && ! empty( $order_id ) ) {
+
+ $order_query = new WP_Query( array(
+ 'post_type' => 'shop_order',
+ 'p' => $order_id,
+ 'post_status' => array_keys( wc_get_order_statuses() ),
+ 'posts_per_page' => 1,
+ 'meta_query' => array(
+ 'code' => array(
+ 'key' => '_ts_cancel_review_reminder_code',
+ 'compare' => '=',
+ 'value' => $code,
+ ),
+ ),
+ ) );
+
+ while ( $order_query->have_posts() ) {
+ $order_query->next_post();
+ $order = wc_get_order( $order_query->post->ID );
+
+ if ( $order ) {
+ $order_id = wc_ts_get_crud_data( $order, 'id' );
+
+ delete_post_meta( $order_id, '_ts_cancel_review_reminder_code' );
+ delete_post_meta( $order_id, '_ts_review_reminder_opted_in' );
+
+ wp_die( sprintf( _x( 'Your review reminder e-mail has been cancelled successfully. Return to %s.', 'trusted-shops', 'woocommerce-germanized' ), '' . _x( 'Home', 'trusted-shops', 'woocommerce-germanized' ) . ' ' ) );
+ }
+ }
+ }
+ }
+ }
+
+ public function email_styles( $css ) {
+ $css .= '
+ .wc-ts-cancel-review-reminder {
+ margin-top: 16px;
+ }
+ ';
+
+ return $css;
+ }
+
+ public function get_cancel_review_reminder_link( $order ) {
+ $code = wc_ts_get_crud_data( $order, 'ts_cancel_review_reminder_code' );
+
+ if ( ! $code || empty( $code ) ) {
+ global $wp_hasher;
+
+ if ( empty( $wp_hasher ) ) {
+ require_once ABSPATH . WPINC . '/class-phpass.php';
+ $wp_hasher = new PasswordHash( 8, true );
+ }
+
+ $code = $wp_hasher->HashPassword( wp_generate_password( 20 ) );
+
+ update_post_meta( wc_ts_get_crud_data( $order, 'id' ), '_ts_cancel_review_reminder_code', $code );
+ }
+
+ $order_id = wc_ts_get_crud_data( $order, 'id' );
+ $link = add_query_arg( array( 'disable-review-reminder' => $code, 'order-id' => $order_id ), get_site_url() );
+
+ if ( $lang = wc_ts_get_order_language( $order ) ) {
+ $link = add_query_arg( array( 'lang' => $lang ), $link );
+ }
+
+ return apply_filters( 'woocommerce_trusted_shops_cancel_review_reminder_link', $link, $code, $order );
+ }
+
+ public function email_cancel_review_reminder( $order, $sent_to_admin, $plain_text ) {
+ $type = WC_germanized()->emails->get_current_email_object();
+
+ // Try to flush the cache before continuing
+ WC_GZD_Cache_Helper::maybe_flush_cache( 'db', array( 'cache_type' => 'meta', 'meta_type' => 'post', 'meta_key' => 'ts_review_reminder_opted_in' ) );
+ $opted_in = wc_ts_get_crud_data( $order, 'ts_review_reminder_opted_in' );
+
+ if ( $type && 'yes' === $opted_in && 'customer_processing_order' === $type->id ) {
+ wc_get_template( 'emails/cancel-review-reminder.php', array( 'link' => $this->get_cancel_review_reminder_link( $order ) ) );
+ }
+ }
+
+ public function update_order_meta( $order_id ) {
+ $checkbox = wc_gzd_get_legal_checkbox( 'review_reminder' );
+
+ if ( isset( $_POST['review_reminder'] ) || ! $checkbox || ( $checkbox && ! $checkbox->is_enabled() ) ) {
+ update_post_meta( $order_id, '_ts_review_reminder_opted_in', 'yes' );
+ }
+ }
+
+ public function review_reminder_checkbox() {
+ if ( ! function_exists( 'wc_gzd_register_legal_checkbox' ) ) {
+ return;
+ }
+
+ wc_gzd_register_legal_checkbox( 'review_reminder', array(
+ 'html_id' => 'review-reminder',
+ 'html_name' => 'review_reminder',
+ 'html_wrapper_classes' => array( 'legal' ),
+ 'label' => _x( 'Yes, I would like to be reminded via e-mail after {days} day(s) to review my order. I am able to cancel the reminder at any time by clicking on the "cancel review reminder" link within the order confirmation.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'label_args' => array( '{days}' => $this->base->review_reminder_days ),
+ 'hide_input' => false,
+ 'is_enabled' => false,
+ 'is_mandatory' => false,
+ 'error_message' => _x( 'Please allow us to send a review reminder by e-mail.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'priority' => 6,
+ 'is_core' => true,
+ 'admin_name' => _x( 'Review reminder', 'trusted-shops', 'woocommerce-germanized' ),
+ 'admin_desc' => _x( 'Asks the customer to receive a Trusted Shops review reminder.', 'trusted-shops', 'woocommerce-germanized' ),
+ 'locations' => array( 'checkout' ),
+ ) );
+ }
+
+ public function set_product_widget_template( $template ) {
+
+ if ( in_array( $template, array( 'single-product/rating.php' ) ) ) {
+ $template = 'trusted-shops/product-widget.php';
+ }
+
+ return $template;
+ }
+
+ public function remove_review_tab( $tabs ) {
+ if ( isset( $tabs['reviews'] ) )
+ unset( $tabs['reviews'] );
+
+ return $tabs;
+ }
+
+ public function review_tab( $tabs ) {
+ $tabs['trusted_shops_reviews'] = array(
+ 'title' => $this->base->product_sticker_tab_text,
+ 'priority' => 30,
+ 'callback' => array( $this, 'template_product_sticker' ),
+ );
+ return $tabs;
+ }
+
+ public function template_review_sticker( $template ) {
+ wc_get_template( 'trusted-shops/review-sticker.php', array( 'plugin' => $this->base, 'element' => '#ts_review_sticker' ) );
+ }
+
+ public function template_product_sticker( $template ) {
+ wc_get_template( 'trusted-shops/product-sticker.php', array( 'plugin' => $this->base ) );
+ }
+
+ public function template_trustbadge() {
+ wc_get_template( 'trusted-shops/trustbadge.php', array( 'plugin' => $this->base ) );
+ }
+
+ public function template_thankyou( $order_id ) {
+ wc_get_template( 'trusted-shops/thankyou.php', array(
+ 'order_id' => $order_id,
+ 'plugin' => $this->base,
+ ) );
+ }
+
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-widgets.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-widgets.php
new file mode 100644
index 000000000..57494adbe
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops-widgets.php
@@ -0,0 +1,33 @@
+base = $base;
+
+ add_action( 'widgets_init', array( $this, 'include_widgets' ), 25 );
+ }
+
+ public function include_widgets() {
+ if ( $this->base->is_review_sticker_enabled() ) {
+ $this->register_widget( 'review_sticker' );
+ }
+ }
+
+ private function register_widget( $name ) {
+ $classname = $this->base->get_dependency_name( 'widget_' . $name );
+ include_once( 'widgets/class-' . strtolower( str_replace( '_', '-', $classname ) ) . '.php' );
+ register_widget( $classname );
+ }
+
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops.php b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops.php
new file mode 100644
index 000000000..16035bb79
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-trusted-shops.php
@@ -0,0 +1,660 @@
+plugin = $plugin;
+
+ $args = wp_parse_args( $params, array(
+ 'et_params' => array(),
+ 'signup_params' => array(),
+ 'prefix' => '',
+ 'signup_url' => '',
+ 'supports' => array( 'reminder' ),
+ 'path' => dirname( __FILE__ ) . '/'
+ ) );
+
+ foreach ( $args as $arg => $val ) {
+ $this->$arg = $val;
+ }
+
+ $this->option_prefix = strtolower( $this->prefix );
+ $this->load();
+ }
+
+ public function load() {
+
+ // Refresh TS ID + API URL
+ $this->refresh();
+ $this->includes();
+
+ add_action( 'init', array( $this, 'refresh' ), 50 );
+
+ if ( is_admin() ) {
+ $this->get_dependency( 'admin' );
+ }
+
+ $this->get_dependency( 'schedule' );
+ $this->get_dependency( 'shortcodes' );
+ $this->get_dependency( 'widgets' );
+ $this->get_dependency( 'template_hooks' );
+
+ if ( $this->is_enabled() ) {
+ add_action( 'wp_enqueue_scripts', array( $this, 'load_frontend_assets' ), 50 );
+
+ if ( is_admin() ) {
+ add_filter( 'woocommerce_gzd_wpml_translatable_options', array( $this, 'register_wpml_options' ), 20, 1 );
+ add_filter( 'woocommerce_gzd_wpml_remove_translation_empty_equal', array( $this, 'stop_wpml_options_string_deletions' ), 20, 4 );
+ }
+ }
+ }
+
+ public function register_wpml_options( $settings ) {
+ $admin = $this->get_dependency( 'admin' );
+
+ return array_merge( $settings, $admin->get_translatable_settings() );
+ }
+
+ /**
+ * Make sure that other languages are not synced with main language e.g. option does not default to main language
+ *
+ * @param $allow
+ * @param $option
+ * @param $new_value
+ * @param $old_value
+ * @return bool
+ */
+ public function stop_wpml_options_string_deletions( $allow, $option, $new_value, $old_value ) {
+ $admin = $this->get_dependency( 'admin' );
+
+ if ( array_key_exists( $option, $admin->get_translatable_settings() ) ) {
+ $allow = false;
+ }
+
+ return $allow;
+ }
+
+ public function includes() {
+ include_once( $this->path . 'wc-ts-core-functions.php' );
+ }
+
+ public function load_frontend_assets() {
+ $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
+ $assets_path = $this->plugin->plugin_url() . '/assets/css';
+
+ wp_register_style( 'woocommerce-trusted-shops', $assets_path . '/layout' . $suffix . '.css', false, $this->plugin->version );
+ wp_enqueue_style( 'woocommerce-trusted-shops' );
+ }
+
+ public function get_dependency_name( $name ) {
+ $classname = 'WC_Trusted_Shops_' . ucwords( str_replace( '-', '_', strtolower( $name ) ) );
+ return $classname;
+ }
+
+ public function get_dependency( $name ) {
+ $classname = $this->get_dependency_name( $name );
+
+ return call_user_func_array( array( $classname, 'instance' ), array( $this ) );
+ }
+
+ public function refresh() {
+ $this->id = $this->__get( 'id' );
+ $this->api_url = 'http://api.trustedshops.com/rest/public/v2/shops/'. $this->id .'/quality.json';
+ }
+
+ public function get_multi_language_compatibility() {
+ return apply_filters( 'woocommerce_trusted_shops_multi_language_compatibility', $this->plugin->get_compatibility( 'wpml-string-translation' ) );
+ }
+
+ public function is_multi_language_setup() {
+ $compatibility = $this->get_multi_language_compatibility();
+
+ return $compatibility->is_activated() ? true : false;
+ }
+
+ /**
+ * Get Trusted Shops Options
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get( $key ) {
+ $option_name = 'woocommerce_' . $this->option_prefix . 'trusted_shops_' . $key;
+ $value = get_option( $option_name );
+
+ /**
+ * By default WPML does not allow empty strings to override default translations.
+ * This snippet manually checks for translations and allows to override default WPML translations.
+ */
+ if ( ! is_admin() && $this->is_multi_language_setup() ) {
+ $compatibility = $this->get_multi_language_compatibility();
+
+ $default_language = $compatibility->get_default_language();
+ $current_language = $compatibility->get_current_language();
+
+ if ( $current_language !== $default_language ) {
+ if ( isset( $this->options[ $current_language ][ $key ] ) ) {
+ return $this->options[ $current_language ][ $key ];
+ } else {
+ if ( $string_id = $compatibility->get_string_id( $option_name ) ) {
+ $translation = $compatibility->get_string_translation( $string_id, $current_language );
+
+ if ( false !== $translation ) {
+ $this->options[ $current_language ][ $key ] = $translation;
+ $value = $translation;
+ }
+ }
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Checks whether a certain Trusted Shops Option isset
+ *
+ * @param string $key
+ * @return boolean
+ */
+ public function __isset( $key ) {
+ return ( ! get_option( 'woocommerce_' . $this->option_prefix . 'trusted_shops_' . $key ) ) ? false : true;
+ }
+
+ /**
+ * Checks whether Trusted Shops is enabled
+ *
+ * @return boolean
+ */
+ public function is_enabled() {
+ return ( $this->id ) ? true : false;
+ }
+
+ public function get_rich_snippets_locations() {
+ $locations = array();
+
+ if ( $this->rich_snippets_category === 'yes' ) {
+ $locations[] = 'category';
+ }
+
+ if ( $this->rich_snippets_product === 'yes' ) {
+ $locations[] = 'product';
+ }
+
+ if ( $this->rich_snippets_home === 'yes' ) {
+ $locations[] = 'home';
+ }
+
+ return $locations;
+ }
+
+ public function is_trustbadge_enabled() {
+ return ( $this->trustbadge_enable === 'yes' && $this->id !== '' ? true : false );
+ }
+
+ /**
+ * Checks whether Trusted Shops Rich Snippets are enabled
+ *
+ * @return boolean
+ */
+ public function is_rich_snippets_enabled() {
+ return ( $this->rich_snippets_enable === 'yes' && $this->is_enabled() ? true : false );
+ }
+
+ /**
+ * Checks whether review widget is enabled
+ *
+ * @return boolean
+ */
+ public function is_review_widget_enabled() {
+ return ( $this->review_widget_enable === 'yes' && $this->is_enabled() ? true : false );
+ }
+
+ public function is_review_reminder_enabled() {
+ return ( $this->review_reminder_enable === 'yes' && $this->supports( 'reminder' ) && $this->is_enabled() ? true : false );
+ }
+
+ public function is_review_reminder_checkbox_enabled() {
+ return ( $this->review_reminder_checkbox === 'yes' && $this->is_review_reminder_enabled() ? true : false );
+ }
+
+ public function product_reviews_visible() {
+ return ( $this->is_enabled() && $this->is_product_sticker_enabled() ? true : false );
+ }
+
+ public function is_product_reviews_enabled() {
+ return ( $this->reviews_enable === 'yes' && $this->is_enabled() ? true : false );
+ }
+
+ public function is_product_sticker_enabled() {
+ return ( $this->is_product_reviews_enabled() && $this->product_sticker_enable === 'yes' ? true : false );
+ }
+
+ public function is_review_sticker_enabled() {
+ return $this->review_sticker_enable === 'yes' ? true : false;
+ }
+
+ public function is_product_widget_enabled() {
+ return ( $this->is_product_reviews_enabled() && $this->product_widget_enable === 'yes' ? true : false );
+ }
+
+ public function supports( $type ) {
+ return ( in_array( $type, $this->supports ) ? true : false );
+ }
+
+ /**
+ * Gets Trusted Shops payment gateway by woocommerce payment id
+ *
+ * @param integer $payment_method_id
+ * @return string
+ */
+ public function get_payment_gateway( $payment_method_id ) {
+ $gateways = WC()->payment_gateways()->payment_gateways();
+
+ if ( array_key_exists( $payment_method_id, $gateways ) ) {
+ return $gateways[ $payment_method_id ]->get_method_title();
+ } else {
+ return 'wcOther';
+ }
+ }
+
+ /**
+ * Returns the average rating by grabbing the rating from the current languages' cache.
+ *
+ * @return array
+ */
+ public function get_average_rating() {
+ $reviews = ( $this->reviews_cache ? $this->reviews_cache : array() );
+
+ if ( $this->is_multi_language_setup() ) {
+ $default_language = $this->get_multi_language_compatibility()->get_default_language();
+ $current_language = $this->get_multi_language_compatibility()->get_current_language();
+
+ if ( $current_language != $default_language ) {
+ $reviews = ( $this->{"reviews_cache_{$current_language}"} ? $this->{"reviews_cache_{$current_language}"} : array() );
+ }
+ }
+
+ return $reviews;
+ }
+
+ /**
+ * Returns the certificate link
+ *
+ * @return string
+ */
+ public function get_certificate_link() {
+ return 'https://www.trustedshops.com/shop/certificate.php?shop_id=' . $this->id;
+ }
+
+ /**
+ * Returns add new rating link
+ *
+ * @return string
+ */
+ public function get_new_review_link( $email, $order_id ) {
+ return 'https://www.trustedshops.de/bewertung/bewerten_' . $this->id . '.html&buyerEmail=' . urlencode( base64_encode( $email ) ) . '&shopOrderID=' . urlencode( base64_encode( $order_id ) );
+ }
+
+ /**
+ * Returns the rating link
+ *
+ * @return string
+ */
+ public function get_rating_link() {
+ return 'https://www.trustedshops.de/bewertung/info_' . $this->id . '.html';
+ }
+
+ /**
+ * Gets the attachment id of review widget graphic
+ *
+ * @return mixed
+ */
+ public function get_review_widget_attachment() {
+ return ( ! $this->review_widget_attachment ? false : $this->review_widget_attachment );
+ }
+
+ protected function get_product_shopping_data( $id, $attribute ) {
+ $product = is_numeric( $id ) ? wc_get_product( $id ) : $id;
+
+ if ( ! $product ) {
+ return false;
+ }
+
+ $data = wc_ts_get_crud_data( $product, $attribute );
+
+ if ( 'variation' === $product->get_type() ) {
+ if ( empty( $data ) ) {
+ $parent = wc_get_product( wc_ts_get_crud_data( $product, 'parent' ) );
+ $data = wc_ts_get_crud_data( $parent, $attribute );
+ }
+ }
+
+ return $data;
+ }
+
+ public function get_product_image( $id ) {
+ $product = is_numeric( $id ) ? wc_get_product( $id ) : $id;
+
+ if ( ! $product ) {
+ return false;
+ }
+
+ $image = '';
+
+ if ( is_callable( array( $product, 'get_image_id' ) ) ) {
+ $image_id = $product->get_image_id();
+ $images = wp_get_attachment_image_src( $image_id, 'shop_single' );
+
+ if ( ! empty( $images ) ) {
+ $image = $images[0];
+ }
+ } else {
+ if ( has_post_thumbnail( wc_ts_get_crud_data( $product, 'id' ) ) ) {
+ $images = wp_get_attachment_image_src( get_post_thumbnail_id( wc_ts_get_crud_data( $product, 'id' ) ), 'shop_single' );
+
+ if ( ! empty( $images ) ) {
+ $image = $images[0];
+ }
+ }
+ }
+
+ return $image;
+ }
+
+ public function get_product_brand( $id ) {
+ $product = is_numeric( $id ) ? wc_get_product( $id ) : $id;
+
+ if ( ! $product ) {
+ return false;
+ }
+
+ return $product->get_attribute( $this->brand_attribute );
+ }
+
+ public function get_product_mpn( $id ) {
+ return $this->get_product_shopping_data( $id, '_ts_mpn' );
+ }
+
+ public function get_product_gtin( $id ) {
+ return $this->get_product_shopping_data( $id, '_ts_gtin' );
+ }
+
+ public function get_product_skus( $id ) {
+ $product = is_numeric( $id ) ? wc_get_product( $id ) : $id;
+ $skus = array();
+ $skus[] = ( $product->get_sku() ) ? $product->get_sku() : wc_ts_get_crud_data( $product, 'id' );
+
+ if ( 'grouped' === $product->get_type() ) {
+ foreach( $product->get_children() as $child ) {
+ if ( $child_product = wc_get_product( $child ) ) {
+ $skus[] = ( $child_product->get_sku() ) ? $child_product->get_sku() : wc_ts_get_crud_data( $child_product, 'id' );
+ }
+ }
+ }
+
+ return $skus;
+ }
+
+ public function get_template( $name ) {
+ $html = "";
+
+ ob_start();
+ wc_get_template( 'trusted-shops/' . str_replace( '_', '-', $name ) . '-tpl.php', array( 'plugin' => $this ) );
+ $html = ob_get_clean();
+
+ return preg_replace('/^\h*\v+/m', '', strip_tags( $html ) );
+ }
+
+ public function get_script( $name, $replace = true, $args = array() ) {
+ $script = $this->get_template( $name );
+
+ if ( $this->integration_mode === 'expert' ) {
+ $option_script = $this->{$name . "_code"};
+
+ if ( $option_script ) {
+ $script = $option_script;
+ }
+ }
+
+ if ( $replace ) {
+ $args = wp_parse_args( $args, array(
+ 'id' => $this->id,
+ 'locale' => $this->get_locale(),
+ ) );
+
+ foreach ( $args as $key => $arg ) {
+ $search = '{' . $key . '}';
+ $replace = $arg;
+
+ if ( is_array( $arg ) ) {
+ $search = "'{" . $key . "}'";
+
+ foreach( $arg as $k => $v ) {
+ $arg[$k] = "'$v'";
+ }
+
+ $replace = implode( ',', $arg );
+ }
+
+ $script = str_replace( $search, $replace, $script );
+ }
+ }
+
+ return $script;
+ }
+
+ public function get_selector_attribute( $type, $selector = '' ) {
+ $element = $this->get_selector_raw( $type, $selector );
+
+ if ( substr( $element, 0, 1 ) === '.' ) {
+ $element = substr( $element, 1 );
+ } elseif( substr( $element, 0, 1 ) === '#' ) {
+ $element = substr( $element, 1 );
+ }
+
+ return $element;
+ }
+
+ public function get_selector_raw( $type, $selector = '' ) {
+ $element = $this->{$type . "_selector"};
+
+ if ( empty( $element ) ) {
+ $element = "#ts_{$type}";
+ }
+
+ if ( ! empty( $selector ) ) {
+ $element = $selector;
+ }
+
+ return $element;
+ }
+
+ public function get_selector( $type, $selector = '' ) {
+ $element = $this->get_selector_raw( $type, $selector );
+ $attribute = $this->get_selector_attribute( $type, $selector );
+ $is_class = false;
+
+ if ( substr( $element, 0, 1 ) === '.' ) {
+ $is_class = true;
+ }
+
+ return $is_class ? 'class="' . esc_attr( $attribute ) . '"' : 'id="' . esc_attr( $attribute ) . '"';
+ }
+
+ public function get_product_sticker_code( $replace = true, $args = array() ) {
+ if ( $replace ) {
+ $selector = $this->product_sticker_selector;
+
+ $args = wp_parse_args( $args, array(
+ 'element' => empty( $selector ) ? '#ts_product_sticker' : $selector,
+ 'border_color' => $this->product_sticker_border_color,
+ 'star_color' => $this->product_sticker_star_color,
+ 'star_size' => $this->product_sticker_star_size,
+ ) );
+ }
+
+ return $this->get_script( 'product_sticker', $replace, $args );
+ }
+
+ public function get_review_sticker_code( $replace = true, $args = array() ) {
+ if ( $replace ) {
+ $args = wp_parse_args( $args, array(
+ 'element' => '#ts_review_sticker',
+ 'bg_color' => $this->review_sticker_bg_color,
+ 'font' => $this->review_sticker_font,
+ 'number' => $this->review_sticker_number,
+ 'better_than' => $this->review_sticker_better_than
+ ) );
+ }
+
+ return $this->get_script( 'review_sticker', $replace, $args );
+ }
+
+ public function get_product_widget_code( $replace = true, $args = array() ) {
+ if ( $replace ) {
+ $selector = $this->product_widget_selector;
+
+ $args = wp_parse_args( $args, array(
+ 'element' => empty( $selector ) ? '#ts_product_widget' : $selector,
+ 'star_color' => $this->product_widget_star_color,
+ 'star_size' => $this->product_widget_star_size,
+ 'font_size' => $this->product_widget_font_size,
+ ) );
+ }
+
+ return $this->get_script( 'product_widget', $replace, $args );
+ }
+
+ public function get_trustbadge_code( $replace = true, $args = array() ) {
+ if ( $replace ) {
+ $args = wp_parse_args( $args, array(
+ 'offset' => $this->trustbadge_y,
+ 'variant' => $this->trustbadge_variant === 'standard' ? 'reviews' : 'default',
+ 'disable' => $this->is_trustbadge_enabled() ? 'false' : 'true',
+ ) );
+ }
+
+ return $this->get_script( 'trustbadge', $replace, $args );
+ }
+
+ public function get_rich_snippets_code( $replace = true, $args = array() ) {
+ if ( $replace ) {
+ $rating = $this->get_average_rating();
+
+ $args = apply_filters( 'woocommerce_trusted_shops_rich_snippets_args', wp_parse_args( $args, array(
+ 'average' => $rating['avg'],
+ 'count' => $rating['count'],
+ 'maximum' => $rating['max'],
+ 'rating' => $rating,
+ 'name' => get_bloginfo( 'name' ),
+ ) ), $this );
+ }
+
+ return $this->get_script( 'rich_snippets', $replace, $args );
+ }
+
+ public function get_supported_languages() {
+ return array_keys( $this->get_locale_mapping() );
+ }
+
+ protected function get_locale_mapping() {
+ $supported = array(
+ 'de' => 'de_DE',
+ 'en' => 'en_GB',
+ 'fr' => 'fr_FR',
+ );
+
+ return $supported;
+ }
+
+ public function get_language() {
+ $locale = $this->get_locale();
+
+ return substr( $locale, 0, 2 );
+ }
+
+ public function get_locale() {
+ $supported = $this->get_locale_mapping();
+
+ $locale = 'en_GB';
+ $base = substr( function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(), 0, 2 );
+
+ if ( isset( $supported[ $base ] ) )
+ $locale = $supported[ $base ];
+
+ return $locale;
+ }
+}
+
+?>
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-ts-dependencies.php b/packages/woocommerce-trusted-shops/includes/class-wc-ts-dependencies.php
new file mode 100644
index 000000000..be76ad889
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-ts-dependencies.php
@@ -0,0 +1,139 @@
+ array( 'version' => '2.4', 'version_prefix' => 'woocommerce', 'name' => 'WooCommerce' ),
+ );
+
+ public static function instance() {
+ if ( is_null( self::$_instance ) ) {
+ self::$_instance = new self();
+ }
+ return self::$_instance;
+ }
+
+ /**
+ * Cloning is forbidden.
+ *
+ * @since 1.0
+ */
+ public function __clone() {
+ _doing_it_wrong( __FUNCTION__, _x( 'Cheatin’ huh?', 'trusted-shops', 'woocommerce-germanized' ), '1.0' );
+ }
+
+ /**
+ * Unserializing instances of this class is forbidden.
+ *
+ * @since 1.0
+ */
+ public function __wakeup() {
+ _doing_it_wrong( __FUNCTION__, _x( 'Cheatin’ huh?', 'trusted-shops', 'woocommerce-germanized' ), '1.0' );
+ }
+
+ public function __construct() {
+
+ $this->plugins = (array) get_option( 'active_plugins', array() );
+
+ if ( is_multisite() ) {
+ $this->plugins = array_merge( $this->plugins, get_site_option( 'active_sitewide_plugins', array() ) );
+ }
+
+ foreach ( $this->plugins_required as $plugin => $data ) {
+
+ if ( ! $this->is_plugin_activated( $plugin ) || $this->is_plugin_outdated( $plugin ) ) {
+ add_action( 'admin_notices', array( $this, 'dependencies_notice' ) );
+ $this->loadable = false;
+ }
+ }
+ }
+
+ public function get_plugin_version( $plugin_slug ) {
+ return get_option( $plugin_slug . '_version' );
+ }
+
+ public function is_plugin_outdated( $plugin ) {
+ $required = ( isset( $this->plugins_required[ $plugin ] ) ? $this->plugins_required[ $plugin ] : false );
+
+ if ( ! $required ) {
+ return false;
+ }
+
+ if ( version_compare( $this->get_plugin_version( $required[ 'version_prefix' ] ), $required[ 'version' ], "<" ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public function is_plugin_activated( $plugin ) {
+
+ if ( strpos( $plugin, '.php' ) === false ) {
+ $plugin = trailingslashit( $plugin ) . $plugin . '.php';
+ }
+
+ return in_array( $plugin, $this->plugins ) || array_key_exists( $plugin, $this->plugins );
+ }
+
+ /**
+ * This method removes accuration from $ver2 if this version is more accurate than $main_ver
+ */
+ public function compare_versions( $main_ver, $ver2, $operator ) {
+
+ $expl_main_ver = explode( '.', $main_ver );
+ $expl_ver2 = explode( '.', $ver2 );
+
+ // Check if ver2 string is more accurate than main_ver
+ if ( sizeof( $expl_main_ver ) == 2 && sizeof( $expl_ver2 ) > 2 ) {
+ $new_ver_2 = array_slice( $expl_ver2, 0, 2 );
+ $ver2 = implode( '.', $new_ver_2 );
+ }
+
+ return version_compare( $main_ver, $ver2, $operator );
+ }
+
+ /**
+ * Checks if WooCommerce is activated
+ *
+ * @return boolean true if WooCommerce is activated
+ */
+ public function is_woocommerce_activated() {
+ return $this->is_plugin_activated( 'woocommerce/woocommerce.php' );
+ }
+
+ public function is_wpml_activated() {
+ return ( $this->is_plugin_activated( 'sitepress-multilingual-cms/sitepress.php' ) && $this->is_plugin_activated( 'woocommerce-multilingual/wpml-woocommerce.php' ) );
+ }
+
+ public function woocommerce_version_supports_crud() {
+ return ( $this->compare_versions( $this->get_plugin_version( 'woocommerce' ), '2.7', '>=' ) );
+ }
+
+ public function is_loadable() {
+ return $this->loadable;
+ }
+
+ public function dependencies_notice() {
+ global $dependencies;
+ $dependencies = $this;
+
+ include_once( 'admin/views/html-notice-dependencies.php' );
+ }
+
+}
+
+WC_TS_Dependencies::instance();
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-ts-install.php b/packages/woocommerce-trusted-shops/includes/class-wc-ts-install.php
new file mode 100644
index 000000000..2e87b1c48
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-ts-install.php
@@ -0,0 +1,168 @@
+ 'updates/woocommerce-ts-update-3.0.0.php',
+ '4.0.6' => 'updates/woocommerce-ts-update-4.0.6.php'
+ );
+
+ /**
+ * Hook in tabs.
+ */
+ public function __construct() {
+ if ( ! Package::is_integration() ) {
+ add_action( 'admin_init', array( __CLASS__, 'check_version' ), 10 );
+ }
+
+ add_action( 'admin_init', array( __CLASS__, 'install_actions' ), 5 );
+ }
+
+ /**
+ * Install actions such as installing pages when a button is clicked.
+ */
+ public static function install_actions() {
+ if ( ! empty( $_GET['do_update_woocommerce_ts'] ) ) {
+ self::update();
+
+ // Update complete
+ delete_option( '_wc_ts_needs_update' );
+ }
+ }
+
+ /**
+ * check_version function.
+ *
+ * @access public
+ * @return void
+ */
+ public static function check_version() {
+ if ( ! defined( 'IFRAME_REQUEST' ) && ( get_option( 'woocommerce_trusted_shops_version' ) != WC_trusted_shops()->version || get_option( 'woocommerce_trusted_shops_db_version' ) != WC_trusted_shops()->version ) ) {
+ self::install();
+ do_action( 'woocommerce_trusted_shops_updated' );
+ }
+ }
+
+ public static function install_integration() {
+ self::create_cron_jobs();
+ self::update_versions();
+ }
+
+ protected static function update_versions() {
+ // Queue upgrades
+ $current_version = get_option( 'woocommerce_trusted_shops_version', null );
+ $current_db_version = get_option( 'woocommerce_trusted_shops_db_version', null );
+
+ if ( ! is_null( $current_db_version ) && version_compare( $current_db_version, max( array_keys( self::$db_updates ) ), '<' ) ) {
+ // Update
+ update_option( '_wc_ts_needs_update', 1 );
+ } else {
+ self::update_db_version();
+ }
+
+ self::update_ts_version();
+
+ do_action( 'woocommerce_trusted_shops_installed' );
+ }
+
+ /**
+ * Install TS
+ */
+ public static function install() {
+ // Load Translation for default options
+ $locale = apply_filters( 'plugin_locale', get_locale(), 'woocommerce-germanized' );
+ $mofile = WC_trusted_shops()->plugin_path() . '/i18n/languages/woocommerce-trusted-shops.mo';
+
+ if ( file_exists( WC_trusted_shops()->plugin_path() . '/i18n/languages/woocommerce-trusted-shops-' . $locale . '.mo' ) ) {
+ $mofile = WC_trusted_shops()->plugin_path() . '/i18n/languages/woocommerce-trusted-shops-' . $locale . '.mo';
+ }
+
+ load_textdomain( 'woocommerce-trusted-shops', $mofile );
+
+ self::create_options();
+ self::create_cron_jobs();
+ self::update_versions();
+
+ // Flush rules after install
+ flush_rewrite_rules();
+ }
+
+ /**
+ * Update WC version to current
+ */
+ private static function update_ts_version() {
+ delete_option( 'woocommerce_trusted_shops_version' );
+ add_option( 'woocommerce_trusted_shops_version', WC_trusted_shops()->version );
+ }
+
+ /**
+ * Update DB version to current
+ */
+ private static function update_db_version( $version = null ) {
+ delete_option( 'woocommerce_trusted_shops_db_version' );
+ add_option( 'woocommerce_trusted_shops_db_version', is_null( $version ) ? WC_trusted_shops()->version : $version );
+ }
+
+ /**
+ * Handle updates
+ */
+ public static function update() {
+ $current_db_version = get_option( 'woocommerce_trusted_shops_db_version' );
+
+ foreach ( self::$db_updates as $version => $updater ) {
+ if ( version_compare( $current_db_version, $version, '<' ) ) {
+ include( $updater );
+ self::update_db_version( $version );
+ }
+ }
+
+ self::update_db_version();
+ }
+
+ /**
+ * Create cron jobs (clear them first)
+ */
+ private static function create_cron_jobs() {
+ // Cron jobs
+ wp_clear_scheduled_hook( 'woocommerce_gzd_trusted_shops_reviews' );
+ wp_schedule_event( time(), 'twicedaily', 'woocommerce_gzd_trusted_shops_reviews' );
+ }
+
+ /**
+ * Default options
+ *
+ * Sets up the default options used on the settings page
+ *
+ * @access public
+ */
+ private static function create_options() {
+ // Include settings so that we can run through defaults
+ $options = apply_filters( 'woocommerce_gzd_installation_default_settings', array() );
+
+ foreach ( $options as $value ) {
+
+ if ( isset( $value['default'] ) && isset( $value['id'] ) ) {
+ $autoload = isset( $value['autoload'] ) ? (bool) $value['autoload'] : true;
+ add_option( $value['id'], $value['default'], '', ( $autoload ? 'yes' : 'no' ) );
+ }
+ }
+ }
+}
+
+endif;
+
+return new WC_TS_Install();
diff --git a/packages/woocommerce-trusted-shops/includes/class-wc-ts-settings-handler.php b/packages/woocommerce-trusted-shops/includes/class-wc-ts-settings-handler.php
new file mode 100644
index 000000000..12632ac5a
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/class-wc-ts-settings-handler.php
@@ -0,0 +1,77 @@
+id = 'trusted-shops';
+ $this->label = _x( 'Trusted Shops', 'trusted-shops', 'woocommerce-germanized' );
+
+ parent::__construct();
+ }
+
+ /**
+ * Get settings array
+ *
+ * @return array
+ */
+ public function get_settings() {
+ $admin = WC_trusted_shops()->trusted_shops->get_dependency( 'admin' );
+ $settings = $admin->get_settings();
+
+ return $settings;
+ }
+
+ public function get_settings_for_section_core( $section_id ) {
+ $admin = WC_trusted_shops()->trusted_shops->get_dependency( 'admin' );
+ $settings = $admin->get_settings();
+
+ return $settings;
+ }
+
+ public function output() {
+ global $current_section;
+
+ $settings = $this->get_settings_for_section_core( '' );
+ $sidebar = $this->get_sidebar();
+
+ include_once( WC_trusted_shops()->plugin_path() . '/includes/admin/views/html-settings-section.php' );
+ }
+
+ public function get_sidebar() {
+ $admin = WC_trusted_shops()->trusted_shops->get_dependency( 'admin' );
+ $sidebar = $admin->get_sidebar();
+
+ return $sidebar;
+ }
+
+ /**
+ * Save settings
+ */
+ public function save() {
+ $settings = $this->get_settings_for_section_core( '' );
+
+ do_action( 'woocommerce_ts_before_save', $settings );
+ WC_Admin_Settings::save_fields( $settings );
+ do_action( 'woocommerce_ts_after_save', $settings );
+ }
+}
+
+endif;
+
+?>
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/compatibility/class-wc-ts-compatibility-wpml-string-translation.php b/packages/woocommerce-trusted-shops/includes/compatibility/class-wc-ts-compatibility-wpml-string-translation.php
new file mode 100644
index 000000000..f6827cdb7
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/compatibility/class-wc-ts-compatibility-wpml-string-translation.php
@@ -0,0 +1,343 @@
+ defined( 'WPML_ST_VERSION' ) ? WPML_ST_VERSION : '1.0',
+ )
+ );
+ }
+
+ public function is_activated() {
+ global $sitepress;
+
+ return defined( 'WPML_ST_VERSION' ) && isset( $sitepress) ? true : false;
+ }
+
+ public function load() {
+ if ( is_admin() ) {
+ $this->admin_translate_options();
+ }
+ }
+
+ public function get_languages() {
+ global $sitepress;
+ $codes = array();
+
+ if ( isset( $sitepress ) && is_callable( array( $sitepress, 'get_ls_languages' ) ) ) {
+ $languages = $sitepress->get_ls_languages();
+
+ if ( ! empty( $languages ) ) {
+ $codes = array_keys( $languages );
+ }
+ }
+
+ return $codes;
+ }
+
+ public function get_language_name( $language = '' ) {
+ global $sitepress;
+
+ if ( empty( $language ) ) {
+ $language = $this->get_current_language();
+ }
+
+ return $sitepress->get_display_language_name( $language );
+ }
+
+ public function get_current_language() {
+ global $sitepress;
+
+ return $sitepress->get_current_language();
+ }
+
+ public function get_default_language() {
+ global $sitepress;
+ $default = '';
+
+ if ( isset( $sitepress ) && is_callable( array( $sitepress, 'get_default_language' ) ) ) {
+ $default = $sitepress->get_default_language();
+ }
+
+ return $default;
+ }
+
+ /**
+ * Register strings that are translatable within the settings panel. Strings will be loaded in the admin user's language
+ * within the settings screen only.
+ *
+ * @return array
+ */
+ public function get_translatable_options() {
+ return apply_filters( 'woocommerce_gzd_wpml_translatable_options', array() );
+ }
+
+ /**
+ * By default WPML allow only certain strings to be translated within the administration area (e.g. blog title).
+ * If you want some translatable strings to be loaded globally within the admin panel use the filter accordingly.
+ *
+ * @return array
+ */
+ public function get_translatable_admin_options() {
+ return apply_filters( 'woocommerce_gzd_wpml_translatable_admin_options', array() );
+ }
+
+ public function admin_translate_options() {
+ $this->set_filters();
+ }
+
+ public function set_filters() {
+ $admin_strings = $this->get_translatable_admin_options();
+
+ if ( $this->enable_option_filters() ) {
+
+ foreach( $this->get_translatable_options() as $option => $args ) {
+ add_filter( 'option_' . $option, array( $this, 'translate_option_filter' ), 10, 2 );
+ add_filter( 'pre_update_option_' . $option, array( $this, 'pre_update_translation_filter' ), 10, 3 );
+
+ wc_ts_remove_class_filter( 'option_' . $option, 'WPML_Admin_Texts', 'icl_st_translate_admin_string', 10 );
+ }
+
+ } elseif( ! empty( $admin_strings ) ) {
+
+ foreach( $admin_strings as $option => $args ) {
+ add_filter( 'option_' . $option, array( $this, 'translate_option_filter' ), 10, 2 );
+ wc_ts_remove_class_filter( 'option_' . $option, 'WPML_Admin_Texts', 'icl_st_translate_admin_string', 10 );
+ }
+ }
+ }
+
+ protected function enable_option_filters() {
+ $enable = false;
+
+ if ( isset( $_GET['tab'] ) && ( 'trusted-shops' === $_GET['tab'] ) ) {
+ $enable = true;
+ }
+
+ return apply_filters( 'woocommerce_gzd_enable_wpml_string_translation_settings_filters', $enable );
+ }
+
+ public function get_string_language( $string_id, $option = '' ) {
+ if ( $string = $this->get_string_by_id( $string_id ) ) {
+ return $string->language;
+ }
+
+ global $WPML_String_Translation;
+
+ return $WPML_String_Translation->get_current_string_language( $option );
+ }
+
+ public function get_string_by_id( $string_id ) {
+ global $wpdb;
+
+ $string = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}icl_strings WHERE id=%d LIMIT 1", $string_id ) );
+
+ if ( $string ) {
+ return $string[0];
+ }
+
+ return false;
+ }
+
+ public function get_string_id( $option, $context = '' ) {
+ $context = empty( $context ) ? 'admin_texts_' . $option : $context;
+
+ return icl_st_is_registered_string( $context, $option );
+ }
+
+ public function get_string_value( $string_id ) {
+ if ( $string = $this->get_string_by_id( $string_id ) ) {
+ return $string->value;
+ }
+
+ return false;
+ }
+
+ public function update_string_value( $string_id, $value ) {
+ global $wpdb;
+
+ $value = maybe_serialize( $value );
+
+ $wpdb->update( "{$wpdb->prefix}icl_strings", array( 'value' => $value ), array( 'id' => $string_id ) );
+ }
+
+ public function delete_string_translation( $string_id, $language ) {
+ global $wpdb;
+
+ $wpdb->query( $wpdb->prepare("DELETE FROM {$wpdb->prefix}icl_string_translations WHERE string_id=%d AND language=%s", $string_id, $language ) );
+ }
+
+ public function update_string_translation( $option, $language, $value, $status = '' ) {
+ if ( empty( $status ) ) {
+ $status = ICL_TM_COMPLETE;
+ }
+
+ if ( $string_id = $this->get_string_id( $option ) ) {
+ icl_add_string_translation( $string_id, $language, $value, $status );
+ }
+
+ icl_update_string_translation( $option, $language, $value, $status );
+
+ // Make sure that the string is stored within the WPML translatable option names
+ $option_names = get_option( '_icl_admin_option_names', array() );
+ $option_names[ $option ] = 1;
+
+ update_option( '_icl_admin_option_names', $option_names );
+ }
+
+ public function get_string_translation( $string_id, $language, $status = '' ) {
+ $translations = icl_get_string_translations_by_id( $string_id );
+ $status = empty( $status ) ? ICL_TM_COMPLETE : $status;
+
+ if ( isset( $translations[ $language ] ) && $translations[ $language ]['status'] == $status ) {
+ return $translations[ $language ]['value'];
+ }
+
+ return false;
+ }
+
+ public function get_translated_string( $option, $language, $context = '' ) {
+ $value = null;
+
+ if ( $string_id = $this->get_string_id( $option, $context ) ) {
+ $value = $this->get_string_translation( $string_id, $language );
+ }
+
+ return $value;
+ }
+
+ public function register_string( $option, $value, $context = '' ) {
+ $context = empty( $context ) ? 'admin_texts_' . $option : $context;
+
+ return icl_register_string( $context, $option, $value );
+ }
+
+ public function pre_update_translation_filter( $new_value, $old_value, $option ) {
+ $string_options = $this->get_translatable_options();
+
+ if ( is_array( $new_value ) || is_array( $string_options[ $option ] ) ) {
+
+ if ( ! is_array( $new_value ) ) {
+ $new_value = array();
+ }
+
+ $args = $string_options[ $option ];
+
+ foreach( $new_value as $id => $options ) {
+ foreach( $options as $key => $value ) {
+ if ( in_array( $key, $args ) ) {
+ $old_value_internal = isset( $old_value[ $id ][ $key ] ) ? $old_value[ $id ][ $key ] : '';
+ $new_value[ $id ][ $key ] = $this->pre_update_translation( $value, $old_value_internal, "[{$option}][{$id}]{$key}", "admin_texts_{$option}" );
+ }
+ }
+ }
+
+ return $new_value;
+ } else {
+ return $this->pre_update_translation( $new_value, $old_value, $option );
+ }
+ }
+
+ protected function pre_update_translation( $new_value, $old_value, $option, $context = '' ) {
+ $org_string_id = $this->get_string_id( $option, $context );
+ $strings_language = $this->get_string_language( $org_string_id, $option );
+ $return_value = $old_value;
+
+ if ( $strings_language === $this->get_current_language() || 'all' === $this->get_current_language() ) {
+
+ $current_string_value = $this->get_string_value( $org_string_id );
+
+ // Update original string value
+ if ( $org_string_id && ( $new_value !== $old_value || $current_string_value !== $new_value ) ) {
+ $this->update_string_value( $org_string_id, $new_value );
+ }
+
+ $return_value = $new_value;
+
+ } else {
+ $update_translation = true;
+
+ if ( $org_string_id ) {
+ $org_string = $this->get_string_value( $org_string_id );
+
+ /**
+ * Remove translation if it equals original string
+ * Use woocommerce_gzd_wpml_remove_translation_empty_equal filter to disallow string deletion which results in "real" option translations
+ */
+ if ( ( $org_string === $new_value || empty( $new_value ) ) && apply_filters( 'woocommerce_gzd_wpml_remove_translation_empty_equal', true, $option, $new_value, $old_value ) ) {
+ $this->delete_string_translation( $org_string_id, $this->get_current_language() );
+
+ $return_value = $old_value;
+ $update_translation = false;
+ }
+ }
+
+ if ( $update_translation ) {
+ $this->update_string_translation( $option, $this->get_current_language(), $new_value );
+ }
+ }
+
+ // Allow WPML to delete the cache
+ do_action( "update_option_{$option}", $old_value, $return_value, $option );
+
+ return $return_value;
+ }
+
+ public function translate_option_filter( $org_value, $option ) {
+ $string_options = $this->get_translatable_options();
+
+ if ( is_array( $org_value ) || is_array( $string_options[ $option ] ) ) {
+
+ if ( ! is_array( $org_value ) ) {
+ $org_value = array();
+ }
+
+ $args = $string_options[ $option ];
+
+ foreach( $org_value as $id => $options ) {
+ foreach( $options as $key => $value ) {
+ if ( in_array( $key, $args ) ) {
+ $org_value[ $id ][ $key ] = $this->translate_option( $value, "[{$option}][{$id}]{$key}", "admin_texts_{$option}" );
+ }
+ }
+ }
+
+ return $org_value;
+ } else {
+ return $this->translate_option( $org_value, $option );
+ }
+ }
+
+ protected function translate_option( $org_value, $option, $context = '' ) {
+ $string_id = $this->get_string_id( $option, $context );
+ $language = $this->get_current_language();
+
+ if ( ! $string_id ) {
+ $string_id = $this->register_string( $option, $org_value, $context );
+ }
+
+ $translation = $this->get_string_translation( $string_id, $language );
+
+ if ( false !== $translation ) {
+ $org_value = $translation;
+ }
+
+ return $org_value;
+ }
+
+ public function pre_update_translate_checkboxes( $new_value, $old_value, $option ) {
+ return $new_value;
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/emails/class-wc-ts-email-customer-trusted-shops.php b/packages/woocommerce-trusted-shops/includes/emails/class-wc-ts-email-customer-trusted-shops.php
new file mode 100644
index 000000000..1a8e1c4f7
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/emails/class-wc-ts-email-customer-trusted-shops.php
@@ -0,0 +1,159 @@
+id = 'customer_trusted_shops';
+ $this->title = _x( 'Trusted Shops Review Reminder', 'trusted-shops', 'woocommerce-germanized' );
+ $this->description = _x( 'This E-Mail is being sent to a customer to remind him about the possibility to leave a review at Trusted Shops.', 'trusted-shops', 'woocommerce-germanized' );
+
+ $this->template_html = 'emails/customer-trusted-shops.php';
+ $this->template_plain = 'emails/plain/customer-trusted-shops.php';
+ $this->helper = function_exists( 'wc_gzd_get_email_helper' ) ? wc_gzd_get_email_helper( $this ) : false;
+
+ // Triggers for this email
+ add_action( 'woocommerce_germanized_trusted_shops_review_notification', array( $this, 'trigger' ) );
+
+ $this->placeholders = array(
+ '{site_title}' => $this->get_blogname(),
+ '{order_number}' => '',
+ '{order_date}' => '',
+ );
+
+ // Call parent constuctor
+ parent::__construct();
+
+ $this->customer_email = true;
+ }
+
+ /**
+ * Get email subject.
+ *
+ * @since 3.1.0
+ * @return string
+ */
+ public function get_default_subject() {
+ return _x( 'Please rate your {site_title} order from {order_date}', 'trusted-shops', 'woocommerce-germanized' );
+ }
+
+ /**
+ * Get email heading.
+ *
+ * @since 3.1.0
+ * @return string
+ */
+ public function get_default_heading() {
+ return _x( 'Please rate your Order', 'trusted-shops', 'woocommerce-germanized' );
+ }
+
+ /**
+ * trigger function.
+ *
+ * @access public
+ * @return void
+ */
+ public function trigger( $order_id ) {
+ if ( $this->helper ) {
+ $this->helper->setup_locale();
+ } else {
+ $this->setup_locale();
+ }
+
+ if ( $order_id ) {
+ $this->object = wc_get_order( $order_id );
+ $this->recipient = wc_ts_get_crud_data( $this->object, 'billing_email' );
+
+ $this->placeholders['{order_date}'] = wc_gzd_get_order_date( $this->object, wc_date_format() );
+ $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();
+ $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 function.
+ *
+ * @access public
+ * @return string
+ */
+ public function get_content_html() {
+ return wc_get_template_html( $this->template_html, array(
+ '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(
+ 'order' => $this->object,
+ 'email_heading' => $this->get_heading(),
+ 'additional_content' => $this->get_additional_content(),
+ 'sent_to_admin' => false,
+ 'plain_text' => false,
+ 'email' => $this
+ )
+ );
+ }
+}
+
+endif;
+
+return new WC_TS_Email_Customer_Trusted_Shops();
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/updates/woocommerce-ts-update-3.0.0.php b/packages/woocommerce-trusted-shops/includes/updates/woocommerce-ts-update-3.0.0.php
new file mode 100644
index 000000000..ec1101ab0
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/updates/woocommerce-ts-update-3.0.0.php
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/updates/woocommerce-ts-update-4.0.6.php b/packages/woocommerce-trusted-shops/includes/updates/woocommerce-ts-update-4.0.6.php
new file mode 100644
index 000000000..9dee857c5
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/updates/woocommerce-ts-update-4.0.6.php
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/includes/wc-ts-core-functions.php b/packages/woocommerce-trusted-shops/includes/wc-ts-core-functions.php
new file mode 100644
index 000000000..c097b5d64
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/wc-ts-core-functions.php
@@ -0,0 +1,319 @@
+get_wc_product();
+ }
+
+ $value = null;
+
+ $getter = substr( $key, 0, 3 ) === "get" ? $key : "get_$key";
+ $key = substr( $key, 0, 3 ) === "get" ? substr( $key, 3 ) : $key;
+
+ if ( 'id' === $key && is_callable( array( $object, 'is_type' ) ) && $object->is_type( 'variation' ) && ! wc_ts_woocommerce_supports_crud() ) {
+ $key = 'variation_id';
+ } elseif ( 'parent' === $key && is_callable( array( $object, 'is_type' ) ) && $object->is_type( 'variation' ) && ! wc_ts_woocommerce_supports_crud() ) {
+ // Set getter to parent so that it is not being used for pre 2.7
+ $key = 'id';
+ $getter = 'parent';
+ }
+
+ $getter_mapping = array(
+ 'parent' => 'get_parent_id',
+ 'completed_date' => 'get_date_completed',
+ 'order_date' => 'get_date_created',
+ 'product_type' => 'get_type',
+ 'order_type' => 'get_type',
+ );
+
+ if ( array_key_exists( $key, $getter_mapping ) ) {
+ $getter = $getter_mapping[ $key ];
+ }
+
+ if ( is_callable( array( $object, $getter ) ) ) {
+ $reflection = new ReflectionMethod( $object, $getter );
+ if ( $reflection->isPublic() ) {
+ $value = $object->{$getter}();
+ }
+ } elseif ( wc_ts_woocommerce_supports_crud() ) {
+ // Prefix meta if suppress_suffix is not set
+ if ( substr( $key, 0, 1 ) !== '_' && ! $suppress_suffix )
+ $key = '_' . $key;
+
+ $value = $object->get_meta( $key );
+ } else {
+ $key = substr( $key, 0, 1 ) === "_" ? substr( $key, 1 ) : $key;
+ $value = $object->{$key};
+ }
+
+ return $value;
+ }
+}
+
+if ( ! function_exists( 'wc_ts_woocommerce_supports_crud' ) ) {
+
+ function wc_ts_woocommerce_supports_crud() {
+ return WC_TS_Dependencies::instance()->woocommerce_version_supports_crud();
+ }
+}
+
+if ( ! function_exists( 'wc_ts_help_tip' ) ) {
+
+ function wc_ts_help_tip( $tip, $allow_html = false ) {
+ if ( function_exists( 'wc_help_tip' ) )
+ return wc_help_tip( $tip, $allow_html );
+
+ return '[?] ';
+ }
+}
+
+if ( ! function_exists( 'wc_ts_set_crud_data' ) ) {
+
+ function wc_ts_set_crud_data( $object, $key, $value ) {
+ if ( wc_ts_woocommerce_supports_crud() ) {
+
+ $key_unprefixed = substr( $key, 0, 1 ) === '_' ? substr( $key, 1 ) : $key;
+ $setter = substr( $key_unprefixed, 0, 3 ) === "set" ? $key : "set_{$key_unprefixed}";
+
+ if ( is_callable( array( $object, $setter ) ) ) {
+ $reflection = new ReflectionMethod( $object, $setter );
+ if ( $reflection->isPublic() ) {
+ $object->{$setter}( $value );
+ }
+ } else {
+ $object = wc_ts_set_crud_meta_data( $object, $key, $value );
+ }
+ } else {
+ $object = wc_ts_set_crud_meta_data( $object, $key, $value );
+ }
+ return $object;
+ }
+}
+
+if ( ! function_exists( 'wc_ts_set_crud_meta_data' ) ) {
+ function wc_ts_set_crud_meta_data( $object, $key, $value ) {
+
+ if ( wc_ts_woocommerce_supports_crud() ) {
+ $object->update_meta_data( $key, $value );
+ } else {
+ update_post_meta( wc_ts_get_crud_data( $object, 'id' ), $key, $value );
+ }
+ return $object;
+ }
+}
+
+if ( ! function_exists( 'wc_ts_get_order_date' ) ) {
+
+ function wc_ts_get_order_date( $order, $format = '' ) {
+ $date_formatted = '';
+
+ if ( function_exists( 'wc_format_datetime' ) ) {
+ return wc_format_datetime( $order->get_date_created(), $format );
+ } else {
+ $date = $order->order_date;
+ }
+
+ if ( empty( $format ) ) {
+ $format = get_option( 'date_format' );
+ }
+
+ if ( ! empty( $date ) ) {
+ $date_formatted = date_i18n( $format, strtotime( $date ) );
+ }
+
+ return $date_formatted;
+ }
+}
+
+if ( ! function_exists( 'wc_ts_get_order_currency' ) ) {
+
+ function wc_ts_get_order_currency( $order ) {
+ if ( wc_ts_woocommerce_supports_crud() ) {
+ return $order->get_currency();
+ }
+
+ return $order->get_order_currency();
+ }
+}
+
+if ( ! function_exists( 'wc_ts_get_order_language' ) ) {
+
+ function wc_ts_get_order_language( $order ) {
+ $order_id = is_numeric( $order ) ? $order : wc_ts_get_crud_data( $order, 'id' );
+
+ return get_post_meta( $order_id, 'wpml_language', true );
+ }
+}
+
+if ( ! function_exists( 'wc_ts_switch_language' ) ) {
+
+ function wc_ts_switch_language( $lang, $set_default = false ) {
+ global $sitepress;
+ global $wc_ts_original_lang;
+
+ if ( $set_default ) {
+ $wc_ts_original_lang = $lang;
+ }
+
+ if ( isset( $sitepress ) && is_callable( array( $sitepress, 'get_current_language' ) ) && is_callable( array( $sitepress, 'switch_lang' ) ) ) {
+ if ( $sitepress->get_current_language() != $lang ) {
+
+ $sitepress->switch_lang( $lang, true );
+
+ // Somehow WPML doesn't automatically change the locale
+ if ( is_callable( array( $sitepress, 'reset_locale_utils_cache' ) ) ) {
+ $sitepress->reset_locale_utils_cache();
+ }
+
+ if ( function_exists( 'switch_to_locale' ) ) {
+ switch_to_locale( get_locale() );
+
+ // Filter on plugin_locale so load_plugin_textdomain loads the correct locale.
+ add_filter( 'plugin_locale', 'get_locale' );
+
+ // Init WC locale.
+ WC()->load_plugin_textdomain();
+ WC_trusted_shops()->load_plugin_textdomain();
+ WC_trusted_shops()->trusted_shops->refresh();
+ }
+
+ do_action( 'woocommerce_gzd_trusted_shops_switched_language', $lang, $wc_ts_original_lang );
+ }
+ }
+
+ do_action( 'woocommerce_gzd_trusted_shops_switch_language', $lang, $wc_ts_original_lang );
+ }
+}
+
+if ( ! function_exists( 'wc_ts_restore_language' ) ) {
+
+ function wc_ts_restore_language() {
+ global $wc_ts_original_lang;
+
+ if ( isset( $wc_ts_original_lang ) && ! empty( $wc_ts_original_lang ) ) {
+ wc_ts_switch_language( $wc_ts_original_lang );
+ }
+ }
+}
+
+if ( ! function_exists( 'wc_ts_remove_class_filter' ) ) {
+ /**
+ * Remove Class Filter Without Access to Class Object
+ *
+ * In order to use the core WordPress remove_filter() on a filter added with the callback
+ * to a class, you either have to have access to that class object, or it has to be a call
+ * to a static method. This method allows you to remove filters with a callback to a class
+ * you don't have access to.
+ *
+ * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
+ * Updated 2-27-2017 to use internal WordPress removal for 4.7+ (to prevent PHP warnings output)
+ *
+ * @param string $tag Filter to remove
+ * @param string $class_name Class name for the filter's callback
+ * @param string $method_name Method name for the filter's callback
+ * @param int $priority Priority of the filter (default 10)
+ *
+ * @return bool Whether the function is removed.
+ */
+ function wc_ts_remove_class_filter( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
+ global $wp_filter;
+
+ // Check that filter actually exists first
+ if ( ! isset( $wp_filter[ $tag ] ) ) return FALSE;
+
+ /**
+ * If filter config is an object, means we're using WordPress 4.7+ and the config is no longer
+ * a simple array, rather it is an object that implements the ArrayAccess interface.
+ *
+ * To be backwards compatible, we set $callbacks equal to the correct array as a reference (so $wp_filter is updated)
+ *
+ * @see https://make.wordpress.org/core/2016/09/08/wp_hook-next-generation-actions-and-filters/
+ */
+ if ( is_object( $wp_filter[ $tag ] ) && isset( $wp_filter[ $tag ]->callbacks ) ) {
+ // Create $fob object from filter tag, to use below
+ $fob = $wp_filter[ $tag ];
+ $callbacks = &$wp_filter[ $tag ]->callbacks;
+ } else {
+ $callbacks = &$wp_filter[ $tag ];
+ }
+
+ // Exit if there aren't any callbacks for specified priority
+ if ( ! isset( $callbacks[ $priority ] ) || empty( $callbacks[ $priority ] ) ) return FALSE;
+
+ // Loop through each filter for the specified priority, looking for our class & method
+ foreach( (array) $callbacks[ $priority ] as $filter_id => $filter ) {
+
+ // Filter should always be an array - array( $this, 'method' ), if not goto next
+ if ( ! isset( $filter[ 'function' ] ) || ! is_array( $filter[ 'function' ] ) ) continue;
+
+ // If first value in array is not an object, it can't be a class
+ if ( ! is_object( $filter[ 'function' ][ 0 ] ) ) continue;
+
+ // Method doesn't match the one we're looking for, goto next
+ if ( $filter[ 'function' ][ 1 ] !== $method_name ) continue;
+
+ // Method matched, now let's check the Class
+ if ( get_class( $filter[ 'function' ][ 0 ] ) === $class_name ) {
+
+ // WordPress 4.7+ use core remove_filter() since we found the class object
+ if( isset( $fob ) ){
+ // Handles removing filter, reseting callback priority keys mid-iteration, etc.
+ $fob->remove_filter( $tag, $filter['function'], $priority );
+
+ } else {
+ // Use legacy removal process (pre 4.7)
+ unset( $callbacks[ $priority ][ $filter_id ] );
+ // and if it was the only filter in that priority, unset that priority
+ if ( empty( $callbacks[ $priority ] ) ) {
+ unset( $callbacks[ $priority ] );
+ }
+ // and if the only filter for that tag, set the tag to an empty array
+ if ( empty( $callbacks ) ) {
+ $callbacks = array();
+ }
+ // Remove this filter from merged_filters, which specifies if filters have been sorted
+ unset( $GLOBALS['merged_filters'][ $tag ] );
+ }
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+}
+
+if ( ! function_exists( 'wc_ts_remove_class_action' ) ) {
+ /**
+ * Remove Class Action Without Access to Class Object
+ *
+ * In order to use the core WordPress remove_action() on an action added with the callback
+ * to a class, you either have to have access to that class object, or it has to be a call
+ * to a static method. This method allows you to remove actions with a callback to a class
+ * you don't have access to.
+ *
+ * Works with WordPress 1.2+ (4.7+ support added 9-19-2016)
+ *
+ * @param string $tag Action to remove
+ * @param string $class_name Class name for the action's callback
+ * @param string $method_name Method name for the action's callback
+ * @param int $priority Priority of the action (default 10)
+ *
+ * @return bool Whether the function is removed.
+ */
+ function wc_ts_remove_class_action( $tag, $class_name = '', $method_name = '', $priority = 10 ) {
+ wc_ts_remove_class_filter( $tag, $class_name, $method_name, $priority );
+ }
+}
diff --git a/packages/woocommerce-trusted-shops/includes/widgets/class-wc-trusted-shops-widget-review-sticker.php b/packages/woocommerce-trusted-shops/includes/widgets/class-wc-trusted-shops-widget-review-sticker.php
new file mode 100644
index 000000000..3098a9796
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/includes/widgets/class-wc-trusted-shops-widget-review-sticker.php
@@ -0,0 +1,63 @@
+widget_cssclass = 'woocommerce woocommerce_gzd widget_trusted_shops_review_sticker';
+ $this->widget_description = _x( "Show your TS shop review sticker.", 'trusted-shops', 'woocommerce-germanized' );
+ $this->widget_id = 'woocommerce_gzd_widget_trusted_shops_shop_review_sticker';
+ $this->widget_name = _x( 'Trusted Shops Shop Review Sticker', 'trusted-shops', 'woocommerce-germanized' );
+ $this->settings = array(
+ 'title' => array(
+ 'type' => 'text',
+ 'std' => _x( 'Trusted Shops Reviews', 'trusted-shops', 'woocommerce-germanized' ),
+ 'label' => _x( 'Title', 'trusted-shops', 'woocommerce-germanized' ),
+ ),
+ );
+ parent::__construct();
+ }
+
+ /**
+ * widget function.
+ *
+ * @see WP_Widget
+ * @access public
+ * @param array $args
+ * @param array $instance
+ * @return void
+ */
+ public function widget( $args, $instance ) {
+ extract( $args );
+
+ $element = "#ts_review_sticker_{$this->number}";
+
+ $title = apply_filters('widget_title', empty( $instance['title'] ) ? _x( 'Trusted Shops Reviews', 'trusted-shops', 'woocommerce-germanized' ) : $instance['title'], $instance, $this->id_base );
+
+ echo $before_widget;
+
+ if ( $title )
+ echo $before_title . $title . $after_title;
+
+ echo '';
+
+ echo do_shortcode( '[trusted_shops_review_sticker element="' . $element . '"]' );
+
+ echo '
';
+
+ echo $after_widget;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/license.txt b/packages/woocommerce-trusted-shops/license.txt
new file mode 100644
index 000000000..e4e3f0c5f
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/license.txt
@@ -0,0 +1,699 @@
+WooCommerce Trusted Shops
+
+Copyright 2011 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-trusted-shops/readme.txt b/packages/woocommerce-trusted-shops/readme.txt
new file mode 100644
index 000000000..5ead8872e
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/readme.txt
@@ -0,0 +1,156 @@
+=== Trustbadge Reviews for WooCommerce ===
+Contributors: vendidero, trustbadge
+Tags: advanced reviews, badge, best reviews, business ratings, business reviews, confirm email reviews, google rating, google shopping, product ratings, product reviews, rate products, rating summary, Rating Widget, ratings, reputation, review widget, review, reviews easy, reviews, rich snippets, seal, seo, star rating, stars, trust, trustbadge, trusted reviews, trusted shops, ts, user rating, user reviews, woocommerce trusted shops, woocommerce
+Donate link: http://www.trustbadge.com
+Requires at least: 4.9
+Tested up to: 5.8
+WC requires at least: 3.4
+WC tested up to: 5.6
+Stable tag: 4.0.12
+Requires PHP: 5.6
+License: GPLv3
+License URI: http://www.gnu.org/licenses/gpl-3.0.html
+
+Show that your customers love you with reviews in your online store and boost your business with the free Trustbadge Reviews Plugin for WooCommerce.
+== Description ==
+
+The eTrusted Reviews Plugin for WooCommerce enables you to automatically collect seller reviews and product reviews from your happy customers. You can integrate the Trustbadge® within your store with one click and it will be displayed on every site of your store showing your overall score, your star ratings and three reviews when expanded. It also links to your profile on eTrusted where all reviews from your happy customers are displayed to improve your SEO. The Trustbadge® works with any web browser and on all devices. Your customers do not require any additional software.
+* Collect seller and product reviews automatically
+* Show your trustworthiness on your website by authentic reviews
+* Increase your conversion rates
+
+= Why customer reviews are highly important =
+
+„Reviews are the joint single biggest conversion factor along with price.“ — Jeff Bezos, CEO and founder of Amazon
+„In a networked world trust is the most important currency.“
— Eric Schmidt, Chairman of Google
+„According to Google, 70% of Americans now say they look at product reviews before making a purchase.“
+
+= eTrusted: Get Better Reviews =
+
+Trust, security and transparency are vital for success in the world of e-commerce. 90 % of decisions to buy online are based on these factors. People only buy from those they trust and if consumers can't trust an online store, they will go elsewhere. To succeed in internet sales and to boost their conversion rate, online traders need to show their potential customers how trustworthy they are.
+
+And this is precisely where eTrusted can help you. We support Woocommerce users with services that have been successfully applied for by 20,000+ of online retailers: Our customer review system allows you to collect, show and manage your customers’ feedback effortlessly!
+
+Get your account now!
+
+= Seller reviews + product reviews = Increase your conversion rate and get more sales =
+
+Seller reviews and product reviews complement each other in order to give your potential customers a positive attitude towards a purchase in your store. The Trustbadge offers both. Seller reviews show that your customers trust you and reinforce their feeling that they are at the right place to buy.
+
+Great product reviews convince them to buy a specific product. If you provide both kinds of reviews to your customers, a purchase is more likely to happen which means more sales for your store.
+
+Some customers experienced an uplift of up to 12.5 % in conversions, simply by using the Trustbadge. No matter to which country or on which device you sell - the Trustbadge is well prepared. It comes with 7 possible languages and is mobile-optimized - this means all common devices are supported.
+
+= Getting Started =
+
+With your WooCommerce store you can start in less than 66 seconds
+
+1. Sign up for a Trustbadge Reviews account .
+2. Install the Trustbadge Reviews Toolkit in your WooCommerce store
+3. Customize your Trustbadge in the backend of WooCommerce
+
+As promised, it´s easy like that. Welcome on board!
+
+== Installation ==
+
+= Minimal Requirements =
+
+* WordPress 4.9 or newer
+* WooCommerce 3.0
+* PHP Version 5.6 or newer
+
+= Shortcodes =
+
+`[trusted_shops_rich_snippets]`
+Use this shortcode to embed your updated Trusted Shops Rich Snippets within your post/page or product description.
+
+`[trusted_shops_reviews]`
+Embed your Trusted Shops Review Image within your post/page or product description.
+
+`[trusted_shops_badge]`
+Embed your Trusted Shops Badge within your content.
+
+== Frequently Asked Questions ==
+
+= How can I become a Trusted Shops Member? =
+More information regarding your Trusted Shops Membership can be found [here](https://business.trustedshops.co.uk/).
+
+== Screenshots ==
+
+1. WooCommerce Trusted Shops Settings
+
+== Changelog ==
+= 4.0.12 =
+* Fix: Custom selectors defaults
+* Improvement: Updating default settings when switching to standard mode
+
+= 4.0.11 =
+* Improvement: CSV export format
+* Improvement: WP 5.8, Woo 5.5 support
+
+= 4.0.8 =
+* Improvement: Use Woo payment method title
+* Improvement: Better function exists checking
+
+= 4.0.4 =
+* Improvement: Indicate Woo 4.0 + WP 5.4 support
+* Improvement: Removed legacy support
+* Fix: Email schedule
+* Fix: Force review widget template override
+
+= 4.0.0 =
+* Improvement: Legacy code removals
+* Improvement: PHP 5.6
+* Improvement: WC 3.8 support
+
+= 3.0.3 =
+* Improvement: Indicate correct module name in templates for suppport purposes
+
+= 3.0.2 =
+* Improvement: Do only replace Woo reviews when product sticker is enabled
+
+= 3.0.1 =
+* Fix: Autoloading class
+
+= 3.0.0 =
+* Feature - Better WPML support
+* Feature - Better setting control
+* Improvement - Code refactoring
+
+= 2.2.0 =
+* Feature - WC 3.2 Compatibility
+* Fix - Review stars not showing on product page
+
+= 2.0.1 =
+* Feature - WC 2.7 beta compatibility
+* Feature - Rich snippets image
+* Feature - Brand/MPN attribute
+* Fix - Template globals filter removal
+* Fix - Settings show/hide
+
+= 2.0.3 =
+* Fix - Better Review Widget Update
+
+= 2.0.2 =
+* Fix - Star Size Option
+
+= 2.0.1 =
+* Fix - Review Collector Export
+
+= 2.0.0 =
+* Feature - Product Reviews
+* Feature - Product Review Sticker/Stars
+* Feature - GTIN for Product Reviews
+* Feature - In Standard Mode no need to insert JS Trustbadge Code
+* Feature - Expert Mode for code adjustments
+
+= 1.1.0 =
+* Feature - Trusted Shops Review Collector
+
+= 1.0.0 =
+* Feature - Trusted Shops Integration
+
+== Upgrade Notice ==
+
+= 1.0.0 =
+no upgrade - just install :)
diff --git a/packages/woocommerce-trusted-shops/src/Package.php b/packages/woocommerce-trusted-shops/src/Package.php
new file mode 100644
index 000000000..a93e94472
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/src/Package.php
@@ -0,0 +1,112 @@
+
+
+ version, '3.1', '>=' ) ? true : false;
+ }
+
+ public static function is_integration() {
+ return class_exists( 'WooCommerce_Germanized' ) ? true : false;
+ }
+
+ private static function includes() {
+ include_once self::get_path() . '/includes/class-wc-trusted-shops-core.php';
+ }
+
+ public static function init_hooks() {}
+
+ /**
+ * 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';
+ }
+
+ private static function define_constant( $name, $value ) {
+ if ( ! defined( $name ) ) {
+ define( $name, $value );
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/emails/cancel-review-reminder.php b/packages/woocommerce-trusted-shops/templates/emails/cancel-review-reminder.php
new file mode 100644
index 000000000..4288eb84e
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/emails/cancel-review-reminder.php
@@ -0,0 +1,14 @@
+
+
+
+
' . _x( 'cancel review reminder', 'trusted-shops', 'woocommerce-germanized' ) . '' ); ?>
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/emails/customer-trusted-shops.php b/packages/woocommerce-trusted-shops/templates/emails/customer-trusted-shops.php
new file mode 100644
index 000000000..caf4e8657
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/emails/customer-trusted-shops.php
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/emails/plain/customer-trusted-shops.php b/packages/woocommerce-trusted-shops/templates/emails/plain/customer-trusted-shops.php
new file mode 100644
index 000000000..86c503317
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/emails/plain/customer-trusted-shops.php
@@ -0,0 +1,32 @@
+trusted_shops->get_new_review_link( wc_ts_get_crud_data( $order, 'billing_email' ), wc_ts_get_crud_data( $order, 'id' ) ) . "\n\n";
+
+echo "\n\n----------------------------------------\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 apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) );
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/product-sticker-tpl.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-sticker-tpl.php
new file mode 100644
index 000000000..05439d0c4
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-sticker-tpl.php
@@ -0,0 +1,34 @@
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/product-sticker.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-sticker.php
new file mode 100644
index 000000000..2976e83ca
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-sticker.php
@@ -0,0 +1,19 @@
+get_product_skus( $post->ID );
+?>
+
+get_selector( 'product_sticker' ); ?>>
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/product-widget-tpl.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-widget-tpl.php
new file mode 100644
index 000000000..b17de61c1
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-widget-tpl.php
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/product-widget.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-widget.php
new file mode 100644
index 000000000..8fc743123
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/product-widget.php
@@ -0,0 +1,23 @@
+ID );
+$plugin = isset( $plugin ) ? $plugin : WC_trusted_shops()->trusted_shops;
+$skus = $plugin->get_product_skus( $post->ID );
+?>
+
+get_selector( 'product_widget' ); ?>>
+
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/review-sticker-tpl.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/review-sticker-tpl.php
new file mode 100644
index 000000000..68e3c5d12
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/review-sticker-tpl.php
@@ -0,0 +1,33 @@
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/review-sticker.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/review-sticker.php
new file mode 100644
index 000000000..c0942a1fe
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/review-sticker.php
@@ -0,0 +1,17 @@
+
+
+get_selector( 'review_sticker', $element ); ?>>
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/rich-snippets-tpl.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/rich-snippets-tpl.php
new file mode 100644
index 000000000..0683f15ad
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/rich-snippets-tpl.php
@@ -0,0 +1,22 @@
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/rich-snippets.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/rich-snippets.php
new file mode 100644
index 000000000..af70d3cb7
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/rich-snippets.php
@@ -0,0 +1,16 @@
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/thankyou.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/thankyou.php
new file mode 100644
index 000000000..e4333e5a3
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/thankyou.php
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ get_total(); ?>
+
+ get_payment_method_title(); ?>
+
+ is_product_reviews_enabled() ) : ?>
+ get_items() as $item_id => $item ) :
+
+ $org_product = $order->get_product_from_item( $item );
+ $parent_product = $org_product;
+
+ if ( ! $org_product )
+ continue;
+
+ // Currently not supporting reviews for variations
+ if ( $org_product->is_type( 'variation' ) )
+ $parent_product = wc_get_product( wc_ts_get_crud_data( $org_product, 'parent' ) );
+
+ ?>
+
+
+ get_product_image( $org_product ); ?>
+
+ get_sku() ? $parent_product->get_sku() : wc_ts_get_crud_data( $parent_product, 'id' ) ); ?>
+ get_product_gtin( $org_product ), $org_product ); ?>
+ get_product_brand( $parent_product ), $parent_product ); ?>
+ get_product_mpn( $org_product ), $org_product ); ?>
+
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/trustbadge-tpl.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/trustbadge-tpl.php
new file mode 100644
index 000000000..0245517e2
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/trustbadge-tpl.php
@@ -0,0 +1,29 @@
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/templates/trusted-shops/trustbadge.php b/packages/woocommerce-trusted-shops/templates/trusted-shops/trustbadge.php
new file mode 100644
index 000000000..55a1df55f
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/templates/trusted-shops/trustbadge.php
@@ -0,0 +1,15 @@
+
+
+
\ No newline at end of file
diff --git a/packages/woocommerce-trusted-shops/woocommerce-trusted-shops.php b/packages/woocommerce-trusted-shops/woocommerce-trusted-shops.php
new file mode 100644
index 000000000..17204b71b
--- /dev/null
+++ b/packages/woocommerce-trusted-shops/woocommerce-trusted-shops.php
@@ -0,0 +1,73 @@
+
+
+
+ composer install',
+ '' . esc_html( str_replace( ABSPATH, '', __DIR__ ) ) . '
'
+ );
+ ?>
+
+
+