Changeset 3485558
- Timestamp:
- 03/18/2026 11:04:49 AM (2 weeks ago)
- Location:
- bookpod-author-tools
- Files:
-
- 7 edited
-
tags/2.1.5/readme.txt (modified) (1 diff)
-
trunk/bookpod-author-tools.php (modified) (1 diff)
-
trunk/bpat-book.php (modified) (7 diffs)
-
trunk/bpat-checkout-logic.js (modified) (5 diffs)
-
trunk/bpat-order.php (modified) (17 diffs)
-
trunk/readme-he_IL.txt (modified) (2 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
bookpod-author-tools/tags/2.1.5/readme.txt
r3475828 r3485558 56 56 == Changelog == 57 57 58 = 2.1. 5=58 = 2.1.6 = 59 59 * Adding support for digital books shipping messages, emails and checkout 60 60 * fixing and updating the create book fulfillment -
bookpod-author-tools/trunk/bookpod-author-tools.php
r3475827 r3485558 3 3 * Plugin Name: BookPod Author Tools 4 4 * Description: A plugin for managing books and orders through Bookpod. 5 * Version: 2.1. 55 * Version: 2.1.6 6 6 * Author: Rachel Stern 7 7 * Text Domain: bookpod-author-tools -
bookpod-author-tools/trunk/bpat-book.php
r3475827 r3485558 127 127 'cleanup' => true, 128 128 ]; 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 */ 146 function 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 ''; 129 254 } 130 255 … … 406 531 } 407 532 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 ) { 409 562 $derived_booktype = 'digital'; 410 563 } elseif ( $downloadable ) { … … 423 576 424 577 if ( $prefill['epubprice'] === '' ) { 425 $prefill['epubprice'] = $ regular_price;578 $prefill['epubprice'] = $var_digital_price !== '' ? $var_digital_price : $regular_price; 426 579 } 427 580 $prefill['price'] = ''; … … 430 583 431 584 if ( $prefill['price'] === '' ) { 432 $prefill['price'] = $ regular_price;585 $prefill['price'] = $var_physical_price !== '' ? $var_physical_price : $regular_price; 433 586 } 434 587 435 588 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 ); 441 592 } 442 593 } … … 451 602 452 603 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 ); 457 610 } 458 611 459 612 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 = []; 467 622 if ( empty( $prefill['categories'] ) ) { 468 623 $cat_ids = $product->get_category_ids(); … … 471 626 if ( $term && ! is_wp_error( $term ) ) { 472 627 $slug = urldecode( $term->slug ); 628 $mapped = false; 473 629 if ( isset( $category_map[ $slug ] ) ) { 474 630 $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; 475 635 } else { 476 636 $key = array_search( $slug, $category_map ); 477 637 if ( $key !== false ) { 478 638 $prefill['categories'][] = $category_map[ $key ]; 639 $mapped = true; 479 640 } 641 } 642 if ( ! $mapped ) { 643 $unmapped_category_names[] = $term->name; 480 644 } 481 645 } … … 485 649 486 650 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 ); 490 684 } 491 685 } -
bookpod-author-tools/trunk/bpat-checkout-logic.js
r3449910 r3485558 127 127 var $label = $('label[for="' + id + '"]'); 128 128 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(); 135 131 136 132 if ($label.find(".required").length === 0) { … … 239 235 } 240 236 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 241 241 $originalSelect.hide(); 242 242 $originalSelect.css('display', 'none', 'important'); … … 307 307 308 308 $li.on('click', function() { 309 console.log('BPAT: User selected point:', id, label); // דיבאג בקונסול310 311 309 $input.val(label); 312 310 $list.hide(); 313 311 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 } 314 317 $originalSelect.val(id).trigger('change'); 315 318 … … 406 409 // ========================== 407 410 function getSelectedBookpodMethod() { 411 // 1. Classic checkout — custom BookPod method select 408 412 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 412 426 var $checkedRate = $('.wc-block-components-shipping-rates-control input[type="radio"]:checked'); 413 427 if ($checkedRate.length) { … … 501 515 }); 502 516 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 503 565 // ✅ Blocks re-render: re-init selectWoo only when pickup is selected AND select exists 504 566 if (typeof wp !== "undefined" && wp.data && wp.data.subscribe) { -
bookpod-author-tools/trunk/bpat-order.php
r3475827 r3485558 103 103 104 104 $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 105 112 $is_bookpod = ( ! empty( $bookpod_id ) && bpat_should_send_to_bookpod( $id_to_check ) ); 106 113 … … 146 153 147 154 $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 } 149 163 150 164 if ( ! $bookpod_id || ! bpat_should_send_to_bookpod( $id_to_check_meta ) ) { … … 209 223 } 210 224 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 */ 229 function 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 211 242 function bpat_build_bookpod_shipping_details_from_wc_order( $order ) { 212 243 … … 230 261 $reference = bpat_normalize_scalar_value( $order->get_order_number() ); 231 262 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 } 232 277 233 278 if ( empty( $bookpod_method ) ) { … … 861 906 */ 862 907 function 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; 864 910 865 911 if ( ! $product_id ) { … … 867 913 } 868 914 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 ) ); 872 925 } 873 926 … … 927 980 928 981 /** 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. 930 984 */ 931 function bpat_product_is_digital( $product_id ) { 932 $product = wc_get_product( (int) $product_id ); 985 function 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 ); 933 988 return $product && $product->is_virtual(); 934 989 } … … 957 1012 } 958 1013 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 ) ) { 960 1016 $has_physical = true; 961 1017 } … … 978 1034 'required' => true, 979 1035 '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', 983 1039 ), 984 1040 'class' => array( 'form-row-wide' ), … … 989 1045 'type' => 'text', 990 1046 '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(). 991 1049 'required' => false, 992 1050 'class' => array('form-row-wide', 'bookpod-conditional-home'), … … 997 1055 'type' => 'text', 998 1056 'label' => __( 'Building Number', 'bookpod-author-tools' ), 1057 // Same as above — conditionally required via bpat_validate_bookpod_conditional_fields(). 999 1058 'required' => false, 1000 1059 'class' => array('form-row-wide', 'bookpod-conditional-home'), … … 1134 1193 } 1135 1194 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 1136 1206 $response = wp_remote_get( 1137 1207 'https://cloud-function-bookpod-festjdz7ga-ey.a.run.app/internal-api/pickup-points?company=7', 1138 ['timeout' => 20]1208 $pickup_args 1139 1209 ); 1140 1210 … … 1150 1220 } 1151 1221 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 } 1153 1227 } 1154 1228 … … 1251 1325 } 1252 1326 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 */ 1332 add_action( 'woocommerce_checkout_update_order_review', 'bpat_save_bookpod_method_to_session' ); 1333 1334 function 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 */ 1354 add_action( 'woocommerce_cart_calculate_fees', 'bpat_add_bookpod_shipping_fee' ); 1355 1356 function 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 1253 1378 add_action( 'woocommerce_checkout_process', 'bpat_validate_bookpod_conditional_fields' ); 1254 1379 … … 1304 1429 plugins_url( 'bpat-checkout-logic.js', __FILE__ ), 1305 1430 $deps, 1306 ' 1.6',1431 '2.0', 1307 1432 true 1308 1433 ); … … 1425 1550 add_action( 'woocommerce_email_before_order_table', 'bpat_add_warehouse_email_warning', 10, 4 ); 1426 1551 1427 function bpat_add_warehouse_email_warning( $order, $sent_to_admin, $plain_text, $email ) {1552 function bpat_add_warehouse_email_warning( $order, $sent_to_admin, $plain_text, $email = null ) { 1428 1553 1429 1554 // 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 ) { 1431 1556 return; 1432 1557 } … … 1533 1658 add_action( 'woocommerce_email_before_order_table', 'bpat_flag_customer_email_start', 10, 4 ); 1534 1659 1535 function bpat_flag_customer_email_start( $order, $sent_to_admin, $plain_text, $email ) {1660 function bpat_flag_customer_email_start( $order, $sent_to_admin, $plain_text, $email = null ) { 1536 1661 1537 1662 if ( $sent_to_admin ) { return; } 1663 if ( null === $email ) { return; } 1538 1664 1539 1665 $target_emails = array( 'customer_processing_order', 'customer_completed_order', 'customer_invoice' ); -
bookpod-author-tools/trunk/readme-he_IL.txt
r3475827 r3485558 4 4 Requires at least: 5.8 5 5 Tested up to: 6.9 6 Stable tag: 2.1. 56 Stable tag: 2.1.6 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 56 56 == יומן שינויים == 57 57 58 = 2.1. 5=58 = 2.1.6 = 59 59 * הוספת תמיכה בספרים דיגטליים בהודעות, מיילים וסל הקניות 60 60 * תיקון ועדכון טופס יצירת ספר -
bookpod-author-tools/trunk/readme.txt
r3475827 r3485558 4 4 Requires at least: 5.8 5 5 Tested up to: 6.9 6 Stable tag: 2.1. 56 Stable tag: 2.1.6 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 56 56 == Changelog == 57 57 58 = 2.1. 5=58 = 2.1.6 = 59 59 * Adding support for digital books shipping messages, emails and checkout 60 60 * fixing and updating the create book fulfillment
Note: See TracChangeset
for help on using the changeset viewer.