From f3a825aff02ea79101dae011decccabc9fb4243b Mon Sep 17 00:00:00 2001 From: sal4sup Date: Fri, 17 Apr 2026 15:01:29 +0300 Subject: [PATCH 1/4] Add auto-create label on Print/Download for single order page --- assets/css/admin-order-single.css | 12 -- assets/js/admin-order-single.js | 178 ++++++++++++++++++++---------- src/Order/Base.php | 57 +++++++++- src/Order/Single.php | 9 +- 4 files changed, 183 insertions(+), 73 deletions(-) diff --git a/assets/css/admin-order-single.css b/assets/css/admin-order-single.css index 1cab6e88..8029151b 100644 --- a/assets/css/admin-order-single.css +++ b/assets/css/admin-order-single.css @@ -24,18 +24,6 @@ width:50px; } -#shipment-postnl-label-form.generated .button-save-form { - display:none; -} - -#shipment-postnl-label-form .button-download-label { - display:none; -} - -#shipment-postnl-label-form.generated .button-download-label { - display:inline-block; -} - #shipment-postnl-label-form .delete-label { display:none; } diff --git a/assets/js/admin-order-single.js b/assets/js/admin-order-single.js index bcda71a2..1e824cd7 100644 --- a/assets/js/admin-order-single.js +++ b/assets/js/admin-order-single.js @@ -8,38 +8,26 @@ init: function() { jQuery( '#shipment-postnl-label-form' ) .on( 'click', 'a.delete-label', this.delete_label ) - .on( 'click', 'button.button-save-form', this.save_form ) + .on( 'click', 'button.button-print-label', this.print_label ) + .on( 'click', 'button.button-download-label', this.download_label ) .on( 'click', 'button.button-activate-return', this.activate_return ) .on( 'click', 'button.button-send-smart-return', this.send_smart_return ); }, - // When a user enters a new tracking item - save_form: function () { - var error_cont = label_form.find( '#shipment-postnl-error-text' ); - - label_form.block( { - message: null, - overlayCSS: { - background: '#fff', - opacity: 0.6 - } - } ); - - error_cont.empty(); - + build_save_data: function() { var data = { action: 'postnl_order_save_form', order_id: woocommerce_admin_meta_boxes.post_id, }; - + for ( var i = 0; i < postnl_admin_order_obj.fields.length; i++ ) { - var field_name = postnl_admin_order_obj.fields[ i ]; - var maybe_field = label_form.find( '#' + postnl_admin_order_obj.fields[ i ] ); + var field_name = postnl_admin_order_obj.fields[ i ]; + var maybe_field = label_form.find( '#' + field_name ); if ( ! maybe_field.is( ':input' ) ) { continue; } - + if ( 'checkbox' === maybe_field.prop( 'type' ) ) { data[ field_name ] = maybe_field.is( ':checked' ) ? maybe_field.val() : ''; } else { @@ -47,57 +35,133 @@ } } - $.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response ) { - label_form.unblock(); + return data; + }, - if ( true === response.success ) { - label_form.addClass( 'generated' ); + handle_label_created: function( response ) { + label_form.addClass( 'generated' ); - for (let field in response.data.backend ) { - jQuery( '#postnl_' + field ).prop( 'disabled', true ); + for ( let field in response.data.backend ) { + jQuery( '#postnl_' + field ).prop( 'disabled', true ); - if ( 'create_return_label' === field && 'yes' === response.data.backend[ field ] ) { - label_form.addClass( 'has-return' ); - } + if ( 'create_return_label' === field && 'yes' === response.data.backend[ field ] ) { + label_form.addClass( 'has-return' ); + } - if ( 'letterbox' === field && 'yes' === response.data.backend[ field ] ) { - label_form.addClass( 'has-letterbox' ); - } + if ( 'letterbox' === field && 'yes' === response.data.backend[ field ] ) { + label_form.addClass( 'has-letterbox' ); + } + } + + if ( response.data.tracking_note ) { + $( '#woocommerce-order-notes' ).block( { + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 } + } ); + + var note_data = { + action: 'woocommerce_add_order_note', + post_id: woocommerce_admin_meta_boxes.post_id, + note_type: response.data.note_type, + note: response.data.tracking_note, + security: woocommerce_admin_meta_boxes.add_order_note_nonce + }; + + $.post( woocommerce_admin_meta_boxes.ajax_url, note_data, function( response_note ) { + $( 'ul.order_notes' ).prepend( response_note ); + $( '#woocommerce-order-notes' ).unblock(); + $( '#add_order_note' ).val( '' ); + } ); + } + }, - if( response.data.tracking_note ) { + print_label: function() { + // Open window synchronously before AJAX to avoid popup blocker. + var printWindow = window.open( '', '_blank' ); + var printUrl = $( this ).data( 'print-url' ); + var error_cont = label_form.find( '#shipment-postnl-error-text' ); + error_cont.empty(); - $( '#woocommerce-order-notes' ).block({ - message: null, - overlayCSS: { - background: '#fff', - opacity: 0.6 - } - }); + if ( label_form.hasClass( 'generated' ) ) { + printWindow.location.href = printUrl; + printWindow.onload = function() { + printWindow.print(); + }; + return false; + } - var data = { - action: 'woocommerce_add_order_note', - post_id: woocommerce_admin_meta_boxes.post_id, - note_type: response.data.note_type, - note: response.data.tracking_note, - security: woocommerce_admin_meta_boxes.add_order_note_nonce - }; + label_form.block( { + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + } ); - $.post( woocommerce_admin_meta_boxes.ajax_url, data, function( response_note ) { - $( 'ul.order_notes' ).prepend( response_note ); - $( '#woocommerce-order-notes' ).unblock(); - $( '#add_order_note' ).val( '' ); - }); - } + $.post( woocommerce_admin_meta_boxes.ajax_url, postnl_order_single.build_save_data(), function( response ) { + label_form.unblock(); + + if ( true === response.success ) { + postnl_order_single.handle_label_created( response ); + printWindow.location.href = printUrl; + printWindow.onload = function() { + printWindow.print(); + }; } else { + printWindow.close(); var error_text = response.data.hasOwnProperty( 'message' ) ? response.data.message : 'Unknown error!'; error_cont.html( error_text ); } - }); + } ); return false; }, + download_label: function() { + var downloadUrl = $( this ).data( 'download-url' ); + var error_cont = label_form.find( '#shipment-postnl-error-text' ); + error_cont.empty(); + + if ( label_form.hasClass( 'generated' ) ) { + postnl_order_single.trigger_download( downloadUrl ); + return false; + } + + label_form.block( { + message: null, + overlayCSS: { + background: '#fff', + opacity: 0.6 + } + } ); + + $.post( woocommerce_admin_meta_boxes.ajax_url, postnl_order_single.build_save_data(), function( response ) { + label_form.unblock(); + + if ( true === response.success ) { + postnl_order_single.handle_label_created( response ); + postnl_order_single.trigger_download( downloadUrl ); + } else { + var error_text = response.data.hasOwnProperty( 'message' ) ? response.data.message : 'Unknown error!'; + error_cont.html( error_text ); + } + } ); + + return false; + }, + + trigger_download: function( url ) { + var a = document.createElement( 'a' ); + a.href = url; + a.download = ''; + document.body.appendChild( a ); + a.click(); + document.body.removeChild( a ); + }, + // Delete a tracking item delete_label: function() { var error_cont = label_form.find( '#shipment-postnl-error-text' ); @@ -110,7 +174,7 @@ } ); error_cont.empty(); - + var data = { action: 'postnl_order_delete_data', order_id: woocommerce_admin_meta_boxes.post_id, @@ -123,7 +187,7 @@ if ( ! maybe_field.is( ':input' ) ) { continue; } - + if ( 'checkbox' === maybe_field.prop( 'type' ) ) { data[ field_name ] = maybe_field.is( ':checked' ) ? maybe_field.val() : ''; } else { @@ -248,14 +312,14 @@ error_cont.html( response.data.message ); }else{ console.log(response); - error_cont.html( 'Email sent successfully' ); + error_cont.html( 'Email sent successfully' ); } label_form.unblock(); }); } } - + postnl_order_single.init(); diff --git a/src/Order/Base.php b/src/Order/Base.php index c4f04616..a752c472 100644 --- a/src/Order/Base.php +++ b/src/Order/Base.php @@ -1302,6 +1302,26 @@ public function get_download_label_url( $order_id, $label_type = 'label' ) { return $download_url; } + /** + * Generate inline (print) label url + * + * @param int $order_id ID of the order post. + * @param String $label_type Type of the label. Possible options : 'label', 'return-label'. + * + * @return String. + */ + public function get_print_label_url( $order_id, $label_type = 'label' ) { + return add_query_arg( + array( + 'postnl_label_order_id' => $order_id, + 'label_type' => $label_type, + 'postnl_label_nonce' => wp_create_nonce( 'postnl_download_label_nonce' ), + 'view' => 'inline', + ), + home_url() + ); + } + /** * Get label file. * @@ -1337,7 +1357,13 @@ public function get_label_file() { return; } - $this->download_label( $saved_data['labels'][ $label_type ]['filepath'] ); + $view_inline = ! empty( $_GET['view'] ) && 'inline' === sanitize_text_field( wp_unslash( $_GET['view'] ) ); + + if ( $view_inline ) { + $this->print_label( $saved_data['labels'][ $label_type ]['filepath'] ); + } else { + $this->download_label( $saved_data['labels'][ $label_type ]['filepath'] ); + } } /** @@ -1371,6 +1397,35 @@ protected function download_label( $file_path ) { } } + /** + * Serves the label file inline for browser print dialog. + * + * @param string $file_path File path to the label. + * + * @return boolean|void + */ + protected function print_label( $file_path ) { + if ( ! empty( $file_path ) && is_string( $file_path ) && file_exists( $file_path ) ) { + if ( ob_get_contents() ) { + ob_clean(); + } + + $filename = basename( $file_path ); + + header( 'Content-Type: application/pdf' ); + header( 'Content-Disposition: inline; filename="' . $filename . '"' ); + header( 'Expires: 0' ); + header( 'Cache-Control: must-revalidate' ); + header( 'Pragma: public' ); + header( 'Content-Length: ' . filesize( $file_path ) ); + + readfile( $file_path ); + exit; + } else { + return false; + } + } + /** * Get tracking note for the order. * diff --git a/src/Order/Single.php b/src/Order/Single.php index e4feb083..a354158b 100644 --- a/src/Order/Single.php +++ b/src/Order/Single.php @@ -419,9 +419,12 @@ public function meta_box_html( $post_or_order_object ) { generate_pickup_points_html( $pickup_info ); ?>
- - + +
From 5b11a0489df348439a1fc8ec41d064eca1a480de Mon Sep 17 00:00:00 2001 From: sal4sup Date: Fri, 17 Apr 2026 16:00:13 +0300 Subject: [PATCH 2/4] Add auto-trigger print/download for bulk orders --- assets/css/admin-order-bulk.css | 10 ++ assets/js/admin-order-bulk.js | 179 +++++++++++++++++++++++++------- src/Order/Bulk.php | 123 +++++++++++++++++++++- 3 files changed, 273 insertions(+), 39 deletions(-) diff --git a/assets/css/admin-order-bulk.css b/assets/css/admin-order-bulk.css index 73f1b3b7..1435c25c 100644 --- a/assets/css/admin-order-bulk.css +++ b/assets/css/admin-order-bulk.css @@ -40,6 +40,16 @@ .postnl-action-download-label:after { background: url(../images/post-download-label.png) no-repeat center center; } +.postnl-bulk-spinner { + margin-bottom: 8px; + font-style: italic; + color: #666; +} +.postnl-bulk-error { + margin-bottom: 8px; + color: #cc0000; + font-weight: 700; +} .postnl_eligible_auto_letterbox { font-size: 18px; } diff --git a/assets/js/admin-order-bulk.js b/assets/js/admin-order-bulk.js index 2a5b83a4..6c9b991d 100644 --- a/assets/js/admin-order-bulk.js +++ b/assets/js/admin-order-bulk.js @@ -1,10 +1,12 @@ ( function( $ ) { + var current_bulk_action = 'create-label'; + var postnl_order_bulk = { // init Class init: function() { var posts_filter = jQuery( '#posts-filter, #wc-orders-filter' ); - + posts_filter .on( 'change', '#bulk-action-selector-top', this.toggle_create_label_modal ); posts_filter @@ -13,47 +15,150 @@ .on( 'click', '.button.action', this.disable_submit_button ); }, + get_selected_order_ids: function( post_form ) { + var order_ids = []; + post_form.find( 'input[name="post[]"]:checked' ).each( function() { + order_ids.push( $( this ).val() ); + } ); + if ( ! order_ids.length ) { + post_form.find( 'input[name="id[]"]:checked' ).each( function() { + order_ids.push( $( this ).val() ); + } ); + } + return order_ids; + }, + + trigger_download: function( url ) { + var a = document.createElement( 'a' ); + a.href = url; + a.download = ''; + document.body.appendChild( a ); + a.click(); + document.body.removeChild( a ); + }, + toggle_create_label_modal: function( evt ){ evt.preventDefault(); - var value = jQuery( this ).val(); - var title = jQuery(':selected', this ).text(); - var post_form = jQuery( this ).parents('#posts-filter, #wc-orders-filter'); + var value = jQuery( this ).val(); + var title = jQuery( ':selected', this ).text(); + var post_form = jQuery( this ).parents( '#posts-filter, #wc-orders-filter' ); + + var is_create = 'postnl-create-label' === value; + var is_print = 'postnl-print-labels' === value; + var is_download = 'postnl-download-labels' === value; + + if ( is_create || is_print || is_download ) { + if ( is_print ) { + current_bulk_action = 'print'; + } else if ( is_download ) { + current_bulk_action = 'download'; + } else { + current_bulk_action = 'create-label'; + } - if( 'postnl-create-label' == value ){ - // Show thickbox modal. tb_show( "", '/?TB_inline=true&width=385&height=200&inlineId=postnl-create-label-modal' ); var tb_window = jQuery( '#TB_window' ); - tb_window.find( '#TB_ajaxWindowTitle' ).text(title); // Set title - tb_window.find( '#postnl-create-label-proceed' ).on( 'click', function( evt ) { + tb_window.find( '#TB_ajaxWindowTitle' ).text( title ); + + tb_window.find( '#postnl-create-label-proceed' ).off( 'click.postnl' ).on( 'click.postnl', function( evt ) { evt.preventDefault(); - post_form.append( '' ); - tb_window.find( '.shipment-postnl-row-container' ).each( function() { - var field_clone = jQuery( this ).clone(); - post_form.find( '#postnl-field-container' ).append( field_clone ); + if ( 'create-label' === current_bulk_action ) { + post_form.append( '' ); + tb_window.find( '.shipment-postnl-row-container' ).each( function() { + var field_clone = jQuery( this ).clone(); + post_form.find( '#postnl-field-container' ).append( field_clone ); + } ); + var selected_value = tb_window.find( '#postnl_position_printing_labels' ).val(); + post_form.find( '#postnl-field-container' ).append( '' ); + jQuery( this ).prop( 'disabled', true ); + post_form.submit(); + return; + } + + // Print or Download via AJAX. + var proceed_btn = jQuery( this ); + var action = current_bulk_action; + + // Open print window synchronously before AJAX to avoid popup blocker. + var printWindow = null; + if ( 'print' === action ) { + printWindow = window.open( '', '_blank' ); + } + + var order_ids = postnl_order_bulk.get_selected_order_ids( post_form ); + + if ( ! order_ids.length ) { + tb_window.find( '#postnl-bulk-error' ).text( 'No orders selected.' ).show(); + if ( printWindow ) { + printWindow.close(); + } + return; + } + + proceed_btn.prop( 'disabled', true ); + tb_window.find( '#postnl-bulk-error' ).hide().text( '' ); + tb_window.find( '#postnl-bulk-spinner' ).show(); + + var ajax_data = { + action: 'postnl_bulk_labels_ajax', + security: postnl_admin_bulk_obj.security, + order_ids: order_ids, + bulk_action: action, + }; + + var pos_val = tb_window.find( '#postnl_position_printing_labels' ).val(); + if ( pos_val ) { + ajax_data['postnl_position_printing_labels'] = pos_val; + } + + var num_val = tb_window.find( '#postnl_num_labels' ).val(); + if ( num_val ) { + ajax_data['postnl_num_labels'] = num_val; + } + + $.post( postnl_admin_bulk_obj.ajax_url, ajax_data, function( response ) { + tb_window.find( '#postnl-bulk-spinner' ).hide(); + proceed_btn.prop( 'disabled', false ); + + if ( ! response.success ) { + var error_text = response.data && response.data.message ? response.data.message : 'Unknown error.'; + tb_window.find( '#postnl-bulk-error' ).text( error_text ).show(); + if ( printWindow ) { + printWindow.close(); + } + return; + } + + tb_remove(); + + if ( 'print' === action && printWindow ) { + printWindow.location.href = response.data.url; + printWindow.onload = function() { + printWindow.print(); + }; + } else { + postnl_order_bulk.trigger_download( response.data.url ); + } } ); - var selected_value = tb_window.find('#postnl_position_printing_labels').val(); - post_form.find('#postnl-field-container').append(''); - jQuery( this ).prop( 'disabled', true ); - post_form.submit(); } ); } - if( 'postnl-change-shipping-options' == value ){ + if ( 'postnl-change-shipping-options' === value ) { tb_show( "", '/?TB_inline=true&width=385&height=230&inlineId=postnl-change-shipping-options-modal' ); var tb_window = jQuery( '#TB_window' ); - tb_window.find( '#TB_ajaxWindowTitle' ).text(title); + tb_window.find( '#TB_ajaxWindowTitle' ).text( title ); tb_window.find( '#postnl_shipping_zone' ).on( 'change', function( event ) { var current = this.value; - $('.conditional').each(function() { - if ( $(this).hasClass(current) ) { - $(this).show(); + $( '.conditional' ).each( function() { + if ( $( this ).hasClass( current ) ) { + $( this ).show(); } else { - $(this).hide() + $( this ).hide(); } - }); - }); + } ); + } ); tb_window.find( '#postnl-change-shipping-options-proceed' ).on( 'click', function( evt ) { evt.preventDefault(); post_form.append( '' ); @@ -61,33 +166,37 @@ var field_clone = jQuery( this ).clone(); post_form.find( '#postnl-field-container' ).append( field_clone ); } ); - post_form.find('#postnl-field-container').append(''); - post_form.find('#postnl-field-container').append(''); - post_form.find('#postnl-field-container').append(''); - post_form.find('#postnl-field-container').append(''); - post_form.find('#postnl-field-container').append(''); - post_form.find('#postnl-field-container').append(''); + post_form.find( '#postnl-field-container' ).append( '' ); + post_form.find( '#postnl-field-container' ).append( '' ); + post_form.find( '#postnl-field-container' ).append( '' ); + post_form.find( '#postnl-field-container' ).append( '' ); + post_form.find( '#postnl-field-container' ).append( '' ); + post_form.find( '#postnl-field-container' ).append( '' ); jQuery( this ).prop( 'disabled', true ); post_form.submit(); } ); } - }, disable_submit_button: function( evt ) { - var bulkactions = jQuery( this ).closest( '.bulkactions' ); + var bulkactions = jQuery( this ).closest( '.bulkactions' ); var bulkdropdown = bulkactions.find( 'select[name=action]' ); + var val = bulkdropdown.val(); - if ( 'postnl-create-label' === bulkdropdown.val() ) { + if ( 'postnl-create-label' === val ) { jQuery( this ).prop( 'disabled', true ); jQuery( '#posts-filter' ).submit(); } - if ( 'postnl-change-shipping-options' === bulkdropdown.val() ) { + if ( 'postnl-change-shipping-options' === val ) { jQuery( this ).prop( 'disabled', true ); jQuery( '#posts-filter' ).submit(); } + if ( 'postnl-print-labels' === val || 'postnl-download-labels' === val ) { + evt.preventDefault(); + tb_show( "", '/?TB_inline=true&width=385&height=200&inlineId=postnl-create-label-modal' ); + } }, }; postnl_order_bulk.init(); -} )( jQuery ); \ No newline at end of file +} )( jQuery ); diff --git a/src/Order/Bulk.php b/src/Order/Bulk.php index 945d896a..124c3182 100644 --- a/src/Order/Bulk.php +++ b/src/Order/Bulk.php @@ -75,6 +75,8 @@ public function init_hooks() { // Add 'Create Label' action button. add_action( 'wp_ajax_postnl_create_label', array( $this, 'postnl_create_label_ajax' ) ); add_filter( 'woocommerce_admin_order_actions', array( $this, 'add_create_label_actions_button' ), 10, 2 ); + + add_action( 'wp_ajax_postnl_bulk_labels_ajax', array( $this, 'postnl_bulk_labels_ajax' ) ); } /** @@ -85,8 +87,10 @@ public function init_hooks() { * @return array */ public function add_order_bulk_actions( $bulk_actions ) { - $base_country = Utils::get_base_country(); - $bulk_actions['postnl-create-label'] = esc_html__( 'PostNL Create Label', 'postnl-for-woocommerce' ); + $base_country = Utils::get_base_country(); + $bulk_actions['postnl-create-label'] = esc_html__( 'PostNL Create Label', 'postnl-for-woocommerce' ); + $bulk_actions['postnl-print-labels'] = esc_html__( 'PostNL Print Labels', 'postnl-for-woocommerce' ); + $bulk_actions['postnl-download-labels'] = esc_html__( 'PostNL Download Labels', 'postnl-for-woocommerce' ); if ( $base_country != 'BE' ) { $bulk_actions['postnl-change-shipping-options'] = esc_html__( 'PostNL Change Shipping Options', 'postnl-for-woocommerce' ); } @@ -316,7 +320,13 @@ public function get_bulk_file() { return; } - $this->download_label( $label_path ); + $view_inline = ! empty( $_GET['view'] ) && 'inline' === sanitize_text_field( wp_unslash( $_GET['view'] ) ); + + if ( $view_inline ) { + $this->print_label( $label_path ); + } else { + $this->download_label( $label_path ); + } } /** @@ -347,6 +357,15 @@ public function enqueue_bulk_assets() { true ); + wp_localize_script( + 'postnl-admin-order-bulk', + 'postnl_admin_bulk_obj', + array( + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'security' => wp_create_nonce( 'postnl_bulk_labels_ajax_nonce' ), + ) + ); + wp_enqueue_script( 'postnl-admin-shipment-track-trace', POSTNL_WC_PLUGIN_DIR_URL . '/assets/js/admin-shipment-track-trace.js', @@ -478,7 +497,103 @@ protected function create_label_fields() { * Collection of fields in create label bulk action. */ public function modal_create_label() { - $this->create_modal_content_wrapper( 'postnl-create-label', $this->create_label_fields() ); + $screen = get_current_screen(); + + $is_legacy_order = ! empty( $screen->id ) && 'edit-shop_order' === $screen->id && ! empty( $screen->base ) && 'edit' === $screen->base; + $is_hpos_order = ! empty( $screen->id ) && 'woocommerce_page_wc-orders' === $screen->id && ( empty( $_GET['action'] ) || 'edit' !== $_GET['action'] ); + + global $thepostid, $post; + + if ( $is_legacy_order && empty( $thepostid ) && empty( $post ) ) { + return; + } + + if ( $is_legacy_order || $is_hpos_order ) { + $fields = $this->create_label_fields(); + ?> + + wp_create_nonce( 'postnl_download_label_bulk_nonce' ), + 'view' => 'inline', + ), + home_url() + ); + } + + /** + * AJAX handler for bulk Print/Download Labels actions. + * + * @throws \Exception On validation or processing failure. + */ + public function postnl_bulk_labels_ajax() { + try { + if ( empty( $_POST['security'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['security'] ) ), 'postnl_bulk_labels_ajax_nonce' ) ) { + throw new \Exception( esc_html__( 'Nonce is invalid!', 'postnl-for-woocommerce' ) ); + } + + if ( ! current_user_can( 'manage_woocommerce' ) ) { + throw new \Exception( esc_html__( 'Insufficient permissions.', 'postnl-for-woocommerce' ) ); + } + + $order_ids = ! empty( $_POST['order_ids'] ) ? array_map( 'absint', (array) $_POST['order_ids'] ) : array(); + $bulk_action = ( ! empty( $_POST['bulk_action'] ) && 'print' === $_POST['bulk_action'] ) ? 'print' : 'download'; + + if ( empty( $order_ids ) ) { + throw new \Exception( esc_html__( 'No orders selected.', 'postnl-for-woocommerce' ) ); + } + + $gen_labels = array(); + + foreach ( $order_ids as $order_id ) { + $result = $this->generate_label_and_notes( $order_id, $_POST ); + if ( isset( $result['labels_data']['labels'] ) ) { + $gen_labels[] = $result['labels_data']['labels']; + } + } + + if ( empty( $gen_labels ) ) { + throw new \Exception( esc_html__( 'No labels could be created for the selected orders.', 'postnl-for-woocommerce' ) ); + } + + $merge_result = $this->merge_bulk_labels( $gen_labels ); + + if ( empty( $merge_result ) || 'error' === ( $merge_result['type'] ?? '' ) ) { + $error_msg = ! empty( $merge_result['message'] ) ? wp_strip_all_tags( $merge_result['message'] ) : esc_html__( 'Could not merge bulk labels.', 'postnl-for-woocommerce' ); + throw new \Exception( $error_msg ); + } + + $url = 'print' === $bulk_action ? $this->get_print_bulk_url() : $this->get_download_bulk_url(); + + wp_send_json_success( + array( + 'url' => $url, + 'bulk_action' => $bulk_action, + ) + ); + } catch ( \Exception $e ) { + wp_send_json_error( array( 'message' => $e->getMessage() ) ); + } } /** From 2e5488bc211aa5bd2c1722033bf2c398b914ab5f Mon Sep 17 00:00:00 2001 From: sal4sup Date: Fri, 17 Apr 2026 16:11:30 +0300 Subject: [PATCH 3/4] Update changelog --- README.md | 2 ++ changelog.txt | 2 ++ readme.txt | 2 ++ 3 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 466e2147..8013b143 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ PostNL’s official extension for WooCommerce on WordPress. Manage your national ### 5.9.5 * Fix: Update translations. +* Add: Print Label and Download Label buttons on the single order page now auto-create the label if one does not exist, then immediately trigger the print dialog or file download. +* Add: New "PostNL Print Labels" and "PostNL Download Labels" bulk actions that create labels for all selected orders and immediately trigger the print dialog or file download without a page reload. ### 5.9.4 * Fix: Missing styles from the cart page. diff --git a/changelog.txt b/changelog.txt index e71be392..d09215e5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -2,6 +2,8 @@ = 5.9.5 (2026-xx-xx) = * Fix: Update translations. +* Add: Print Label and Download Label buttons on the single order page now auto-create the label if one does not exist, then immediately trigger the print dialog or file download. +* Add: New "PostNL Print Labels" and "PostNL Download Labels" bulk actions that create labels for all selected orders and immediately trigger the print dialog or file download without a page reload. = 5.9.4 (2026-02-06) = * Fix: Missing styles from the cart page. diff --git a/readme.txt b/readme.txt index 7a885e1f..f68d1e85 100644 --- a/readme.txt +++ b/readme.txt @@ -83,6 +83,8 @@ Follow these instructions (https://www.postnl.nl/Images/aanvragen-api-key-stappe = 5.9.5 (2026-xx-xx) = * Fix: Update translations. +* Add: Print Label and Download Label buttons on the single order page now auto-create the label if one does not exist, then immediately trigger the print dialog or file download. +* Add: New "PostNL Print Labels" and "PostNL Download Labels" bulk actions that create labels for all selected orders and immediately trigger the print dialog or file download without a page reload. = 5.9.4 (2026-02-06) = * Fix: Missing styles from the cart page. From 5297acb0cd9a0bcdfcc6a1208a7ab4b94bc0f540 Mon Sep 17 00:00:00 2001 From: sal4sup Date: Mon, 20 Apr 2026 20:12:01 +0300 Subject: [PATCH 4/4] Show print button on the action on the orders list page --- assets/css/admin-order-bulk.css | 3 +++ assets/js/admin-order-bulk.js | 16 ++++++++++++++++ changelog.txt | 1 + src/Order/Bulk.php | 9 +++++++-- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/assets/css/admin-order-bulk.css b/assets/css/admin-order-bulk.css index 1435c25c..55eee9b2 100644 --- a/assets/css/admin-order-bulk.css +++ b/assets/css/admin-order-bulk.css @@ -40,6 +40,9 @@ .postnl-action-download-label:after { background: url(../images/post-download-label.png) no-repeat center center; } +a.postnl-action-print-label:after { + content: "\f193"; +} .postnl-bulk-spinner { margin-bottom: 8px; font-style: italic; diff --git a/assets/js/admin-order-bulk.js b/assets/js/admin-order-bulk.js index 6c9b991d..ed97baa0 100644 --- a/assets/js/admin-order-bulk.js +++ b/assets/js/admin-order-bulk.js @@ -13,6 +13,22 @@ .on( 'change', '#bulk-action-selector-bottom', this.toggle_create_label_modal ); posts_filter .on( 'click', '.button.action', this.disable_submit_button ); + posts_filter + .on( 'click', 'a.postnl-action-print-label', this.row_print_label ); + }, + + row_print_label: function( evt ) { + evt.preventDefault(); + var printUrl = jQuery( this ).attr( 'href' ); + var printWindow = window.open( '', '_blank' ); + if ( ! printWindow ) { + return false; + } + printWindow.location.href = printUrl; + printWindow.onload = function() { + printWindow.print(); + }; + return false; }, get_selected_order_ids: function( post_form ) { diff --git a/changelog.txt b/changelog.txt index d09215e5..a48f4cff 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,6 +4,7 @@ * Fix: Update translations. * Add: Print Label and Download Label buttons on the single order page now auto-create the label if one does not exist, then immediately trigger the print dialog or file download. * Add: New "PostNL Print Labels" and "PostNL Download Labels" bulk actions that create labels for all selected orders and immediately trigger the print dialog or file download without a page reload. +* Add: Separate Print and Download row-action icons in the Orders list for orders that already have a label. = 5.9.4 (2026-02-06) = * Fix: Missing styles from the cart page. diff --git a/src/Order/Bulk.php b/src/Order/Bulk.php index 124c3182..8f2ee657 100644 --- a/src/Order/Bulk.php +++ b/src/Order/Bulk.php @@ -745,11 +745,16 @@ public function add_create_label_actions_button( array $actions, \WC_Order $orde } if ( $this->have_label_file( $order ) ) { - $actions['postnl-label'] = array( + $actions['postnl-download-label'] = array( 'url' => $this->get_download_label_url( $order->get_id() ), - 'name' => esc_html__( 'PostNL Print Label', 'postnl-for-woocommerce' ), + 'name' => esc_html__( 'PostNL Download Label', 'postnl-for-woocommerce' ), 'action' => 'postnl-action-download-label', ); + $actions['postnl-print-label'] = array( + 'url' => $this->get_print_label_url( $order->get_id() ), + 'name' => esc_html__( 'PostNL Print Label', 'postnl-for-woocommerce' ), + 'action' => 'postnl-action-print-label', + ); } else { $actions['postnl-label'] = array( 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=postnl_create_label&order_id=' . $order->get_id() ), 'postnl_create_label' ),