Fix WooCommerce fatal error
August 27, 2025 · AI Conversations
<script>
jQuery(document).ready(function ($) {
//Check for an entry cookie in the product's category
<?php
$product_cat = false;
$product_id = $product->get_id();
$product_cats = get_the_terms( $product->get_id(), 'product_cat' );
if ( $product_cats ) {
$product_cat = $product_cats[0]->term_id;
}
if ( $product_cat && !get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ): ?>
var categoryId = '<?php echo absint($product_cat); ?>';
if ( Cookies.get('entry_category-' + categoryId) || ( document.cookie.indexOf('wp_user_logged_in') !== -1 && Cookies.get('logged_in_entry_category-' + categoryId) ) ) {
$('.single_add_to_cart_button').text('Select your product');
}
<?php endif; ?>
//Disable enter-to-submit on quantity input (stops adding to cart without going through consultation first)
$('input[name=quantity]').on('keypress', function(){
if (event.which == '13') {
event.preventDefault();
return false;
}
});
//Custom add to cart button that requires consultation form
$('#consult-first').on( 'click', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
//check that the button isn't disabled, and that we have a variation selected (this would be false in a re/loading state which sometimes happens on longer variation product loads)
if ( !$(this).hasClass("disabled") && $('input[name="variation_id"]').val() > 0 ) {
$(this).parents('form').submit();
}
//if someone clicks a disabled/loading button don't do anything
return false;
});
if ( $('#form_id_override').length > 0 ) {
$('.single_add_to_cart_button').on( 'click', function(event){
event.preventDefault();
event.stopImmediatePropagation();
$(this).attr('name', '');
$(this).parents('form').submit();
});
}
});
</script>
<script>
jQuery(document).ready(function ($) {
//Make sure we scroll to the top for the consultation entry display
setTimeout(function(){
if ( $('.returning').is(':visible') ) {
window.onunload = function(){ window.scrollTo(0,0); }
}
},1);
$('.reveal-consultation').on('click', function(event){
event.preventDefault();
$('.consultation-container').fadeIn();
$('.changed-condition').fadeIn();
$('.condition-choice').slideUp();
return false;
});
//validate Gform
var form = $('.gform_wrapper > form');
$('.entry-content').on("click", "input[type=radio]", function (e) {
if ( $('#product_id').val() != undefined ) {
//If any incorrect choices are visible it means the form is invalid so disable the submit button
if ( $('.choice_incorrect').is(':visible') == true ) {
$('.gform_button').addClass("disabled");
} else {
$('.gform_button').removeClass("disabled");
}
}
});
/**
* Show error message
* @param {string} msg
* @returns {void}
*/
function showError(msg) {
$('.validation_error').empty();
$('.validation_error').css({borderTop: "none", borderBottom: "none", padding: 0});
$('.gform_heading').append('<div class="validation_error">' + msg + '</div>');
}
<?php
//Set up a var that allows us to handle things differently if this is a pingdom bot
$is_pingdom = isset( $_SERVER['HTTP_USER_AGENT'] ) ? stripos( $_SERVER['HTTP_USER_AGENT'], 'pingdom' ) : false;
$variation_id = isset( $_POST['variation_id'] ) ? intval($_POST['variation_id']) : ''; ?>
//Form submission
form.find('input[type=submit]').click(function (e) {
e.preventDefault();
if (!formFilled()) {
showError('Please fill all (*) required fields.')
$('html, body').animate({scrollTop: form.offset().top - 100}, 'slow');
} else if (!hasIncorrects()) {
<?php
//Redirect straight to add-to-cart without generating an entry if this is pingdom
if ( $is_pingdom !== false) { ?>
window.location = "<?php echo site_url("/cart/?add-to-cart=" . $variation_id ); ?>";
<?php } else {
//If a GET request, replace the history with GET params so we can keep our nice clean POST URL
if ( !( isset($_GET['cat']) || isset($_GET['product_id']) ) || isset( $_POST['form_id_override'] ) ) {
$url = add_query_arg( pmm_get_product_data('select_treatment') ); ?>
history.replaceState(null, "Consultation Form: Submitted", "<?php echo esc_url_raw($url); ?>");
<?php } ?>
$('.gform_footer').addClass('loading');
form.submit();
<?php } ?>
} else {
$('.gform_heading .validation_error').hide();
}
});
//check each radio has checked
//check each radio has checked
function formFilled() {
let isValid = true;
form.find('.gfield_contains_required:visible').each(function () {
const field = $(this);
// Skip hidden fields
if (!field.is(':visible')) return;
// Get the actual input element(s)
const input = field.find('input, select, textarea').first();
// If it's radio or checkbox, check if any are selected
if (input.is('[type=radio]') || input.is('[type=checkbox]')) {
if (field.find('input:checked').length === 0) {
isValid = false;
return false; // Break out of .each loop
}
} else {
// For text, textarea, select — check value
if (!input.val() || input.val().trim() === '') {
isValid = false;
return false;
}
}
});
return isValid;
}
//Ensure that when a label is clicked, the associated checkbox is checked
//e.g. if a user selects text inside the label
$('.ginput_container_choice label').on('click', function(){
var radioEl = $(this).siblings('input[type="radio"]');
var isChecked = radioEl.prop('checked');
if (!isChecked) {
radioEl.prop('checked', true);
}
});
/**
* Exclusions logic
*/
var availableProductsEl = $('#available_products');
var isOverride = $('#add-to-cart').length > 0;
if ( !isOverride ) {
//Store a master copy of the available products list, so we can restore exclusions when customers switch choices
var availableProductsMaster = availableProductsEl.length && $('#available_products').val().split(',');
var availableProducts = availableProductsEl.length && $('#available_products').val().split(',');
//Run a full check and applying of exclusions of all selected options on every new selection
$('.gfield_choice label').on('click', function() {
//Collect (unique) exclusions from all checked options
var exclusions = [];
$('.gfield_choice input:checked').each(function() {
var choiceExclusions = $(this).parent().data('exclusions');
if ( choiceExclusions ) {
for (i = 0; i < choiceExclusions.length; ++i ) {
if ( exclusions.indexOf(choiceExclusions[i]) === -1 ) {
exclusions.push(choiceExclusions[i]);
}
}
}
})
//Search for the exclusions in the availableProductsMaster and return as a new array
availableProducts = $.grep(availableProductsMaster, function(value) {
return $.inArray(value, exclusions) < 0;
});
//Update the hidden input element
availableProductsEl.val(availableProducts.join(','));
//Handle no products being left in the available list
//Disable the submit button and display the error modal
if (availableProducts.length === 0) {
var noProductsMessage = '<div class="choice_incorrect choice_no_products">Sorry based on your answers there are no suitable treatments we recommend for you. Please contact us on 0208 894 6080 to discuss this.</div>';
if ( $(this).parents('.gfield').find('.choice_no_products').length === 0 ) {
$(this).parents('.gfield').children('.choice_left').append(noProductsMessage);
} else {
$(this).parents('.gfield').find('.choice_no_products').show();
}
$('.gform_button').addClass("disabled");
} else {
$('.choice_no_products').hide();
$('.gform_button').removeClass("disabled");
}
});
}
//check has a incorrect answer
function hasIncorrects() {
var goTo = false;
$.each(form.find('.choice_incorrect:visible'), function (i) {
//figure out if we need to offset the scrollTo position (for non-mobile)
var headerHeight = $('#et_mobile_nav_menu').is(':visible') ? 0 : $('#et-main-area').offset().top;
//Set target element based on element height vs window height (stops very long questions appearing wrongly on mobile)
if ( $(this).parent().parent().outerHeight() > $(window).outerHeight() ) {
goTo = $(this);
headerHeight = $(window).outerHeight() / 4;
} else {
goTo = $(this).parent().parent();
}
$('html, body').animate({
scrollTop: $(goTo).offset().top - headerHeight
}, 1000);
return false;
});
return goTo ? true : false
}
<?php if ( $is_pingdom === false ) { ?>
(function ($) {
// Rebind safely (GF can re-render DOM)
$(document)
.off('click.gfAutoScroll', '.gfield_choice:not(:last) label')
.on('click.gfAutoScroll', '.gfield_choice:not(:last) label', function () {
// Let GF update conditional logic first
$(this).siblings('input').trigger('click');
var $sourceBlockEl = $(this).closest('.gfield');
var $form = $sourceBlockEl.closest('form');
if (!$form.length) $form = $(document); // fallback
// next visible field (skip hidden)
var $nextBlockEl = $sourceBlockEl.nextAll('.gfield:visible').first();
function computeHeaderHeight() {
return $('#et_mobile_nav_menu').is(':visible') ? 0 : ($('#et-main-area').offset() ? $('#et-main-area').offset().top : 0);
}
var headerHeight = computeHeaderHeight();
// Only treat as "correct" if not flagged incorrect AND gating allows
var isCorrectClick =
((($(this).attr('onclick') || '').indexOf('incorrect') === -1)) &&
((typeof isOverride !== 'undefined' && isOverride) ||
(typeof availableProducts !== 'undefined' && availableProducts.length > 0));
// --- helpers (scoped to this form) ---
function isIncorrect($f) {
return $f.hasClass('gfield_error') ||
$f.hasClass('gquiz-field-incorrect') ||
$f.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').length > 0;
}
function isRequired($f) {
return $f.hasClass('gfield_contains_required') ||
$f.find('[aria-required="true"], [required]').length > 0;
}
function hasInputContainer($f) {
return $f.find('.ginput_container').length > 0;
}
function isAnswered($f) {
if (!hasInputContainer($f)) return true; // non-input sections treated as answered/skip
// choices
if ($f.find('.ginput_container_choice, .ginput_container_radio, .ginput_container_checkbox').length) {
var anyChecked = $f.find('input:radio:checked, input:checkbox:checked').length > 0;
if (anyChecked) return true;
}
// text-ish
var textFilled = $f.find('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea')
.filter(function () {
var v = $.trim($(this).val());
return v !== '' && v.toLowerCase() !== 'other'; // ignore placeholder-like "Other"
}).length > 0;
var selectFilled = $f.find('select').filter(function () {
var v = $(this).val();
return v !== '' && v !== null;
}).length > 0;
var dateTimeFilled = $f.find('input[type="date"], input[type="time"]')
.filter(function () { return $.trim($(this).val()) !== ''; }).length > 0;
return textFilled || selectFilled || dateTimeFilled;
}
// --- end helpers ---
var $scrollTo = $(); // empty jQuery
if (isCorrectClick) {
var $visibleFields = $form.find('.gfield:visible');
var idx = $visibleFields.index($sourceBlockEl);
// 1) UP: only REQUIRED fields (incorrect OR unanswered)
for (var i = idx - 1; i >= 0; i--) {
var $f = $visibleFields.eq(i);
if (!isRequired($f)) continue;
if (isIncorrect($f) || !isAnswered($f)) { $scrollTo = $f; break; }
}
// For tall targets, aim at a meaningful sub-element
if ($scrollTo.length && $scrollTo.outerHeight() > $(window).outerHeight()) {
var $sub = $scrollTo.find('.gquiz-incorrect-choice:visible, .choice_incorrect:visible').first();
if (!$sub.length) $sub = $scrollTo.find('input, select, textarea').filter(':visible').first();
if ($sub.length) $scrollTo = $sub;
headerHeight = $(window).outerHeight() / 4;
}
// 2) DOWN: include OPTIONAL questions again (unanswered below current)
if (!$scrollTo.length) {
// a) immediate next visible block if unanswered (required or optional)
if ($nextBlockEl.length && !isAnswered($nextBlockEl)) {
$scrollTo = $nextBlockEl;
} else {
// b) first unanswered field BELOW current (required OR optional)
for (var j = idx + 1; j < $visibleFields.length; j++) {
var $f2 = $visibleFields.eq(j);
if (!hasInputContainer($f2)) continue;
if (!isAnswered($f2)) { $scrollTo = $f2; break; }
}
// c) if none remain, go to the submit/footer
if (!$scrollTo.length) {
$scrollTo = $form.find('.gform_footer:visible').first();
if (!$scrollTo.length) $scrollTo = $('.gform_footer:visible').first(); // global fallback
headerHeight = computeHeaderHeight() + ($(window).outerHeight() / 4);
}
}
}
}
// No scroll on incorrect clicks
if ($scrollTo && $scrollTo.length) {
var elOffset = $scrollTo.is(':visible')
? $scrollTo.offset().top
: ($sourceBlockEl.offset().top + $sourceBlockEl.outerHeight());
$('html, body').animate({ scrollTop: elOffset - headerHeight }, 1000);
}
});
})(jQuery);
<?php } ?>
});
</script>
<script>
jQuery(document).ready(function($){
//Proceed to checkout bar - sticky on scroll
if ( $('.treatment-continue').length > 0 ) {
var $treatmentContinue = $('.treatment-continue');
var elPos = $treatmentContinue.offset();
$(window).on('scroll', function() {
if ( $(window).scrollTop() > elPos.top ) {
$treatmentContinue.addClass('sticky');
} else {
$treatmentContinue.removeClass('sticky');
}
}).trigger('scroll');
};
//Disable input on selected products
$('.selected select, .selected .qty').prop('disabled','disabled');
function selectTreatment(e) {
var $selectButton = $(e.target);
//check that the button isn't disabled, and that we have a variation selected (this would be false in a re/loading state which sometimes happens on longer variation product loads)
if ( !$selectButton.hasClass("disabled") && $selectButton.siblings('input[name="variation_id"]').val() > 0 ) {
var form = $selectButton.parents('form');
form.submit();
$selectButton.removeClass('added');
$selectButton.addClass('loading');
}
//if someone clicks a disabled/loading button don't do anything
return false;
}
function deselectTreatment( $removeButton ) {
var cartItemKey = $removeButton.data('remove_item');
$removeButton.removeClass('can-update').removeClass('deselect').removeClass('added').addClass('loading');
$.post( '/?wc-ajax=remove_from_cart', 'cart_item_key=' + cartItemKey, function(response){
if ( ! response || ! response.fragments ) {
window.location.reload(true);
return;
}
//Reset parent element
$removeButton.parents('.product').removeClass('selected');
$removeButton.parents('.product').find('select, .qty').prop('disabled', false);
//Reset button
$removeButton.removeClass('loading').addClass('added').removeClass('deselect');
$removeButton.text('Removed');
//Hide continue section if we've deselected the last item
if ( $('.deselect').length === 0 ) {
$('.treatment-continue').slideUp();
}
setTimeout(function(){
$removeButton.text('Select').removeClass('added');
$( document.body ).trigger( 'removed_from_cart', [ response.fragments, response.cart_hash, $removeButton ] );
$removeButton.on('click', function(e){ selectTreatment(e) });
$('.select_treatment.deselect').one( 'click', function(){
deselectTreatment( $(this) );
});
}, 1500);
} );
return false;
}
$('.select_treatment').on('click', function(event){
event.preventDefault();
event.stopImmediatePropagation();
if ( $(this).hasClass('deselect') ) {
deselectTreatment( $(this) );
} else {
//Custom AJAX submission of the "Select" button
selectTreatment(event);
}
});
$('.view-more').on( 'click', function(){
$(this).toggleClass('opened');
$(this).siblings('.woocommerce-product-details__short-description').slideToggle();
});
});
</script>
Consultation Form
Select a Consultation Form…
<script>
jQuery(document).ready(function($){
$('body').on('show_variation', function(){
$(this).find('button.single_add_to_cart_button').removeClass('disabled wc-variation-is-unavailable');
});
});
</script>
<script>
jQuery("#forceResumbission").on("change", function(){
if ( jQuery(this).is(":checked") ) {
form.forceResumbission = true;
} else {
form.forceResumbission = null;
}
});
</script>
';
return $button_pre . $button;
}
//Set the force update time using PHP functions for consistency
add_filter( 'gform_form_update_meta', 'pmm_force_update_meta_save' );
function pmm_force_update_meta_save( $meta ) {
if ( isset( $meta['forceResumbission'] ) && $meta['forceResumbission'] == true ) {
$now = new DateTime();
$meta['forceResumbission'] = $now->format('U');
}
return $meta;
}
/**
* Check if an entry needs to be submitted again
* This would be either due to being from the previous plugin, or because of the form being updated with "force resubmission" ticked
*/
function pmm_entry_needs_resubmitting( $entry_id, $form_id, $product_id ) {
$force_resubmission = false;
//Check for the available_products meta – if it's not there this must be an entry from the previous plugin
//Also check if a consultation product level override is in place, then we skip this check
if ( !gform_get_meta( $entry_id, 'available_products' ) && !get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ) {
//echo 'DEBUG: old form';
$force_resubmission = true;
} else {
//Check form's meta for a force resubmit
$form = GFAPI::get_form($form_id);
$entry = GFAPI::get_entry($entry_id);
if ( $form && !is_wp_error( $entry ) ) {
//If the entry's form id doesn't match the passed form id, it's automatically invalid
if ( absint($form_id) !== absint($entry['form_id']) ) {
//echo 'DEBUG: entry's form id does not match passed form id';
return true;
}
if ( isset($form['forceResumbission']) ) {
$force_time = DateTime::createFromFormat( 'U', $form['forceResumbission'] );
$entry_time = DateTime::createFromFormat( 'Y-m-d H:i:s', $entry['date_created'] );
//If forceResubmission date is greater (newer) than the entry submission time we need to force a new entry
if ( $force_time > $entry_time ) {
//echo 'DEBUG: form updated with force resubmit entry (ID:' . $entry_id . ') ' . $entry_time->format('Y-m-d H:i:s') . ' vs. ' . $force_time->format('Y-m-d H:i:s');
$force_resubmission = true;
}
}
}
}
return $force_resubmission;
}
undefined method get_id()
Below are **safe guards** for the most likely hotspots in your file. Drop these **as-is replacements** in your plugin (they’re minimal, no behavior change except avoiding fatals).
—
### 1) Reorder button on the Order details page
**Function:** `pmm_order_item_reorder_button`
function pmm_order_item_reorder_button( $item, $order ) {
$entry_id = $item->get_meta( 'entry_id', true );
$product = $item->get_product();
// Guard: product can be false (deleted/trashed)
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
return;
}
$product_id = $product->is_type('variation') ? $product->get_parent_id() : $product->get_id();
if ( $entry_id && $product_id && pmm_product_reordering_allowed( $product_id ) ): ?>
<td class="reorder-button">
<form action="/consultation-form/" method="post">
<input type="hidden" name="reorder_item_id" value="<?php echo absint($item->get_id()); ?>">
<input type="hidden" name="product_id" value="<?php echo absint($product_id);?>">
<input type="hidden" name="variation_id" value="<?php echo absint($product->get_id());?>">
<input type="hidden" name="quantity" value="<?php echo absint($item->get_quantity()); ?>">
<?php if ( $override_id = get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ): ?>
<input type="hidden" name="form_id_override" value="<?php echo absint($override_id); ?>">
<input type="hidden" name="add-to-cart-id" value="<?php echo absint($product->get_id()); ?>">
<?php endif;
if ( !pmm_product_consultation_required( $product ) ): ?>
<input type="hidden" name="entry_id" value="<?php echo absint($entry_id); ?>">
<input type="hidden" name="add-to-cart" value="<?php echo absint($product->get_id()); ?>">
<?php endif; ?>
<button type="submit" class="button">Reorder</button>
</form>
</td>
<?php endif;
}
—
### 2) Reorder column on My Account → Orders list
**Hooks:** `pmm_reorder_order_column`
function pmm_reorder_order_column( $order ) {
$order_items = $order->get_items( apply_filters( 'woocommerce_purchase_order_item_types', 'line_item' ) );
foreach ( $order_items as $item ) {
$entry_id = $item->get_meta( 'entry_id', true );
$product = $item->get_product();
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
continue;
}
$product_id = $product->is_type('variation') ? $product->get_parent_id() : $product->get_id();
if ( $entry_id && $product && pmm_product_reordering_allowed( $product_id ) ) { ?>
<a href="<?php echo esc_url( $order->get_view_order_url() ); ?>" class="button">Reorder</a>
<?php break;
}
}
}
—
### 3) “Return to Select Treatments” link in cart
**Function:** `pmm_cart_return_to_select_treatments`
function pmm_cart_return_to_select_treatments( $cart_item ) {
if ( empty( $cart_item['entry_id'] ) ) {
return;
}
$entry_id = absint( $cart_item['entry_id'] );
$entry = GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) {
return;
}
$product = isset( $cart_item['data'] ) ? $cart_item['data'] : false;
// Guard: ensure WC_Product
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
return;
}
$product_id = $cart_item['product_id'];
$variation_id = $product->get_id();
// Skip if product has individual form override
if ( get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ) {
return;
}
$product_cats = get_the_terms( $product_id, 'product_cat' );
$product_cat = $product_cats ? $product_cats[0] : false;
$query_args = array(
'product_id' => absint( $product_id ),
'variation_id' => absint( $variation_id ),
'entry_id' => absint( $entry_id ),
);
if ( isset( $cart_item['reorder'] ) ) {
$query_args['reorder'] = absint( $cart_item['reorder'] );
}
$url = add_query_arg( $query_args, home_url('/consultation-form/') );
if ( $url && $product_cat ) {
printf(
'<p>Looking for another treatment?<br>You can see other %s treatments you are eligible for <a href="%s">here</a>.</p>',
esc_html( $product_cat->name ),
esc_url_raw( $url )
);
}
}
—
### 4) Select-treatment product loop output
**Hook:** `pmm_select_treatment_variations_output`
function pmm_select_treatment_variations_output() {
if ( ! pmm_is_select_treatment_page() ) return;
global $product;
// Guard: make sure it’s a WC_Product
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) return;
if ( $product->is_type('variable') ) {
woocommerce_variable_add_to_cart();
} else {
woocommerce_simple_add_to_cart();
}
}
—
### 5) Product-page footer JS bootstrap
**Function:** `pmm_single_product_js`
function pmm_single_product_js() {
if ( ! is_product() ) return;
global $product;
// Guard: ensure WC_Product
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) return;
?>
<!-- existing JS output unchanged -->
<?php
}
—
### 6) Stock check helper using global $product
**Function:** `pmm_check_for_instock`
function pmm_check_for_instock() {
global $product;
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
return;
}
//Exclude products with consultation overrides
$has_override = $product->get_meta( '_pmm_has_override', true );
if ( pmm_is_select_treatment_page() || $has_override ) {
return;
}
$category_ids = $product->get_category_ids();
if ( $category_ids ) {
$category_id = $category_ids[0];
if ( pmm_category_has_instock_consult_products( $category_id ) ) { ?>
<script>
jQuery(document).ready(function($){
$('body').on('show_variation', function(){
$(this).find('button.single_add_to_cart_button').removeClass('disabled wc-variation-is-unavailable');
});
});
</script>
<?php }
}
}
—
### Why this fixes it
– **`$item->get_product()` can be `false`** when the product doesn’t exist anymore.
– **`global $product`** can be unset or another type if the loop context isn’t what you expect.
– Adding **`is_a( $obj, 'WC_Product' )`** guards before calling **`->get_id()`** (or other product methods) prevents the fatal and degrades gracefully.
—
### Quick grep to find any other risky calls
Search your file for these and wrap similarly if needed:
– `->get_id(`
– `->get_parent_id(`
– `->get_children(`
– any place you call **WooCommerce product methods** on a variable that might not be a `WC_Product`.
If you still see the fatal after these patches, share the **exact stack trace line number**—I’ll pinpoint that spot next.
—
**1) `pmm_order_item_reorder_button`**
function pmm_order_item_reorder_button( $item, $order ) {
$entry_id = $item->get_meta( 'entry_id', true );
$product = $item->get_product();
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
return;
}
$product_id = $product->is_type('variation') ? $product->get_parent_id() : $product->get_id();
if ( $entry_id && $product_id && pmm_product_reordering_allowed( $product_id ) ): ?>
<td class="reorder-button">
<form action="/consultation-form/" method="post">
<input type="hidden" name="reorder_item_id" value="<?php echo absint($item->get_id()); ?>">
<input type="hidden" name="product_id" value="<?php echo absint($product_id);?>">
<input type="hidden" name="variation_id" value="<?php echo absint($product->get_id());?>">
<input type="hidden" name="quantity" value="<?php echo absint($item->get_quantity()); ?>">
<?php if ( $override_id = get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ): ?>
<input type="hidden" name="form_id_override" value="<?php echo absint($override_id); ?>">
<input type="hidden" name="add-to-cart-id" value="<?php echo absint($product->get_id()); ?>">
<?php endif;
if ( !pmm_product_consultation_required( $product ) ): ?>
<input type="hidden" name="entry_id" value="<?php echo absint($entry_id); ?>">
<input type="hidden" name="add-to-cart" value="<?php echo absint($product->get_id()); ?>">
<?php endif; ?>
<button type="submit" class="button">Reorder</button>
</form>
</td>
<?php endif;
}
—
**2) `pmm_reorder_order_column`**
function pmm_reorder_order_column( $order ) {
$order_items = $order->get_items( apply_filters( 'woocommerce_purchase_order_item_types', 'line_item' ) );
foreach ( $order_items as $item ) {
$entry_id = $item->get_meta( 'entry_id', true );
$product = $item->get_product();
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
continue;
}
$product_id = $product->is_type('variation') ? $product->get_parent_id() : $product->get_id();
if ( $entry_id && pmm_product_reordering_allowed( $product_id ) ) { ?>
<a href="<?php echo esc_url( $order->get_view_order_url() ); ?>" class="button">Reorder</a>
<?php break;
}
}
}
—
**3) `pmm_cart_return_to_select_treatments`**
function pmm_cart_return_to_select_treatments( $cart_item ) {
if ( empty( $cart_item['entry_id'] ) ) return;
$entry_id = absint( $cart_item['entry_id'] );
$entry = GFAPI::get_entry( $entry_id );
if ( is_wp_error( $entry ) ) return;
$product = isset( $cart_item['data'] ) ? $cart_item['data'] : false;
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) return;
$product_id = $cart_item['product_id'];
$variation_id = $product->get_id();
if ( get_post_meta( $product_id, '_pmm_consultation_form_id', true ) ) return;
$product_cats = get_the_terms( $product_id, 'product_cat' );
$product_cat = $product_cats ? $product_cats[0] : false;
$query_args = array(
'product_id' => absint( $product_id ),
'variation_id' => absint( $variation_id ),
'entry_id' => absint( $entry_id ),
);
if ( isset( $cart_item['reorder'] ) ) {
$query_args['reorder'] = absint( $cart_item['reorder'] );
}
$url = add_query_arg( $query_args, home_url('/consultation-form/') );
if ( $url && $product_cat ) {
printf(
'<p>Looking for another treatment?<br>You can see other %s treatments you are eligible for <a href="%s">here</a>.</p>',
esc_html( $product_cat->name ),
esc_url_raw( $url )
);
}
}
—
**4) `pmm_select_treatment_variations_output`**
function pmm_select_treatment_variations_output() {
if ( ! pmm_is_select_treatment_page() ) return;
global $product;
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) return;
if ( $product->is_type('variable') ) {
woocommerce_variable_add_to_cart();
} else {
woocommerce_simple_add_to_cart();
}
}
—
**5) `pmm_single_product_js` (just the guards at the top)**
function pmm_single_product_js() {
if ( !is_product() ) return;
global $product;
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) return;
?>
<!-- existing JS output unchanged -->
<?php
}
—
**6) `pmm_check_for_instock` (guard at top)**
function pmm_check_for_instock() {
global $product;
if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
return;
}
$has_override = $product->get_meta( '_pmm_has_override', true );
if ( pmm_is_select_treatment_page() || $has_override ) return;
$category_ids = $product->get_category_ids();
if ( $category_ids ) {
$category_id = $category_ids[0];
if ( pmm_category_has_instock_consult_products( $category_id ) ) { ?>
<script>
jQuery(document).ready(function($){
$('body').on('show_variation', function(){
$(this).find('button.single_add_to_cart_button').removeClass('disabled wc-variation-is-unavailable');
});
});
</script>
<?php }
}
}