Plugin Directory

Changeset 3732012


Ignore:
Timestamp:
10/07/2026 07:15:39 AM (3 weeks ago)
Author:
printess
Message:

Release 1.6.98: catch Printess API failures instead of white-screening. Ships 1.6.96 (WooCommerce declared as a required plugin), 1.6.97 (no more 'Trying to access array offset on null' when saving a variable product) and 1.6.98 (approving an order line, checking its production status or saving an edited one no longer ends in a blank page when a Printess request fails; failures are logged under 'printess-editor', added as an order note and shown as an admin notice, and can no longer break a paid order's status change).

Location:
printess-editor
Files:
4 edited
1 copied

Legend:

Unmodified
Added
Removed
  • printess-editor/tags/1.6.98/printess.php

    r3730705 r3732012  
    55 * Plugin URI: https://printess.com/kb/integrations/woo-commerce/index.html
    66 * Developer: Bastian Kröger (support@printess.com); Alexander Oser (support@printess.com)
    7  * Version: 1.6.95
     7 * Version: 1.6.98
    88 * Author: Printess
    99 * Author URI: https://printess.com
    … …  
    1212 * Requires at least: 5.9
    1313 * Requires PHP: 8.1
     14 * Requires Plugins: woocommerce
    1415 * Tested up to: 7.0
    1516 * License: MIT
    … …  
    913914
    914915        /**
     916         * Shortens an api error for the order note and the admin notice.
     917         *
     918         * A non 2xx error from the Printess api carries the whole production payload, which is far too
     919         * much for a notice or an order note. The full text stays in the log.
     920         *
     921         * @param string $message The exception message.
     922         * @param int    $limit   The maximum number of characters to keep.
     923         */
     924function printess_shorten_failure_message( $message, $limit = 300 ) {
     925        $message = trim( preg_replace( '/\s+/', ' ', (string) $message ) );
     926
     927        if ( strlen( $message ) <= $limit ) {
     928                return $message;
     929        }
     930
     931        return substr( $message, 0, $limit ) . '…';
     932}
     933
     934        /**
     935         * Records a failed Printess request where support and the merchant can both find it:
     936         * the WooCommerce log, and a note on the order itself.
     937         *
     938         * The Printess api throws on a transport failure and on any non 2xx response. Every request
     939         * handler that can reach it has to end up here instead of letting the exception escape, because
     940         * an uncaught exception in a hook is a fatal error and the merchant only sees a blank page.
     941         *
     942         * @param string $context    Where the failure happened.
     943         * @param string $message    What went wrong, in words a merchant can act on.
     944         * @param mixed  $order      The order, or null when it could not be loaded.
     945         * @param mixed  $item_id    The order line item id, or null when it is not known.
     946         * @param mixed  $save_token The Printess save token, or null when it is not known.
     947         */
     948function printess_log_production_failure( $context, $message, $order = null, $item_id = null, $save_token = null ) {
     949        $logger = wc_get_logger();
     950
     951        if ( isset( $logger ) ) {
     952                $logger->error(
     953                        'Printess Editor Integration: ' . $context . ': ' . $message
     954                        . ' (order: ' . ( $order instanceof WC_Order ? $order->get_id() : 'unknown' )
     955                        . ', line item: ' . ( ! empty( $item_id ) ? $item_id : 'unknown' )
     956                        . ', save token: ' . ( ! empty( $save_token ) ? $save_token : 'unknown' ) . ')',
     957                        array( 'source' => 'printess-editor' )
     958                );
     959        }
     960
     961        if ( $order instanceof WC_Order ) {
     962                /* translators: %s: the reason the Printess request failed. */
     963                $order->add_order_note( sprintf( __( 'Printess: %s', 'printess-editor' ), printess_shorten_failure_message( $message ) ) );
     964        }
     965}
     966
     967        /**
     968         * Remembers a message for the next admin screen this user opens.
     969         *
     970         * @param string $message The message to show.
     971         */
     972function printess_set_admin_notice( $message ) {
     973        $user_id = get_current_user_id();
     974
     975        if ( $user_id > 0 ) {
     976                set_transient( 'printess_admin_notice_' . $user_id, $message, MINUTE_IN_SECONDS );
     977        }
     978}
     979
     980        /**
     981         * Prints and clears the message stored by printess_set_admin_notice().
     982         */
     983function printess_render_admin_notice() {
     984        $user_id = get_current_user_id();
     985
     986        if ( $user_id <= 0 ) {
     987                return;
     988        }
     989
     990        $key     = 'printess_admin_notice_' . $user_id;
     991        $message = get_transient( $key );
     992
     993        if ( false === $message || empty( $message ) ) {
     994                return;
     995        }
     996
     997        delete_transient( $key );
     998
     999        echo '<div class="notice notice-error is-dismissible"><p>' . esc_html( $message ) . '</p></div>';
     1000}
     1001
     1002        /**
     1003         * Sends the merchant back to the order they came from.
     1004         *
     1005         * @param mixed $order_id The order id.
     1006         */
     1007function printess_redirect_to_order( $order_id ) {
     1008        $query_string = http_build_query(
     1009                array(
     1010                        'post'   => $order_id,
     1011                        'action' => 'edit',
     1012                )
     1013        );
     1014
     1015        $redirect = admin_url( 'post.php?' . $query_string );
     1016        wp_safe_redirect( $redirect );
     1017        die;
     1018}
     1019
     1020        /**
    9151021         * Sends the order line item to the Printess api for production.
    9161022         *
    … …  
    9211027         * @param mixed      $dropship_data_dto The dropship data.
    9221028         * @param int        $copies The number of copies that should be produced (quantity).
     1029         * @throws \Exception In case the line item cannot be produced.
    9231030         */
    9241031function printess_produce( $product, $order_id, $line_item_id, $line_item, $save_token, $dropship_data_dto = null, $copies = 1 ) {
     1032        if ( ! $product ) {
     1033                // get_product() returns false once the catalogue product behind the line has been deleted.
     1034                throw new \Exception( esc_html__( 'The product of this order line no longer exists, so it cannot be sent to production.', 'printess-editor' ) );
     1035        }
     1036
    9251037        $printess_host  = PrintessAdminSettings::get_host();
    9261038        $site_url       = get_site_url();
    … …  
    10561168        );
    10571169
     1170        if ( ! is_array( $response ) || ! isset( $response['jobId'] ) || empty( $response['jobId'] ) ) {
     1171                // Storing an empty job id would leave the line looking queued forever, so fail loudly.
     1172                throw new \Exception( esc_html__( 'Printess accepted the production request but did not return a job id.', 'printess-editor' ) );
     1173        }
     1174
    10581175        return $response['jobId'];
    10591176}
    … …  
    12841401        }
    12851402
    1286         $order = new WC_Order( $order_id );
    1287         $items = $order->get_items();
    1288 
    1289         printess_handle_order_items( $order, $items );
     1403        $order = null;
     1404
     1405        try {
     1406                $order = new WC_Order( $order_id );
     1407                $items = $order->get_items();
     1408
     1409                printess_handle_order_items( $order, $items );
     1410        } catch ( \Throwable $ex ) {
     1411                /*
     1412                 * This runs while the order changes to processing, so it can sit inside a payment gateway's
     1413                 * callback. A Printess failure must never break that transition: log it, note it on the
     1414                 * order, and let the merchant send the line to production by hand from the order screen.
     1415                 */
     1416                printess_log_production_failure( 'Unable to send an order to production', $ex->getMessage(), $order, null, null );
     1417        }
    12901418}
    12911419
    … …  
    29353063                        $printess_thumbnail_url = filter_input( INPUT_GET, 'ptu', FILTER_SANITIZE_URL );
    29363064
     3065                        $order = null;
     3066
     3067                try {
    29373068                        $order         = new WC_Order( $order_id );
    29383069                        $item          = $order->get_item( $line_item_id );
    29393070                        $approval_mode = PrintessAdminSettings::get_approval_mode();
    29403071
    2941                 if ( 'manual' === $approval_mode ) {
    2942                         $item->delete_meta_data( '_printess-job-id' );
    2943                 } else {
    2944                         $order_items = array( $line_item_id => $item );
    2945                         printess_handle_order_items( $order, $order_items );
    2946                 }
     3072                        if ( ! $item ) {
     3073                                throw new \Exception( esc_html__( 'This order line could not be found.', 'printess-editor' ) );
     3074                        }
     3075
     3076                        if ( 'manual' === $approval_mode ) {
     3077                                $item->delete_meta_data( '_printess-job-id' );
     3078                        } else {
     3079                                $order_items = array( $line_item_id => $item );
     3080                                printess_handle_order_items( $order, $order_items );
     3081                        }
    29473082
    29483083                        $item->delete_meta_data( '_printess-result' );
    … …  
    29503085                        $item->update_meta_data( '_printess-thumbnail-url', $printess_thumbnail_url, true );
    29513086                        $item->save_meta_data();
    2952 
    2953                 $query_string = http_build_query(
    2954                         array(
    2955                                 'post'   => $order_id,
    2956                                 'action' => 'edit',
    2957                         )
    2958                 );
    2959 
    2960                         $redirect = admin_url( 'post.php?' . $query_string );
    2961                         wp_safe_redirect( $redirect );
    2962                         die;
     3087                } catch ( \Throwable $ex ) {
     3088                        printess_log_production_failure( 'Unable to save an edited order line item', $ex->getMessage(), $order, $line_item_id, $printess_save_token );
     3089                        /* translators: %s: the reason the Printess request failed. */
     3090                        printess_set_admin_notice( sprintf( __( 'Printess could not save the edited order line: %s', 'printess-editor' ), printess_shorten_failure_message( $ex->getMessage() ) ) );
     3091                }
     3092
     3093                        printess_redirect_to_order( $order_id );
    29633094        }
    29643095}
    … …  
    29723103
    29733104        if ( isset( $action ) && isset( $nonce ) && (('printess_approve_order_line_item' === $action && wp_verify_nonce( $nonce, 'printess_approve_order_line_item' )) || ('printess_reproduce_order_line_item' === $action && wp_verify_nonce( $nonce, 'printess_reproduce_order_line_item' )))  ) {
    2974                                                                         $order_id            = filter_input( INPUT_GET, 'order_id', FILTER_SANITIZE_NUMBER_INT );
    2975                                                                         $line_item_id        = filter_input( INPUT_GET, 'item_id', FILTER_SANITIZE_NUMBER_INT );
    2976                                                                         $printess_save_token = filter_input( INPUT_GET, 'pst', FILTER_SANITIZE_SPECIAL_CHARS );
    2977                                                                         $order               = new WC_Order( $order_id );
    2978                                                                         $item                = $order->get_item( $line_item_id );
    2979 
    2980                                                                         $job_id = $item->get_meta( '_printess-job-id', true );
    2981 
    2982                                                                         if(null !== $job_id && !empty($job_id)) {
    2983                                                                                 $item->delete_meta_data( '_printess-job-id');
    2984                                                                         }
    2985 
    2986                                                                         $order_items = array( $line_item_id => $item );
    2987                                                                         printess_handle_order_items( $order, $order_items );
    2988 
    2989                                                                         $item->delete_meta_data( '_printess-result' );
    2990                                                                         $item->save_meta_data();
    2991 
    2992                                                                         $query_string = http_build_query(
    2993                                                                                 array(
    2994                                                                                         'post'   => $order_id,
    2995                                                                                         'action' => 'edit',
    2996                                                                                 )
    2997                                                                         );
    2998 
    2999                         $redirect = admin_url( 'post.php?' . $query_string );
    3000                         wp_safe_redirect( $redirect );
    3001                         die;
     3105                        $order_id            = filter_input( INPUT_GET, 'order_id', FILTER_SANITIZE_NUMBER_INT );
     3106                        $line_item_id        = filter_input( INPUT_GET, 'item_id', FILTER_SANITIZE_NUMBER_INT );
     3107                        $printess_save_token = filter_input( INPUT_GET, 'pst', FILTER_SANITIZE_SPECIAL_CHARS );
     3108                        $order               = null;
     3109
     3110                try {
     3111                        $order = new WC_Order( $order_id );
     3112                        $item  = $order->get_item( $line_item_id );
     3113
     3114                        if ( ! $item ) {
     3115                                throw new \Exception( esc_html__( 'This order line could not be found.', 'printess-editor' ) );
     3116                        }
     3117
     3118                        $job_id = $item->get_meta( '_printess-job-id', true );
     3119
     3120                        if ( null !== $job_id && ! empty( $job_id ) ) {
     3121                                $item->delete_meta_data( '_printess-job-id' );
     3122                        }
     3123
     3124                        $order_items = array( $line_item_id => $item );
     3125                        printess_handle_order_items( $order, $order_items );
     3126
     3127                        $item->delete_meta_data( '_printess-result' );
     3128                        $item->save_meta_data();
     3129                } catch ( \Throwable $ex ) {
     3130                        printess_log_production_failure( 'Unable to approve an order line item', $ex->getMessage(), $order, $line_item_id, $printess_save_token );
     3131                        /* translators: %s: the reason the Printess request failed. */
     3132                        printess_set_admin_notice( sprintf( __( 'Printess could not send this order line to production: %s', 'printess-editor' ), printess_shorten_failure_message( $ex->getMessage() ) ) );
     3133                }
     3134
     3135                        printess_redirect_to_order( $order_id );
    30023136        }
    30033137}
    … …  
    30113145    $line_item_id        = filter_input( INPUT_GET, 'item_id', FILTER_SANITIZE_NUMBER_INT );
    30123146    $printess_save_token = filter_input( INPUT_GET, 'pst', FILTER_SANITIZE_SPECIAL_CHARS );
    3013     $order               = new WC_Order( $order_id );
    3014     $item                = $order->get_item( $line_item_id );
    3015     $job_id              =  $item->get_meta( '_printess-job-id', true );
    3016 
    3017     $printess_host = PrintessAdminSettings::get_host();
    3018 
    3019     if(null !== $job_id && !empty($job_id)) {
    3020       $api_endpoint = '/production/status/get';
    3021       $response = PrintessApi::send_post_request(
    3022                 $printess_host . $api_endpoint,
    3023                 PrintessAdminSettings::get_service_token(),
    3024                 ["jobId" => $job_id]
    3025         );
    3026 
    3027         if(null !== $response && false === $response["isFinalStatus"] && false === $response["isSuccess"] && null === $response["enqueuedOn"]) {
    3028           //this production job does not exist anymore... somehow mark it
    3029           echo esc_html__( "This production job does not exist anymore.", 'printess-editor' );
    3030           die;
    3031         } else {
    3032           $response["isFailure"] = false === $response["isSuccess"];
    3033 
    3034           if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
    3035                                                 $formFieldLink = $response["result"]["ff"];
    3036 
    3037                                                 if(null != $formFieldLink && !empty($formFieldLink)) {
    3038                                                         $formFields = PrintessApi::send_get_request($formFieldLink);
    3039 
    3040                                                         if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
    3041                                                                 $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
    3042                                                         }
    3043                                                 }
    3044                                         }
    3045 
    3046           $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
    3047           $item->save_meta_data();
    3048         }
    3049     } else {
    3050       $order_items = array( $line_item_id => $item );
    3051 
    3052       //Download production status
    3053       $api_endpoint = '/orders/list';
    3054       $response = PrintessApi::send_post_request(
    3055                 $printess_host . $api_endpoint,
    3056                 PrintessAdminSettings::get_service_token(),
    3057                 ["saveToken" => $printess_save_token]
    3058         );
    3059 
    3060         if(0 == $response["count"]) {
    3061           echo esc_html__( "No order found for save token", 'printess-editor' );
    3062           die;
    3063         }
    3064 
    3065         if(count($response["orders"]) > 0) {
    3066           $response = $response["orders"][0];
    3067 
    3068           if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
    3069                                                 $formFieldLink = $response["result"]["ff"];
    3070 
    3071                                                 if(null != $formFieldLink && !empty($formFieldLink)) {
    3072                                                         $formFields = PrintessApi::send_get_request($formFieldLink);
    3073 
    3074                                                         if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
    3075                                                                 $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
    3076                                                         }
    3077                                                 }
    3078                                         }
    3079 
    3080           $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
    3081           $item->save_meta_data();
    3082         } else {
    3083           echo esc_html__( "No order found for save token", 'printess-editor' );
    3084           die;
    3085         }
     3147    $order               = null;
     3148
     3149    try {
     3150      $order               = new WC_Order( $order_id );
     3151      $item                = $order->get_item( $line_item_id );
     3152
     3153      if ( ! $item ) {
     3154        throw new \Exception( esc_html__( 'This order line could not be found.', 'printess-editor' ) );
     3155      }
     3156
     3157      $job_id              =  $item->get_meta( '_printess-job-id', true );
     3158
     3159      $printess_host = PrintessAdminSettings::get_host();
     3160
     3161      if(null !== $job_id && !empty($job_id)) {
     3162        $api_endpoint = '/production/status/get';
     3163        $response = PrintessApi::send_post_request(
     3164                        $printess_host . $api_endpoint,
     3165                        PrintessAdminSettings::get_service_token(),
     3166                        ["jobId" => $job_id]
     3167                );
     3168
     3169          if ( ! is_array( $response ) ) {
     3170            throw new \Exception( esc_html__( 'Printess returned an unexpected production status.', 'printess-editor' ) );
     3171          }
     3172
     3173          if(false === ($response["isFinalStatus"] ?? null) && false === ($response["isSuccess"] ?? null) && null === ($response["enqueuedOn"] ?? null)) {
     3174            //this production job does not exist anymore... somehow mark it
     3175            printess_set_admin_notice( __( 'This production job does not exist anymore.', 'printess-editor' ) );
     3176            printess_redirect_to_order( $order_id );
     3177          } else {
     3178            $response["isFailure"] = false === ($response["isSuccess"] ?? null);
     3179
     3180            if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
     3181                                                $formFieldLink = $response["result"]["ff"];
     3182
     3183                                                if(null != $formFieldLink && !empty($formFieldLink)) {
     3184                                                        $formFields = PrintessApi::send_get_request($formFieldLink);
     3185
     3186                                                        if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
     3187                                                                $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
     3188                                                        }
     3189                                                }
     3190                                        }
     3191
     3192            $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
     3193            $item->save_meta_data();
     3194          }
     3195      } else {
     3196        $order_items = array( $line_item_id => $item );
     3197
     3198        //Download production status
     3199        $api_endpoint = '/orders/list';
     3200        $response = PrintessApi::send_post_request(
     3201                        $printess_host . $api_endpoint,
     3202                        PrintessAdminSettings::get_service_token(),
     3203                        ["saveToken" => $printess_save_token]
     3204                );
     3205
     3206          if ( ! is_array( $response ) ) {
     3207            throw new \Exception( esc_html__( 'Printess returned an unexpected production status.', 'printess-editor' ) );
     3208          }
     3209
     3210          if(0 == ($response["count"] ?? 0)) {
     3211            printess_set_admin_notice( __( 'No order found for save token', 'printess-editor' ) );
     3212            printess_redirect_to_order( $order_id );
     3213          }
     3214
     3215          if(is_array($response["orders"] ?? null) && count($response["orders"]) > 0) {
     3216            $response = $response["orders"][0];
     3217
     3218            if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
     3219                                                $formFieldLink = $response["result"]["ff"];
     3220
     3221                                                if(null != $formFieldLink && !empty($formFieldLink)) {
     3222                                                        $formFields = PrintessApi::send_get_request($formFieldLink);
     3223
     3224                                                        if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
     3225                                                                $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
     3226                                                        }
     3227                                                }
     3228                                        }
     3229
     3230            $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
     3231            $item->save_meta_data();
     3232          } else {
     3233            printess_set_admin_notice( __( 'No order found for save token', 'printess-editor' ) );
     3234            printess_redirect_to_order( $order_id );
     3235          }
     3236      }
     3237    } catch ( \Throwable $ex ) {
     3238      printess_log_production_failure( 'Unable to check the production status of an order line item', $ex->getMessage(), $order, $line_item_id, $printess_save_token );
     3239      /* translators: %s: the reason the Printess request failed. */
     3240      printess_set_admin_notice( sprintf( __( 'Printess could not check the production status: %s', 'printess-editor' ), printess_shorten_failure_message( $ex->getMessage() ) ) );
    30863241    }
    30873242
    3088     $query_string = http_build_query(
    3089       array(
    3090         'post'   => $order_id,
    3091         'action' => 'edit',
    3092       )
    3093     );
    3094 
    3095           $redirect = admin_url( 'post.php?' . $query_string );
    3096           wp_safe_redirect( $redirect );
    3097           die;
     3243    printess_redirect_to_order( $order_id );
    30983244  }
    30993245}
    … …  
    32153361        }
    32163362
    3217         $template_name = $template_names[ $i ];
    3218         $is_merge_template = $is_merge_template[ $i ];
     3363        $template_name     = $template_names[ $i ] ?? null;
     3364        $is_merge_template = $is_merge_template[ $i ] ?? null;
    32193365
    32203366        if ( isset( $template_name ) ) {
    … …  
    42304376        add_filter('woocommerce_add_to_cart_validation', 'printess_validate_cart_item', 10, 2);
    42314377
     4378        add_action( 'admin_notices', 'printess_render_admin_notice' );
     4379
    42324380        // PRODUCT.
    42334381        add_action( 'admin_head', 'printess_admin_head' );
  • printess-editor/tags/1.6.98/readme.txt

    r3730705 r3732012  
    55Tested up to: 7.0
    66WC Tested up to: 10.5.3
    7 Stable tag: 1.6.95
     7Stable tag: 1.6.98
    88Requires PHP: 8.1
    99License: MIT
    … …  
    437437 - Fixed inconsistent version metadata that stopped WordPress from offering the latest update.
    438438 - Removed the WooCommerce.com marketplace header, which caused misleading "connect your store" notices on the plugins screen.
     439
     440 = 1.6.96 =
     441 - Declared WooCommerce as a required plugin (WordPress 6.5+), so the plugin can only be activated while WooCommerce is active.
     442
     443 = 1.6.97 =
     444 - Fixed a PHP warning ("Trying to access array offset on null") logged when saving a variable product whose variations have no merge template ticked.
     445
     446 = 1.6.98 =
     447 - Fixed a blank page when approving an order line, checking its production status or saving an edited one: a failing Printess request (network error, expired service token, deleted product) threw an uncaught exception.
     448 - Added: such failures are now written to the WooCommerce log under "printess-editor", added as an order note, and shown as an error notice on the order screen, instead of breaking the page.
     449 - Fixed: a Printess failure while an order moves to processing can no longer break that status change.
  • printess-editor/trunk/printess.php

    r3730705 r3732012  
    55 * Plugin URI: https://printess.com/kb/integrations/woo-commerce/index.html
    66 * Developer: Bastian Kröger (support@printess.com); Alexander Oser (support@printess.com)
    7  * Version: 1.6.95
     7 * Version: 1.6.98
    88 * Author: Printess
    99 * Author URI: https://printess.com
    … …  
    1212 * Requires at least: 5.9
    1313 * Requires PHP: 8.1
     14 * Requires Plugins: woocommerce
    1415 * Tested up to: 7.0
    1516 * License: MIT
    … …  
    913914
    914915        /**
     916         * Shortens an api error for the order note and the admin notice.
     917         *
     918         * A non 2xx error from the Printess api carries the whole production payload, which is far too
     919         * much for a notice or an order note. The full text stays in the log.
     920         *
     921         * @param string $message The exception message.
     922         * @param int    $limit   The maximum number of characters to keep.
     923         */
     924function printess_shorten_failure_message( $message, $limit = 300 ) {
     925        $message = trim( preg_replace( '/\s+/', ' ', (string) $message ) );
     926
     927        if ( strlen( $message ) <= $limit ) {
     928                return $message;
     929        }
     930
     931        return substr( $message, 0, $limit ) . '…';
     932}
     933
     934        /**
     935         * Records a failed Printess request where support and the merchant can both find it:
     936         * the WooCommerce log, and a note on the order itself.
     937         *
     938         * The Printess api throws on a transport failure and on any non 2xx response. Every request
     939         * handler that can reach it has to end up here instead of letting the exception escape, because
     940         * an uncaught exception in a hook is a fatal error and the merchant only sees a blank page.
     941         *
     942         * @param string $context    Where the failure happened.
     943         * @param string $message    What went wrong, in words a merchant can act on.
     944         * @param mixed  $order      The order, or null when it could not be loaded.
     945         * @param mixed  $item_id    The order line item id, or null when it is not known.
     946         * @param mixed  $save_token The Printess save token, or null when it is not known.
     947         */
     948function printess_log_production_failure( $context, $message, $order = null, $item_id = null, $save_token = null ) {
     949        $logger = wc_get_logger();
     950
     951        if ( isset( $logger ) ) {
     952                $logger->error(
     953                        'Printess Editor Integration: ' . $context . ': ' . $message
     954                        . ' (order: ' . ( $order instanceof WC_Order ? $order->get_id() : 'unknown' )
     955                        . ', line item: ' . ( ! empty( $item_id ) ? $item_id : 'unknown' )
     956                        . ', save token: ' . ( ! empty( $save_token ) ? $save_token : 'unknown' ) . ')',
     957                        array( 'source' => 'printess-editor' )
     958                );
     959        }
     960
     961        if ( $order instanceof WC_Order ) {
     962                /* translators: %s: the reason the Printess request failed. */
     963                $order->add_order_note( sprintf( __( 'Printess: %s', 'printess-editor' ), printess_shorten_failure_message( $message ) ) );
     964        }
     965}
     966
     967        /**
     968         * Remembers a message for the next admin screen this user opens.
     969         *
     970         * @param string $message The message to show.
     971         */
     972function printess_set_admin_notice( $message ) {
     973        $user_id = get_current_user_id();
     974
     975        if ( $user_id > 0 ) {
     976                set_transient( 'printess_admin_notice_' . $user_id, $message, MINUTE_IN_SECONDS );
     977        }
     978}
     979
     980        /**
     981         * Prints and clears the message stored by printess_set_admin_notice().
     982         */
     983function printess_render_admin_notice() {
     984        $user_id = get_current_user_id();
     985
     986        if ( $user_id <= 0 ) {
     987                return;
     988        }
     989
     990        $key     = 'printess_admin_notice_' . $user_id;
     991        $message = get_transient( $key );
     992
     993        if ( false === $message || empty( $message ) ) {
     994                return;
     995        }
     996
     997        delete_transient( $key );
     998
     999        echo '<div class="notice notice-error is-dismissible"><p>' . esc_html( $message ) . '</p></div>';
     1000}
     1001
     1002        /**
     1003         * Sends the merchant back to the order they came from.
     1004         *
     1005         * @param mixed $order_id The order id.
     1006         */
     1007function printess_redirect_to_order( $order_id ) {
     1008        $query_string = http_build_query(
     1009                array(
     1010                        'post'   => $order_id,
     1011                        'action' => 'edit',
     1012                )
     1013        );
     1014
     1015        $redirect = admin_url( 'post.php?' . $query_string );
     1016        wp_safe_redirect( $redirect );
     1017        die;
     1018}
     1019
     1020        /**
    9151021         * Sends the order line item to the Printess api for production.
    9161022         *
    … …  
    9211027         * @param mixed      $dropship_data_dto The dropship data.
    9221028         * @param int        $copies The number of copies that should be produced (quantity).
     1029         * @throws \Exception In case the line item cannot be produced.
    9231030         */
    9241031function printess_produce( $product, $order_id, $line_item_id, $line_item, $save_token, $dropship_data_dto = null, $copies = 1 ) {
     1032        if ( ! $product ) {
     1033                // get_product() returns false once the catalogue product behind the line has been deleted.
     1034                throw new \Exception( esc_html__( 'The product of this order line no longer exists, so it cannot be sent to production.', 'printess-editor' ) );
     1035        }
     1036
    9251037        $printess_host  = PrintessAdminSettings::get_host();
    9261038        $site_url       = get_site_url();
    … …  
    10561168        );
    10571169
     1170        if ( ! is_array( $response ) || ! isset( $response['jobId'] ) || empty( $response['jobId'] ) ) {
     1171                // Storing an empty job id would leave the line looking queued forever, so fail loudly.
     1172                throw new \Exception( esc_html__( 'Printess accepted the production request but did not return a job id.', 'printess-editor' ) );
     1173        }
     1174
    10581175        return $response['jobId'];
    10591176}
    … …  
    12841401        }
    12851402
    1286         $order = new WC_Order( $order_id );
    1287         $items = $order->get_items();
    1288 
    1289         printess_handle_order_items( $order, $items );
     1403        $order = null;
     1404
     1405        try {
     1406                $order = new WC_Order( $order_id );
     1407                $items = $order->get_items();
     1408
     1409                printess_handle_order_items( $order, $items );
     1410        } catch ( \Throwable $ex ) {
     1411                /*
     1412                 * This runs while the order changes to processing, so it can sit inside a payment gateway's
     1413                 * callback. A Printess failure must never break that transition: log it, note it on the
     1414                 * order, and let the merchant send the line to production by hand from the order screen.
     1415                 */
     1416                printess_log_production_failure( 'Unable to send an order to production', $ex->getMessage(), $order, null, null );
     1417        }
    12901418}
    12911419
    … …  
    29353063                        $printess_thumbnail_url = filter_input( INPUT_GET, 'ptu', FILTER_SANITIZE_URL );
    29363064
     3065                        $order = null;
     3066
     3067                try {
    29373068                        $order         = new WC_Order( $order_id );
    29383069                        $item          = $order->get_item( $line_item_id );
    29393070                        $approval_mode = PrintessAdminSettings::get_approval_mode();
    29403071
    2941                 if ( 'manual' === $approval_mode ) {
    2942                         $item->delete_meta_data( '_printess-job-id' );
    2943                 } else {
    2944                         $order_items = array( $line_item_id => $item );
    2945                         printess_handle_order_items( $order, $order_items );
    2946                 }
     3072                        if ( ! $item ) {
     3073                                throw new \Exception( esc_html__( 'This order line could not be found.', 'printess-editor' ) );
     3074                        }
     3075
     3076                        if ( 'manual' === $approval_mode ) {
     3077                                $item->delete_meta_data( '_printess-job-id' );
     3078                        } else {
     3079                                $order_items = array( $line_item_id => $item );
     3080                                printess_handle_order_items( $order, $order_items );
     3081                        }
    29473082
    29483083                        $item->delete_meta_data( '_printess-result' );
    … …  
    29503085                        $item->update_meta_data( '_printess-thumbnail-url', $printess_thumbnail_url, true );
    29513086                        $item->save_meta_data();
    2952 
    2953                 $query_string = http_build_query(
    2954                         array(
    2955                                 'post'   => $order_id,
    2956                                 'action' => 'edit',
    2957                         )
    2958                 );
    2959 
    2960                         $redirect = admin_url( 'post.php?' . $query_string );
    2961                         wp_safe_redirect( $redirect );
    2962                         die;
     3087                } catch ( \Throwable $ex ) {
     3088                        printess_log_production_failure( 'Unable to save an edited order line item', $ex->getMessage(), $order, $line_item_id, $printess_save_token );
     3089                        /* translators: %s: the reason the Printess request failed. */
     3090                        printess_set_admin_notice( sprintf( __( 'Printess could not save the edited order line: %s', 'printess-editor' ), printess_shorten_failure_message( $ex->getMessage() ) ) );
     3091                }
     3092
     3093                        printess_redirect_to_order( $order_id );
    29633094        }
    29643095}
    … …  
    29723103
    29733104        if ( isset( $action ) && isset( $nonce ) && (('printess_approve_order_line_item' === $action && wp_verify_nonce( $nonce, 'printess_approve_order_line_item' )) || ('printess_reproduce_order_line_item' === $action && wp_verify_nonce( $nonce, 'printess_reproduce_order_line_item' )))  ) {
    2974                                                                         $order_id            = filter_input( INPUT_GET, 'order_id', FILTER_SANITIZE_NUMBER_INT );
    2975                                                                         $line_item_id        = filter_input( INPUT_GET, 'item_id', FILTER_SANITIZE_NUMBER_INT );
    2976                                                                         $printess_save_token = filter_input( INPUT_GET, 'pst', FILTER_SANITIZE_SPECIAL_CHARS );
    2977                                                                         $order               = new WC_Order( $order_id );
    2978                                                                         $item                = $order->get_item( $line_item_id );
    2979 
    2980                                                                         $job_id = $item->get_meta( '_printess-job-id', true );
    2981 
    2982                                                                         if(null !== $job_id && !empty($job_id)) {
    2983                                                                                 $item->delete_meta_data( '_printess-job-id');
    2984                                                                         }
    2985 
    2986                                                                         $order_items = array( $line_item_id => $item );
    2987                                                                         printess_handle_order_items( $order, $order_items );
    2988 
    2989                                                                         $item->delete_meta_data( '_printess-result' );
    2990                                                                         $item->save_meta_data();
    2991 
    2992                                                                         $query_string = http_build_query(
    2993                                                                                 array(
    2994                                                                                         'post'   => $order_id,
    2995                                                                                         'action' => 'edit',
    2996                                                                                 )
    2997                                                                         );
    2998 
    2999                         $redirect = admin_url( 'post.php?' . $query_string );
    3000                         wp_safe_redirect( $redirect );
    3001                         die;
     3105                        $order_id            = filter_input( INPUT_GET, 'order_id', FILTER_SANITIZE_NUMBER_INT );
     3106                        $line_item_id        = filter_input( INPUT_GET, 'item_id', FILTER_SANITIZE_NUMBER_INT );
     3107                        $printess_save_token = filter_input( INPUT_GET, 'pst', FILTER_SANITIZE_SPECIAL_CHARS );
     3108                        $order               = null;
     3109
     3110                try {
     3111                        $order = new WC_Order( $order_id );
     3112                        $item  = $order->get_item( $line_item_id );
     3113
     3114                        if ( ! $item ) {
     3115                                throw new \Exception( esc_html__( 'This order line could not be found.', 'printess-editor' ) );
     3116                        }
     3117
     3118                        $job_id = $item->get_meta( '_printess-job-id', true );
     3119
     3120                        if ( null !== $job_id && ! empty( $job_id ) ) {
     3121                                $item->delete_meta_data( '_printess-job-id' );
     3122                        }
     3123
     3124                        $order_items = array( $line_item_id => $item );
     3125                        printess_handle_order_items( $order, $order_items );
     3126
     3127                        $item->delete_meta_data( '_printess-result' );
     3128                        $item->save_meta_data();
     3129                } catch ( \Throwable $ex ) {
     3130                        printess_log_production_failure( 'Unable to approve an order line item', $ex->getMessage(), $order, $line_item_id, $printess_save_token );
     3131                        /* translators: %s: the reason the Printess request failed. */
     3132                        printess_set_admin_notice( sprintf( __( 'Printess could not send this order line to production: %s', 'printess-editor' ), printess_shorten_failure_message( $ex->getMessage() ) ) );
     3133                }
     3134
     3135                        printess_redirect_to_order( $order_id );
    30023136        }
    30033137}
    … …  
    30113145    $line_item_id        = filter_input( INPUT_GET, 'item_id', FILTER_SANITIZE_NUMBER_INT );
    30123146    $printess_save_token = filter_input( INPUT_GET, 'pst', FILTER_SANITIZE_SPECIAL_CHARS );
    3013     $order               = new WC_Order( $order_id );
    3014     $item                = $order->get_item( $line_item_id );
    3015     $job_id              =  $item->get_meta( '_printess-job-id', true );
    3016 
    3017     $printess_host = PrintessAdminSettings::get_host();
    3018 
    3019     if(null !== $job_id && !empty($job_id)) {
    3020       $api_endpoint = '/production/status/get';
    3021       $response = PrintessApi::send_post_request(
    3022                 $printess_host . $api_endpoint,
    3023                 PrintessAdminSettings::get_service_token(),
    3024                 ["jobId" => $job_id]
    3025         );
    3026 
    3027         if(null !== $response && false === $response["isFinalStatus"] && false === $response["isSuccess"] && null === $response["enqueuedOn"]) {
    3028           //this production job does not exist anymore... somehow mark it
    3029           echo esc_html__( "This production job does not exist anymore.", 'printess-editor' );
    3030           die;
    3031         } else {
    3032           $response["isFailure"] = false === $response["isSuccess"];
    3033 
    3034           if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
    3035                                                 $formFieldLink = $response["result"]["ff"];
    3036 
    3037                                                 if(null != $formFieldLink && !empty($formFieldLink)) {
    3038                                                         $formFields = PrintessApi::send_get_request($formFieldLink);
    3039 
    3040                                                         if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
    3041                                                                 $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
    3042                                                         }
    3043                                                 }
    3044                                         }
    3045 
    3046           $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
    3047           $item->save_meta_data();
    3048         }
    3049     } else {
    3050       $order_items = array( $line_item_id => $item );
    3051 
    3052       //Download production status
    3053       $api_endpoint = '/orders/list';
    3054       $response = PrintessApi::send_post_request(
    3055                 $printess_host . $api_endpoint,
    3056                 PrintessAdminSettings::get_service_token(),
    3057                 ["saveToken" => $printess_save_token]
    3058         );
    3059 
    3060         if(0 == $response["count"]) {
    3061           echo esc_html__( "No order found for save token", 'printess-editor' );
    3062           die;
    3063         }
    3064 
    3065         if(count($response["orders"]) > 0) {
    3066           $response = $response["orders"][0];
    3067 
    3068           if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
    3069                                                 $formFieldLink = $response["result"]["ff"];
    3070 
    3071                                                 if(null != $formFieldLink && !empty($formFieldLink)) {
    3072                                                         $formFields = PrintessApi::send_get_request($formFieldLink);
    3073 
    3074                                                         if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
    3075                                                                 $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
    3076                                                         }
    3077                                                 }
    3078                                         }
    3079 
    3080           $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
    3081           $item->save_meta_data();
    3082         } else {
    3083           echo esc_html__( "No order found for save token", 'printess-editor' );
    3084           die;
    3085         }
     3147    $order               = null;
     3148
     3149    try {
     3150      $order               = new WC_Order( $order_id );
     3151      $item                = $order->get_item( $line_item_id );
     3152
     3153      if ( ! $item ) {
     3154        throw new \Exception( esc_html__( 'This order line could not be found.', 'printess-editor' ) );
     3155      }
     3156
     3157      $job_id              =  $item->get_meta( '_printess-job-id', true );
     3158
     3159      $printess_host = PrintessAdminSettings::get_host();
     3160
     3161      if(null !== $job_id && !empty($job_id)) {
     3162        $api_endpoint = '/production/status/get';
     3163        $response = PrintessApi::send_post_request(
     3164                        $printess_host . $api_endpoint,
     3165                        PrintessAdminSettings::get_service_token(),
     3166                        ["jobId" => $job_id]
     3167                );
     3168
     3169          if ( ! is_array( $response ) ) {
     3170            throw new \Exception( esc_html__( 'Printess returned an unexpected production status.', 'printess-editor' ) );
     3171          }
     3172
     3173          if(false === ($response["isFinalStatus"] ?? null) && false === ($response["isSuccess"] ?? null) && null === ($response["enqueuedOn"] ?? null)) {
     3174            //this production job does not exist anymore... somehow mark it
     3175            printess_set_admin_notice( __( 'This production job does not exist anymore.', 'printess-editor' ) );
     3176            printess_redirect_to_order( $order_id );
     3177          } else {
     3178            $response["isFailure"] = false === ($response["isSuccess"] ?? null);
     3179
     3180            if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
     3181                                                $formFieldLink = $response["result"]["ff"];
     3182
     3183                                                if(null != $formFieldLink && !empty($formFieldLink)) {
     3184                                                        $formFields = PrintessApi::send_get_request($formFieldLink);
     3185
     3186                                                        if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
     3187                                                                $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
     3188                                                        }
     3189                                                }
     3190                                        }
     3191
     3192            $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
     3193            $item->save_meta_data();
     3194          }
     3195      } else {
     3196        $order_items = array( $line_item_id => $item );
     3197
     3198        //Download production status
     3199        $api_endpoint = '/orders/list';
     3200        $response = PrintessApi::send_post_request(
     3201                        $printess_host . $api_endpoint,
     3202                        PrintessAdminSettings::get_service_token(),
     3203                        ["saveToken" => $printess_save_token]
     3204                );
     3205
     3206          if ( ! is_array( $response ) ) {
     3207            throw new \Exception( esc_html__( 'Printess returned an unexpected production status.', 'printess-editor' ) );
     3208          }
     3209
     3210          if(0 == ($response["count"] ?? 0)) {
     3211            printess_set_admin_notice( __( 'No order found for save token', 'printess-editor' ) );
     3212            printess_redirect_to_order( $order_id );
     3213          }
     3214
     3215          if(is_array($response["orders"] ?? null) && count($response["orders"]) > 0) {
     3216            $response = $response["orders"][0];
     3217
     3218            if(array_key_exists("result", $response) && array_key_exists("ff", $response["result"])) {
     3219                                                $formFieldLink = $response["result"]["ff"];
     3220
     3221                                                if(null != $formFieldLink && !empty($formFieldLink)) {
     3222                                                        $formFields = PrintessApi::send_get_request($formFieldLink);
     3223
     3224                                                        if(null != $formFields && is_array($formFields) && array_key_exists("environment", $formFields)) {
     3225                                                                $item->update_meta_data( '_printess-form-fields', wp_json_encode( $formFields["environment"] ), true );
     3226                                                        }
     3227                                                }
     3228                                        }
     3229
     3230            $item->update_meta_data( '_printess-result', wp_json_encode( $response ), true );
     3231            $item->save_meta_data();
     3232          } else {
     3233            printess_set_admin_notice( __( 'No order found for save token', 'printess-editor' ) );
     3234            printess_redirect_to_order( $order_id );
     3235          }
     3236      }
     3237    } catch ( \Throwable $ex ) {
     3238      printess_log_production_failure( 'Unable to check the production status of an order line item', $ex->getMessage(), $order, $line_item_id, $printess_save_token );
     3239      /* translators: %s: the reason the Printess request failed. */
     3240      printess_set_admin_notice( sprintf( __( 'Printess could not check the production status: %s', 'printess-editor' ), printess_shorten_failure_message( $ex->getMessage() ) ) );
    30863241    }
    30873242
    3088     $query_string = http_build_query(
    3089       array(
    3090         'post'   => $order_id,
    3091         'action' => 'edit',
    3092       )
    3093     );
    3094 
    3095           $redirect = admin_url( 'post.php?' . $query_string );
    3096           wp_safe_redirect( $redirect );
    3097           die;
     3243    printess_redirect_to_order( $order_id );
    30983244  }
    30993245}
    … …  
    32153361        }
    32163362
    3217         $template_name = $template_names[ $i ];
    3218         $is_merge_template = $is_merge_template[ $i ];
     3363        $template_name     = $template_names[ $i ] ?? null;
     3364        $is_merge_template = $is_merge_template[ $i ] ?? null;
    32193365
    32203366        if ( isset( $template_name ) ) {
    … …  
    42304376        add_filter('woocommerce_add_to_cart_validation', 'printess_validate_cart_item', 10, 2);
    42314377
     4378        add_action( 'admin_notices', 'printess_render_admin_notice' );
     4379
    42324380        // PRODUCT.
    42334381        add_action( 'admin_head', 'printess_admin_head' );
  • printess-editor/trunk/readme.txt

    r3730705 r3732012  
    55Tested up to: 7.0
    66WC Tested up to: 10.5.3
    7 Stable tag: 1.6.95
     7Stable tag: 1.6.98
    88Requires PHP: 8.1
    99License: MIT
    … …  
    437437 - Fixed inconsistent version metadata that stopped WordPress from offering the latest update.
    438438 - Removed the WooCommerce.com marketplace header, which caused misleading "connect your store" notices on the plugins screen.
     439
     440 = 1.6.96 =
     441 - Declared WooCommerce as a required plugin (WordPress 6.5+), so the plugin can only be activated while WooCommerce is active.
     442
     443 = 1.6.97 =
     444 - Fixed a PHP warning ("Trying to access array offset on null") logged when saving a variable product whose variations have no merge template ticked.
     445
     446 = 1.6.98 =
     447 - Fixed a blank page when approving an order line, checking its production status or saving an edited one: a failing Printess request (network error, expired service token, deleted product) threw an uncaught exception.
     448 - Added: such failures are now written to the WooCommerce log under "printess-editor", added as an order note, and shown as an error notice on the order screen, instead of breaking the page.
     449 - Fixed: a Printess failure while an order moves to processing can no longer break that status change.
Note: See TracChangeset for help on using the changeset viewer.