Plugin Directory

Changeset 3485558


Ignore:
Timestamp:
03/18/2026 11:04:49 AM (2 weeks ago)
Author:
bookpodplugin1
Message:

Updating to version 2.1.6

Location:
bookpod-author-tools
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • bookpod-author-tools/tags/2.1.5/readme.txt

    r3475828 r3485558  
    5656== Changelog ==
    5757
    58 = 2.1.5 =
     58= 2.1.6 =
    5959* Adding support for digital books shipping messages, emails and checkout
    6060* fixing and updating the create book fulfillment
  • bookpod-author-tools/trunk/bookpod-author-tools.php

    r3475827 r3485558  
    33 * Plugin Name: BookPod Author Tools
    44 * Description: A plugin for managing books and orders through Bookpod.
    5  * Version:     2.1.5
     5 * Version:     2.1.6
    66 * Author:      Rachel Stern
    77 * Text Domain: bookpod-author-tools
  • bookpod-author-tools/trunk/bpat-book.php

    r3475827 r3485558  
    127127        'cleanup' => true,
    128128    ];
     129}
     130
     131// ---------------------------------------------------------------------------------------------------------- //
     132
     133/**
     134 * Resolve a product field value from multiple possible sources, in priority order:
     135 *  1. ACF get_field() – if Advanced Custom Fields is active
     136 *  2. Direct post_meta with the provided $meta_keys
     137 *  3. WooCommerce attribute matched by label via wc_get_attribute_taxonomies()
     138 *  4. WooCommerce attribute matched by slug/name via $product->get_attribute()
     139 *
     140 * @param int         $product_id     WooCommerce product ID
     141 * @param WC_Product  $product        WooCommerce product object
     142 * @param string[]    $attr_names     Possible attribute / field names to try (Hebrew & English)
     143 * @param string[]    $meta_keys      Possible post_meta keys to try as fallback
     144 * @return string  Resolved value, or empty string
     145 */
     146function bpat_resolve_product_field( $product_id, $product, array $attr_names, array $meta_keys ) {
     147
     148    // 0. WordPress custom taxonomies — checked first because these are the definitive
     149    //    source in stores that register product_author / product_publisher outside WooCommerce.
     150    $wp_taxonomy_map = [
     151        'product_author'    => [ 'סופרים', 'מחבר', 'author', 'authors' ],
     152        'product_publisher' => [ 'הוצאות', 'הוצאה לאור', 'הוצאה-לאור', 'publisher', 'publishers' ],
     153    ];
     154    foreach ( $wp_taxonomy_map as $taxonomy => $known_names ) {
     155        $known_lower = array_map( 'mb_strtolower', $known_names );
     156        $matched     = false;
     157        foreach ( $attr_names as $name ) {
     158            if ( in_array( mb_strtolower( $name ), $known_lower, true ) ) {
     159                $matched = true;
     160                break;
     161            }
     162        }
     163        if ( $matched && taxonomy_exists( $taxonomy ) ) {
     164            $terms = wp_get_post_terms( $product_id, $taxonomy, [ 'fields' => 'names' ] );
     165            if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
     166                return implode( ', ', $terms );
     167            }
     168        }
     169    }
     170
     171    // 1. ACF
     172    if ( function_exists( 'get_field' ) ) {
     173        foreach ( $attr_names as $name ) {
     174            $val = get_field( $name, $product_id );
     175            if ( $val ) {
     176                if ( is_array( $val ) ) {
     177                    // ACF taxonomy / checkbox fields return arrays of term objects or strings
     178                    $items = [];
     179                    foreach ( $val as $item ) {
     180                        if ( is_object( $item ) && isset( $item->name ) ) {
     181                            $items[] = $item->name;
     182                        } elseif ( is_string( $item ) || is_numeric( $item ) ) {
     183                            $items[] = (string) $item;
     184                        }
     185                    }
     186                    if ( $items ) {
     187                        return implode( ', ', $items );
     188                    }
     189                } else {
     190                    return (string) $val;
     191                }
     192            }
     193        }
     194    }
     195
     196    // 2. Direct post_meta
     197    foreach ( $meta_keys as $key ) {
     198        $val = get_post_meta( $product_id, $key, true );
     199        if ( $val && is_scalar( $val ) ) {
     200            return (string) $val;
     201        }
     202    }
     203
     204    // 3. WooCommerce attribute matched by registered label
     205    if ( function_exists( 'wc_get_attribute_taxonomies' ) ) {
     206        $registered = wc_get_attribute_taxonomies();
     207        foreach ( $registered as $reg ) {
     208            $label_lower = mb_strtolower( $reg->attribute_label );
     209            foreach ( $attr_names as $name ) {
     210                if ( mb_strtolower( $name ) === $label_lower ) {
     211                    $taxonomy = 'pa_' . $reg->attribute_name;
     212                    $terms = wc_get_product_terms( $product_id, $taxonomy, [ 'fields' => 'names' ] );
     213                    if ( ! empty( $terms ) ) {
     214                        return implode( ', ', $terms );
     215                    }
     216                }
     217            }
     218        }
     219    }
     220
     221    // 4. WooCommerce attribute by slug / name (via get_attribute which sanitizes internally)
     222    foreach ( $attr_names as $name ) {
     223        $val = $product->get_attribute( $name );
     224        if ( $val !== '' ) {
     225            return $val;
     226        }
     227    }
     228
     229    // 5. Iterate all product attributes and match by internal array key
     230    //    Covers local attributes stored as 'סופרים' and global ones stored as 'pa_xxx'
     231    foreach ( $product->get_attributes() as $attr_key => $attr_obj ) {
     232        $key_lower   = mb_strtolower( $attr_key );
     233        $slug_lower  = mb_strtolower( str_replace( 'pa_', '', $attr_key ) );
     234
     235        foreach ( $attr_names as $name ) {
     236            $name_lower = mb_strtolower( $name );
     237            if ( $key_lower === $name_lower || $slug_lower === $name_lower ) {
     238                if ( $attr_obj->is_taxonomy() ) {
     239                    $terms = wc_get_product_terms( $product_id, $attr_obj->get_name(), [ 'fields' => 'names' ] );
     240                    if ( ! empty( $terms ) ) {
     241                        return implode( ', ', $terms );
     242                    }
     243                } else {
     244                    $options = $attr_obj->get_options();
     245                    if ( ! empty( $options ) ) {
     246                        return implode( ', ', $options );
     247                    }
     248                }
     249            }
     250        }
     251    }
     252
     253    return '';
    129254}
    130255
     
    406531            }
    407532
    408             if ( $virtual && $downloadable ) {
     533            // For variable products, inspect variations to derive book type and prices.
     534            $var_digital_price  = '';
     535            $var_physical_price = '';
     536
     537            if ( $product->get_type() === 'variable' ) {
     538                foreach ( $product->get_children() as $var_id ) {
     539                    $variation = wc_get_product( $var_id );
     540                    if ( ! $variation ) {
     541                        continue;
     542                    }
     543                    if ( $variation->is_virtual() ) {
     544                        if ( $var_digital_price === '' ) {
     545                            $var_digital_price = (string) $variation->get_regular_price();
     546                        }
     547                    } else {
     548                        if ( $var_physical_price === '' ) {
     549                            $var_physical_price = (string) $variation->get_regular_price();
     550                        }
     551                    }
     552                }
     553
     554                if ( $var_digital_price !== '' && $var_physical_price !== '' ) {
     555                    $derived_booktype = 'both';
     556                } elseif ( $var_digital_price !== '' ) {
     557                    $derived_booktype = 'digital';
     558                } else {
     559                    $derived_booktype = 'print';
     560                }
     561            } elseif ( $virtual && $downloadable ) {
    409562                $derived_booktype = 'digital';
    410563            } elseif ( $downloadable ) {
     
    423576
    424577                if ( $prefill['epubprice'] === '' ) {
    425                     $prefill['epubprice'] = $regular_price;
     578                    $prefill['epubprice'] = $var_digital_price !== '' ? $var_digital_price : $regular_price;
    426579                }
    427580                $prefill['price'] = '';
     
    430583
    431584                if ( $prefill['price'] === '' ) {
    432                     $prefill['price'] = $regular_price;
     585                    $prefill['price'] = $var_physical_price !== '' ? $var_physical_price : $regular_price;
    433586                }
    434587
    435588                if ( $prefill['booktype'] === 'both' && $prefill['epubprice'] === '' ) {
    436                     $prefill['epubprice'] = get_post_meta(
    437                         $source_product_id,
    438                         '_bpat_epub_price',
    439                         true
    440                     ) ?: $regular_price;
     589                    $prefill['epubprice'] = $var_digital_price !== ''
     590                        ? $var_digital_price
     591                        : ( get_post_meta( $source_product_id, '_bpat_epub_price', true ) ?: $regular_price );
    441592                }
    442593            }
     
    451602
    452603            if ( $prefill['author'] === '' ) {
    453                 $prefill['author'] = $product->get_attribute( 'author' )
    454                     ?: $product->get_attribute( 'מחבר' )
    455                     ?: get_post_meta( $source_product_id, '_bpat_author', true )
    456                     ?: get_post_meta( $source_product_id, 'author', true );
     604                $prefill['author'] = bpat_resolve_product_field(
     605                    $source_product_id,
     606                    $product,
     607                    [ 'סופרים', 'מחבר', 'author', 'authors' ],
     608                    [ '_bpat_author', 'סופרים', 'author' ]
     609                );
    457610            }
    458611
    459612            if ( $prefill['publisher'] === '' ) {
    460                 $prefill['publisher'] = $product->get_attribute( 'publisher' )
    461                     ?: $product->get_attribute( 'הוצאה לאור' )
    462                     ?: $product->get_attribute( 'הוצאה-לאור' )
    463                     ?: get_post_meta( $source_product_id, '_bpat_publisher', true )
    464                     ?: get_post_meta( $source_product_id, 'publisher', true );
    465             }
    466 
     613                $prefill['publisher'] = bpat_resolve_product_field(
     614                    $source_product_id,
     615                    $product,
     616                    [ 'הוצאות', 'הוצאה לאור', 'הוצאה-לאור', 'publisher', 'publishers' ],
     617                    [ '_bpat_publisher', 'הוצאות', 'publisher' ]
     618                );
     619            }
     620
     621            $unmapped_category_names = [];
    467622            if ( empty( $prefill['categories'] ) ) {
    468623                $cat_ids = $product->get_category_ids();
     
    471626                    if ( $term && ! is_wp_error( $term ) ) {
    472627                        $slug = urldecode( $term->slug );
     628                        $mapped = false;
    473629                        if ( isset( $category_map[ $slug ] ) ) {
    474630                            $prefill['categories'][] = $category_map[ $slug ];
     631                            $mapped = true;
     632                        } elseif ( isset( $category_map[ $term->name ] ) ) {
     633                            $prefill['categories'][] = $category_map[ $term->name ];
     634                            $mapped = true;
    475635                        } else {
    476636                            $key = array_search( $slug, $category_map );
    477637                            if ( $key !== false ) {
    478638                                $prefill['categories'][] = $category_map[ $key ];
     639                                $mapped = true;
    479640                            }
     641                        }
     642                        if ( ! $mapped ) {
     643                            $unmapped_category_names[] = $term->name;
    480644                        }
    481645                    }
     
    485649
    486650            if ( empty( $prefill['subcategory'] ) ) {
    487                 $tags = wp_get_post_terms( $source_product_id, 'product_tag', ['fields' => 'names'] );
    488                 if ( ! is_wp_error( $tags ) && $tags ) {
    489                     $prefill['subcategory'] = implode( ', ', $tags );
     651                $raw_tags = wp_get_post_terms( $source_product_id, 'product_tag', [ 'fields' => 'all' ] );
     652                $tag_names = [];
     653                if ( ! is_wp_error( $raw_tags ) && $raw_tags ) {
     654                    foreach ( $raw_tags as $tag ) {
     655                        if ( $tag->slug === 'exclude_ehouse' ) {
     656                            continue;
     657                        }
     658                        $tag_slug = urldecode( $tag->slug );
     659                        $mapped_cat = null;
     660                        if ( isset( $category_map[ $tag->name ] ) ) {
     661                            $mapped_cat = $category_map[ $tag->name ];
     662                        } elseif ( isset( $category_map[ $tag_slug ] ) ) {
     663                            $mapped_cat = $category_map[ $tag_slug ];
     664                        } else {
     665                            $key = array_search( $tag_slug, $category_map );
     666                            if ( $key !== false ) {
     667                                $mapped_cat = $category_map[ $key ];
     668                            }
     669                        }
     670                        if ( $mapped_cat !== null ) {
     671                            $prefill['categories'][] = $mapped_cat;
     672                        } else {
     673                            $tag_names[] = $tag->name;
     674                        }
     675                    }
     676                    $prefill['categories'] = array_values( array_unique( $prefill['categories'] ) );
     677                }
     678                // Append unmapped product categories as tags
     679                if ( ! empty( $unmapped_category_names ) ) {
     680                    $tag_names = array_values( array_unique( array_merge( $tag_names, $unmapped_category_names ) ) );
     681                }
     682                if ( ! empty( $tag_names ) ) {
     683                    $prefill['subcategory'] = implode( ', ', $tag_names );
    490684                }
    491685            }
  • bookpod-author-tools/trunk/bpat-checkout-logic.js

    r3449910 r3485558  
    127127          var $label = $('label[for="' + id + '"]');
    128128          if ($label.length) {
    129             var html = $label.html();
    130             if (html.indexOf("(optional)") !== -1) {
    131               $label.html(html.replace("(optional)", "").replace(/\s*$/, ""));
    132             } else if (html.indexOf("(optional)") !== -1) {
    133               $label.html(html.replace("(optional)", "").replace(/\s*$/, ""));
    134             }
     129            // Remove WooCommerce's "(optional)" span (works with any language/translation)
     130            $label.find(".optional").remove();
    135131
    136132            if ($label.find(".required").length === 0) {
     
    239235        }
    240236
     237        // Re-enable the select so it is included in form serialization.
     238        // fetchPoints() disables it while loading, but the custom widget takes over from here.
     239        $originalSelect.prop('disabled', false);
     240
    241241        $originalSelect.hide();
    242242        $originalSelect.css('display', 'none', 'important');
     
    307307
    308308                    $li.on('click', function() {
    309                         console.log('BPAT: User selected point:', id, label); // דיבאג בקונסול
    310 
    311309                        $input.val(label);
    312310                        $list.hide();
    313311
     312                        // Ensure the option exists in the <select> before setting the value.
     313                        // Without a matching <option>, jQuery .val() is a no-op and $_POST arrives empty.
     314                        if ($originalSelect.find('option[value="' + id + '"]').length === 0) {
     315                            $originalSelect.append('<option value="' + id + '">' + safeText(label) + '</option>');
     316                        }
    314317                        $originalSelect.val(id).trigger('change');
    315318
     
    406409    // ==========================
    407410    function getSelectedBookpodMethod() {
     411      // 1. Classic checkout — custom BookPod method select
    408412      var $methodSelect = $('select[name="bpat_bookpod_shipping_method"]');
    409       if ($methodSelect.length) return $methodSelect.val();
    410 
    411       // Blocks radio
     413      if ($methodSelect.length) return $methodSelect.val() || "";
     414
     415      // 2. Classic checkout — WooCommerce shipping rate radio buttons
     416      var $classicRate = $('input[name^="shipping_method"]:checked');
     417      if ($classicRate.length) {
     418        var classicVal = String($classicRate.val() || "");
     419        if (classicVal.indexOf("bpat_bookpod") !== -1) {
     420          if (classicVal.indexOf("pickup_point") !== -1) return "pickup_point";
     421          if (classicVal.indexOf("home") !== -1) return "home";
     422        }
     423      }
     424
     425      // 3. Blocks checkout — WooCommerce shipping rate radio buttons
    412426      var $checkedRate = $('.wc-block-components-shipping-rates-control input[type="radio"]:checked');
    413427      if ($checkedRate.length) {
     
    501515    });
    502516
     517    // ✅ Classic checkout: update visibility immediately when the custom BookPod method select changes
     518    // Also sync to the WooCommerce shipping rate radio so WooCommerce recalculates the order total.
     519    $(document.body).on("change", 'select[name="bpat_bookpod_shipping_method"]', function () {
     520      syncBlockBookpodMethod();
     521      updateVisibility();
     522
     523      var selectedMethod = $(this).val();
     524      if (!selectedMethod) return;
     525
     526      var suffix = selectedMethod === "pickup_point" ? "pickup_point" : "home";
     527      var $rates = $('input[name^="shipping_method"]');
     528      var triggered = false;
     529
     530      $rates.each(function () {
     531        var rateVal = String($(this).val() || "");
     532        if (rateVal.indexOf("bpat_bookpod") !== -1 && rateVal.indexOf(suffix) !== -1) {
     533          if (!$(this).prop("checked")) {
     534            $(this).prop("checked", true);
     535            // Fire the native change event — WooCommerce's checkout.js already listens for
     536            // 'change' on input[name^="shipping_method"] and calls update_order_review() from there.
     537            $(this).trigger("change");
     538          }
     539          triggered = true;
     540          return false; // break
     541        }
     542      });
     543
     544      // If no BookPod WC rate radio exists (shipping zone not configured), trigger update_checkout
     545      // directly so woocommerce_checkout_update_order_review saves the method and
     546      // woocommerce_cart_calculate_fees adds the correct shipping cost as a fee.
     547      if (!triggered) {
     548        $(document.body).trigger("update_checkout");
     549      }
     550    });
     551
     552    // ✅ Classic checkout: when WooCommerce shipping rate radio changes, sync to custom select
     553    $(document.body).on("change", 'input[name^="shipping_method"]', function () {
     554      var val = String($(this).val() || "");
     555      var $sel = $('select[name="bpat_bookpod_shipping_method"]');
     556      if (!$sel.length) return;
     557      if (val.indexOf("bpat_bookpod") !== -1) {
     558        var method = val.indexOf("pickup_point") !== -1 ? "pickup_point" : "home";
     559        if ($sel.val() !== method) {
     560          $sel.val(method).trigger("change");
     561        }
     562      }
     563    });
     564
    503565    // ✅ Blocks re-render: re-init selectWoo only when pickup is selected AND select exists
    504566    if (typeof wp !== "undefined" && wp.data && wp.data.subscribe) {
  • bookpod-author-tools/trunk/bpat-order.php

    r3475827 r3485558  
    103103
    104104        $bookpod_id = get_post_meta( $id_to_check, '_bpat_bookpod_book_id', true );
     105
     106        // Fallback: if the variation has no BookPod ID, try the parent product
     107        if ( empty( $bookpod_id ) && $variation_id ) {
     108            $id_to_check = $product_id;
     109            $bookpod_id  = get_post_meta( $product_id, '_bpat_bookpod_book_id', true );
     110        }
     111
    105112        $is_bookpod = ( ! empty( $bookpod_id ) && bpat_should_send_to_bookpod( $id_to_check ) );
    106113
     
    146153
    147154        $bookpod_id = get_post_meta( $id_to_check_meta, '_bpat_bookpod_book_id', true );
    148         $is_set = get_post_meta( $id_to_check_meta, '_bpat_is_set', true ) === 'yes';
     155        $is_set     = get_post_meta( $id_to_check_meta, '_bpat_is_set', true ) === 'yes';
     156
     157        // Fallback: if the variation has no BookPod ID, try the parent product
     158        if ( empty( $bookpod_id ) && $variation_id ) {
     159            $id_to_check_meta = $product_id;
     160            $bookpod_id       = get_post_meta( $product_id, '_bpat_bookpod_book_id', true );
     161            $is_set           = get_post_meta( $product_id, '_bpat_is_set', true ) === 'yes';
     162        }
    149163
    150164        if ( ! $bookpod_id || ! bpat_should_send_to_bookpod( $id_to_check_meta ) ) {
     
    209223}
    210224
     225/**
     226 * Returns true when every BookPod item in the order is a virtual/digital product.
     227 * Used to skip physical shipping details for ebook-only orders.
     228 */
     229function bpat_bookpod_items_are_all_digital( WC_Order $order ) {
     230    $split = bpat_split_order_items_by_fulfillment( $order );
     231    if ( empty( $split['bookpod_items'] ) ) {
     232        return false;
     233    }
     234    foreach ( $split['bookpod_items'] as $item ) {
     235        if ( ! bpat_product_is_digital( $item->get_product_id(), $item->get_variation_id() ) ) {
     236            return false;
     237        }
     238    }
     239    return true;
     240}
     241
    211242function bpat_build_bookpod_shipping_details_from_wc_order( $order ) {
    212243
     
    230261    $reference = bpat_normalize_scalar_value( $order->get_order_number() );
    231262
     263    // Digital-only BookPod orders: no physical delivery.
     264    // Use shippingMethod 3 (self-pickup) with only basic contact details — no address fields needed.
     265    if ( bpat_bookpod_items_are_all_digital( $order ) ) {
     266        return array(
     267            'shippingMethod'    => 3,
     268            'shippingCompanyId' => 7,
     269            'name'              => $name,
     270            'phoneNumber'       => $phone,
     271            'email'             => $email,
     272            'reference_num1'    => $reference,
     273            'acceptAds'         => false,
     274            'acceptTerms'       => true,
     275        );
     276    }
    232277
    233278    if ( empty( $bookpod_method ) ) {
     
    861906*/
    862907function bpat_cart_item_is_bookpod( $cart_item ) {
    863     $product_id = isset( $cart_item['product_id'] ) ? (int) $cart_item['product_id'] : 0;
     908    $product_id   = isset( $cart_item['product_id'] )   ? (int) $cart_item['product_id']   : 0;
     909    $variation_id = isset( $cart_item['variation_id'] ) ? (int) $cart_item['variation_id'] : 0;
    864910
    865911    if ( ! $product_id ) {
     
    867913    }
    868914
    869     $bookpod_id = get_post_meta( $product_id, '_bpat_bookpod_book_id', true );
    870 
    871     return ( ! empty( $bookpod_id ) && bpat_should_send_to_bookpod( $product_id ) );
     915    $id_to_check = $variation_id ?: $product_id;
     916    $bookpod_id  = get_post_meta( $id_to_check, '_bpat_bookpod_book_id', true );
     917
     918    // Fallback: if the variation has no BookPod ID, try the parent product
     919    if ( empty( $bookpod_id ) && $variation_id ) {
     920        $id_to_check = $product_id;
     921        $bookpod_id  = get_post_meta( $product_id, '_bpat_bookpod_book_id', true );
     922    }
     923
     924    return ( ! empty( $bookpod_id ) && bpat_should_send_to_bookpod( $id_to_check ) );
    872925}
    873926
     
    927980
    928981/**
    929  * Returns true if the WooCommerce product is virtual/digital (no physical shipping).
     982 * Returns true if the product (or its specific variation) is virtual/digital (no physical shipping).
     983 * For variable products, checks the purchased variation rather than the parent.
    930984 */
    931 function bpat_product_is_digital( $product_id ) {
    932     $product = wc_get_product( (int) $product_id );
     985function bpat_product_is_digital( $product_id, $variation_id = 0 ) {
     986    $id_to_check = $variation_id ? $variation_id : $product_id;
     987    $product = wc_get_product( (int) $id_to_check );
    933988    return $product && $product->is_virtual();
    934989}
     
    9571012        }
    9581013
    959         if ( ! bpat_product_is_digital( $cart_item['product_id'] ) ) {
     1014        $variation_id = isset( $cart_item['variation_id'] ) ? (int) $cart_item['variation_id'] : 0;
     1015        if ( ! bpat_product_is_digital( $cart_item['product_id'], $variation_id ) ) {
    9601016            $has_physical = true;
    9611017        }
     
    9781034        'required' => true,
    9791035        'options' => array(
    980             '' => __( 'Select Shipping Method', 'bookpod-author-tools' ),
    981             'home' => __( 'Home Delivery', 'bookpod-author-tools' ),
    982             'pickup_point' => __( 'Pickup Point (Locker/Shop)', 'bookpod-author-tools' ),
     1036            ''             => __( 'Select Shipping Method', 'bookpod-author-tools' ),
     1037            'home'         => __( 'Home Delivery', 'bookpod-author-tools' ) . ' — ₪29',
     1038            'pickup_point' => __( 'Pickup Point (Locker/Shop)', 'bookpod-author-tools' ) . ' — ₪18',
    9831039        ),
    9841040        'class' => array( 'form-row-wide' ),
     
    9891045        'type' => 'text',
    9901046        'label' => __( 'Street Name', 'bookpod-author-tools' ),
     1047        // Must stay false so WooCommerce does not validate the hidden field when pickup_point is chosen.
     1048        // The actual required-when-home enforcement is in bpat_validate_bookpod_conditional_fields().
    9911049        'required' => false,
    9921050        'class' => array('form-row-wide', 'bookpod-conditional-home'),
     
    9971055        'type' => 'text',
    9981056        'label' => __( 'Building Number', 'bookpod-author-tools' ),
     1057        // Same as above — conditionally required via bpat_validate_bookpod_conditional_fields().
    9991058        'required' => false,
    10001059        'class' => array('form-row-wide', 'bookpod-conditional-home'),
     
    11341193        }
    11351194
     1195        $pickup_args = [ 'timeout' => 20 ];
     1196        if ( function_exists( 'bpat_require_credentials' ) ) {
     1197            $pickup_creds = bpat_require_credentials();
     1198            if ( ! is_wp_error( $pickup_creds ) ) {
     1199                $pickup_args['headers'] = [
     1200                    'x-user-id'      => $pickup_creds['userId'],
     1201                    'x-custom-token' => $pickup_creds['token'],
     1202                ];
     1203            }
     1204        }
     1205
    11361206        $response = wp_remote_get(
    11371207            'https://cloud-function-bookpod-festjdz7ga-ey.a.run.app/internal-api/pickup-points?company=7',
    1138             ['timeout' => 20]
     1208            $pickup_args
    11391209        );
    11401210
     
    11501220        }
    11511221
    1152         set_transient($cache_key, $points, 12 * HOUR_IN_SECONDS);
     1222        $http_code = (int) wp_remote_retrieve_response_code( $response );
     1223        // Only cache successful non-empty results; don't let a transient failure persist for 12 h
     1224        if ( $http_code >= 200 && $http_code < 300 && ! empty( $points ) ) {
     1225            set_transient($cache_key, $points, 12 * HOUR_IN_SECONDS);
     1226        }
    11531227    }
    11541228
     
    12511325}
    12521326
     1327/**
     1328 * Capture the BookPod shipping method from the live form data sent during every
     1329 * update_order_review AJAX call, and persist it in the WC session so that
     1330 * bpat_add_bookpod_shipping_fee() can read it during fee calculation.
     1331 */
     1332add_action( 'woocommerce_checkout_update_order_review', 'bpat_save_bookpod_method_to_session' );
     1333
     1334function bpat_save_bookpod_method_to_session( $post_data ) {
     1335    $data = array();
     1336    parse_str( $post_data, $data );
     1337
     1338    $method = isset( $data['bpat_bookpod_shipping_method'] )
     1339        ? sanitize_text_field( $data['bpat_bookpod_shipping_method'] )
     1340        : '';
     1341
     1342    if ( in_array( $method, array( 'home', 'pickup_point', '' ), true ) ) {
     1343        if ( WC()->session ) {
     1344            WC()->session->set( 'bpat_bookpod_shipping_method', $method );
     1345        }
     1346    }
     1347}
     1348
     1349/**
     1350 * Add the BookPod shipping cost as a WooCommerce fee when no BookPod WC shipping
     1351 * rate is covering it (i.e. the BookPod shipping method is not added to any zone).
     1352 * This ensures the price always appears in the order total regardless of zone setup.
     1353 */
     1354add_action( 'woocommerce_cart_calculate_fees', 'bpat_add_bookpod_shipping_fee' );
     1355
     1356function bpat_add_bookpod_shipping_fee() {
     1357    if ( ! is_checkout() || ! bpat_cart_has_only_physical_bookpod_items() ) {
     1358        return;
     1359    }
     1360
     1361    // If a BookPod WC shipping rate is already selected, it handles the cost — don't double-count.
     1362    $chosen = WC()->session ? (array) WC()->session->get( 'chosen_shipping_methods', array() ) : array();
     1363    foreach ( $chosen as $chosen_method ) {
     1364        if ( false !== strpos( (string) $chosen_method, 'bpat_bookpod' ) ) {
     1365            return;
     1366        }
     1367    }
     1368
     1369    $method = WC()->session ? (string) WC()->session->get( 'bpat_bookpod_shipping_method', '' ) : '';
     1370
     1371    if ( 'home' === $method ) {
     1372        WC()->cart->add_fee( __( 'Home Delivery', 'bookpod-author-tools' ), 29 );
     1373    } elseif ( 'pickup_point' === $method ) {
     1374        WC()->cart->add_fee( __( 'Pickup Point Delivery', 'bookpod-author-tools' ), 18 );
     1375    }
     1376}
     1377
    12531378add_action( 'woocommerce_checkout_process', 'bpat_validate_bookpod_conditional_fields' );
    12541379
     
    13041429        plugins_url( 'bpat-checkout-logic.js', __FILE__ ),
    13051430        $deps,
    1306         '1.6',
     1431        '2.0',
    13071432        true
    13081433    );
     
    14251550add_action( 'woocommerce_email_before_order_table', 'bpat_add_warehouse_email_warning', 10, 4 );
    14261551
    1427 function bpat_add_warehouse_email_warning( $order, $sent_to_admin, $plain_text, $email ) {
     1552function bpat_add_warehouse_email_warning( $order, $sent_to_admin, $plain_text, $email = null ) {
    14281553
    14291554    // Run only for "New Order" emails sent to Admin
    1430     if ( ! $sent_to_admin || 'new_order' !== $email->id ) {
     1555    if ( ! $sent_to_admin || null === $email || 'new_order' !== $email->id ) {
    14311556        return;
    14321557    }
     
    15331658add_action( 'woocommerce_email_before_order_table', 'bpat_flag_customer_email_start', 10, 4 );
    15341659
    1535 function bpat_flag_customer_email_start( $order, $sent_to_admin, $plain_text, $email ) {
     1660function bpat_flag_customer_email_start( $order, $sent_to_admin, $plain_text, $email = null ) {
    15361661
    15371662    if ( $sent_to_admin ) { return; }
     1663    if ( null === $email ) { return; }
    15381664
    15391665    $target_emails = array( 'customer_processing_order', 'customer_completed_order', 'customer_invoice' );
  • bookpod-author-tools/trunk/readme-he_IL.txt

    r3475827 r3485558  
    44Requires at least: 5.8
    55Tested up to: 6.9
    6 Stable tag: 2.1.5
     6Stable tag: 2.1.6
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    5656== יומן שינויים ==
    5757
    58 = 2.1.5 =
     58= 2.1.6 =
    5959* הוספת תמיכה בספרים דיגטליים בהודעות, מיילים וסל הקניות
    6060* תיקון ועדכון טופס יצירת ספר
  • bookpod-author-tools/trunk/readme.txt

    r3475827 r3485558  
    44Requires at least: 5.8
    55Tested up to: 6.9
    6 Stable tag: 2.1.5
     6Stable tag: 2.1.6
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    5656== Changelog ==
    5757
    58 = 2.1.5 =
     58= 2.1.6 =
    5959* Adding support for digital books shipping messages, emails and checkout
    6060* fixing and updating the create book fulfillment
Note: See TracChangeset for help on using the changeset viewer.