My code:
add_action( 'woocommerce_before_checkout_form', 'fruit_field' );
function fruit_field( $checkout ) {
woocommerce_form_field( 'fruit', array(
'type' => 'select',
'required' => true,
'options' => array(
'apple' => __('Apple'),
'banana' => __('Banana'),
'watermelon' => __('Watermelon'),
'other' => __('Other'),
),
'class' => array('my-class'),
'label' => __('Best fruit?'),
), $checkout->get_value( 'fruit' ));
}
And the validation:
add_action('woocommerce_checkout_process', 'process_checkout');
function process_checkout() {
if ($_POST['fruit'] === null) {
wc_add_notice( __( 'No fruits?' ), 'error' );
}
}
After submitting the form, it always displays my custom error "No fruits?", no matter what was selected. Is $_POST['fruit'] somehow not available in the process_checkout function?
You can not use custom checkout fields in woocommerce_before_checkout_form hook as your field is outside the checkout form, and it is not posted on submit.
Instead you should use woocommerce_checkout_before_customer_details action hook instead:
add_action( 'woocommerce_checkout_before_customer_details', 'fruit_custom_checkout_field' );
function fruit_custom_checkout_field() {
woocommerce_form_field( '_fruit', array(
'type' => 'select',
'label' => __('Best fruit?'),
'class' => array('my-fruit'),
'required' => true,
'options' => array(
'' => __('Chose a fruit'),
'Apple' => __('Apple'),
'Banana' => __('Banana'),
'Watermelon' => __('Watermelon'),
'Other' => __('Other'),
),
), WC()->checkout->get_value('_fruit') );
}
add_action('woocommerce_checkout_process', 'process_fruit_custom_checkout_field');
function process_fruit_custom_checkout_field() {
if (isset($_POST['_fruit']) && empty($_POST['_fruit']) ) {
wc_add_notice( __( 'please choose a "fruits"' ), 'error' );
}
}
// Save the custom checkout field in the order meta
add_action( 'woocommerce_checkout_create_order', 'save_fruit_custom_field_as_meta_data', 10, 2 );
function save_fruit_custom_field_as_meta_data( $order, $data ) {
if (isset($_POST['_fruit']) && ! empty($_POST['_fruit']) ) {
$order->update_meta_data('_custom_field', esc_attr( $_POST['_fruit'] ) );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Related
I've just added a custom product type option to my WC admin products page:
add_filter( 'product_type_options', [ $this, 'filter_product_type_options' ], 99, 1 );
public function filter_product_type_options( array $product_type_options ): array {
$product_type_options['batches'] = [
'id' => '_batches',
'wrapper_class' => 'show_if_simple show_if_variable',
'label' => esc_html__( 'Batches', 'woo-batches' ),
'description' => esc_html__( 'Product is sold from batches.', 'woo-batches' ),
'default' => 'no',
];
return $product_type_options;
}
I've also added a custom product data tab which I only want to show in case the option is checked:
add_filter( 'woocommerce_product_data_tabs', [ $this, 'filter_woocommerce_product_data_tabs' ] );
public function filter_woocommerce_product_data_tabs( array $tabs ): array {
$tabs['woo_batches'] = [
'label' => esc_html__( 'Batches', 'woo-batches' ),
'target' => 'woo_batches',
'class' => [ 'show_if_simple', 'show_if_variable', 'show_if_batches' ],
'priority' => 25
];
return $tabs;
}
But when I check/uncheck my option, the tab does absolutely nothing. Do I need my own hiding JS function here or am I missing something? I normally thought that I can show/hide everything with
show_if_xxx
hide_if_xxx
You must indeed provide a piece of jQuery yourself. How this is applied to the other check boxes (default) can be found in js/admin/meta-boxes-product.js (line 122...)
Note: I have also made some minor changes to your existing code, among which certain classes
So you get:
// Add a checkbox as Woocommerce admin product option
function filter_product_type_options( $product_type_options ) {
$product_type_options['batches'] = array(
'id' => '_batches',
'wrapper_class' => 'show_if_simple show_if_variable',
'label' => __( 'Batches', 'woo-batches' ),
'description' => __( 'Product is sold from batches.', 'woo-batches' ),
'default' => 'no',
);
return $product_type_options;
}
add_filter( 'product_type_options', 'filter_product_type_options', 10, 1 );
// Add custom product setting tab.
function filter_woocommerce_product_data_tabs( $default_tabs ) {
$default_tabs['woo_batches'] = array(
'label' => __( 'Batches', 'woo-batches' ),
'target' => 'woo_batches',
'class' => array( 'hide_if_simple', 'hide_if_variable', 'show_if_batches' ),
'priority' => 25
);
return $default_tabs;
}
add_filter( 'woocommerce_product_data_tabs', 'filter_woocommerce_product_data_tabs', 10, 1 );
// Prints scripts or data before the default footer scripts.
// This hook is for admin only and can’t be used to add anything on the front end.
function action_admin_footer() {
?>
<script>
jQuery(document).ready(function($) {
$( 'input#_batches' ).change( function() {
var is_batches = $( 'input#_batches:checked' ).length;
// Show rules.
if ( is_batches ) {
$( '.show_if_batches' ).show();
}
});
});
</script>
<?php
}
add_action( 'admin_footer', 'action_admin_footer' );
I have three custom checkout fields, and people have to check at least one for the order to go through.
This is only needed for 1 product.
So, I loop through the cart items to check if the product is in the cart, then add the fields. This part works fine:
add_action( 'woocommerce_before_order_notes', 'mmm_add_custom_checkout_field' );
function mmm_add_custom_checkout_field( $checkout ) {
$product_id = 214884;
$in_cart = false;
foreach( WC()->cart->get_cart() as $cart_item ) {
$product_in_cart = $cart_item['product_id'];
if ( $product_in_cart === $product_id ) $in_cart = true;
}
if ( $in_cart ) {
echo '<h2>Membership Application</h2>';
echo '<p>Select all that applies</p>';
woocommerce_form_field( 'read_wog', array(
'type' => 'checkbox',
'class' => array( 'form-row-wide no-req' ),
'required' => true,
'label' => 'I accept term 1',
), $checkout->get_value( 'read_wog' ) );
woocommerce_form_field( 'one_on_one', array(
'type' => 'checkbox',
'class' => array( 'form-row-wide no-req' ),
'required' => true,
'label' => 'I accept term 2',
), $checkout->get_value( 'one_on_one' ) );
woocommerce_form_field( 'mm_sn', array(
'type' => 'checkbox',
'required' => true,
'class' => array( 'form-row-wide no-req' ),
'label' => 'I accept term 3).',
), $checkout->get_value( 'mm_sn' ) );
}
}
The site uses Paypal Express as a payment gateway, and the validation lets people go through Paypal regardless of the checkbox validation. The validation for default fields works fine. The error notice is added when manually refreshing the page though!
Here's the validation code:
add_action( 'woocommerce_checkout_process', 'mmm_validate_new_checkout_field' );
function mmm_validate_new_checkout_field() {
$product_id = 214884;
$in_cart = false;
foreach( WC()->cart->get_cart() as $cart_item ) {
$product_in_cart = $cart_item['product_id'];
if ( $product_in_cart === $product_id ) $in_cart = true;
}
if( $in_cart && !isset($_POST['mm_sn']) && !isset($_POST['one_on_one']) && !isset($_POST['read_wog']) ) {
wc_add_notice( 'You can only have a full membership if you accept at least 1 term', 'error' );
}
}
Any idea how to make it work?
"The site uses PayPal Express as a payment gateway"
This isn't specific enough to be able to advise. If PayPal JS SDK buttons are being used (called smart buttons in the WooCommerce PayPal plugin configuration), then you can add an onClick handler as documented here: https://developer.paypal.com/docs/business/javascript-sdk/javascript-sdk-reference/#oninitonclick
You'll need to edit the outputted JS of how the WooCommerce plugin invokes paypal.Buttons to include such a function.
The following revisited code will throw an error validation message if at least one checkbox has not been checked on checkout:
// Custom function that check if a specific product is in cart
function is_product_in_cart( $product_id ) {
foreach( WC()->cart->get_cart() as $item ) {
if ( in_array( $product_id, array($item['product_id'], $item['variation_id']) ) ) {
return true;
}
}
return false;
}
// Add Custom checkout checkboxes fields
add_action( 'woocommerce_before_order_notes', 'mmm_add_custom_checkout_field' );
function mmm_add_custom_checkout_field( $checkout ) {
$targeted_id = 214884;
if ( is_product_in_cart( $targeted_id ) ) {
echo '<h2>Membership Application</h2>
<p>Select all that applies</p>';
woocommerce_form_field( 'read_wog', array(
'type' => 'checkbox',
'class' => array( 'form-row-wide no-req' ),
'required' => true,
'label' => 'I accept term 1',
), $checkout->get_value( 'read_wog' ) );
woocommerce_form_field( 'one_on_one', array(
'type' => 'checkbox',
'class' => array( 'form-row-wide no-req' ),
'required' => true,
'label' => 'I accept term 2',
), $checkout->get_value( 'one_on_one' ) );
woocommerce_form_field( 'mm_sn', array(
'type' => 'checkbox',
'required' => true,
'class' => array( 'form-row-wide no-req' ),
'label' => 'I accept term 3).',
), $checkout->get_value( 'mm_sn' ) );
}
}
// Custom checkout checkboxes fields validation
add_action( 'woocommerce_checkout_process', 'mmm_validate_new_checkout_field' );
function mmm_validate_new_checkout_field() {
$targeted_id = 214884;
if ( is_product_in_cart( $targeted_id ) && ! ( isset($_POST['read_wog']) || isset($_POST['one_on_one']) || isset($_POST['mm_sn']) ) ) {
wc_add_notice( 'You can only have a full membership if you accept at least 1 term', 'error' );
}
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
I've added a new field for phone number in WooCommerce checkout form.
Everything is working perfectly except the maxlength = "10". After adding the snippet it shows maxlength = 10 when I inspect element, but the phone field allows me to type n number of characters.
Am I doing anything wrong?
Here is the snippet i used in functions.php
add_action('woocommerce_after_checkout_billing_form', 'custom_checkout_field');
function custom_checkout_field($checkout)
{
woocommerce_form_field('billing_phone_2', array(
'type' => 'number',
'maxlength' => "10",
'id' => 'billing_phone_2',
'class' => array(
'input-text form-row-wide'
) ,
'required' => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ),
'label' => __('Phone 2') ,
'placeholder' => __('') ,
) ,
$checkout->get_value('billing_phone_2'));
}
For the type you could use tel, and for the maxlength it is not necessary to put the number between quotes
So you get:
// Display a custom checkout field after billing form
function action_woocommerce_after_checkout_billing_form( $checkout ) {
woocommerce_form_field( 'billing_phone_2', array(
'type' => 'tel',
'maxlength' => 10,
'class' => array( 'form-row-wide' ),
'required' => true,
'label' => __( 'Phone 2', 'woocommerce' ),
'placeholder' => __( 'My placeholder', 'woocommerce' ),
), $checkout->get_value( 'billing_phone_2' ));
}
add_action( 'woocommerce_after_checkout_billing_form', 'action_woocommerce_after_checkout_billing_form', 10, 1 );
Optional: some validation for the custom (required) field
// Custom checkout field validation
function action_woocommerce_checkout_process() {
// Isset
if ( isset( $_POST['billing_phone_2'] ) ) {
$domain = 'woocommerce';
$phone = $_POST['billing_phone_2'];
// Empty
if ( empty ( $phone ) ) {
wc_add_notice( __( 'Please enter a phone number to complete this order', $domain ), 'error' );
}
// Validates a phone number using a regular expression.
if ( 0 < strlen( trim( preg_replace( '/[\s\#0-9_\-\+\/\(\)\.]/', '', $phone ) ) ) ) {
wc_add_notice( __( 'Please enter a valid phone number', $domain ), 'error' );
}
}
}
add_action( 'woocommerce_checkout_process', 'action_woocommerce_checkout_process', 10, 0 );
If type = number, you can use the min and max attributes
// Display a custom checkout field after billing form
function action_woocommerce_after_checkout_billing_form( $checkout ) {
woocommerce_form_field( 'billing_phone_2', array(
'type' => 'number',
'custom_attributes' => array(
'min' => 0,
'max' => 9999999999,
),
'class' => array( 'form-row-wide' ),
'placeholder' => __( 'Phone 2', 'woocommerce' ),
), $checkout->get_value( 'billing_phone_2' ));
}
add_action( 'woocommerce_after_checkout_billing_form', 'action_woocommerce_after_checkout_billing_form', 10, 1 );
Although I wouldn't recommend it for a phone number, there is a reason that the type 'tel' exists, as used in other phonefields in WooCommerce
I am trying to create a an extra option on the General settings WooCommerce page but could not get that working so I tried with the advanced tab instead, which seem to work.
The goal here is to create a checkbox option which enables a catalog mode by applying the filter for is_purchasable.
But, what I cannot figure out is how to apply and save the filter for woocommerce_is_purchasable if the checkbox is marked the settings saved.
Here's what I got so far:
add_filter( 'woocommerce_get_sections_advanced', 'catalog_mode_add_section' );
add_filter( 'woocommerce_get_settings_advanced', 'catalog_mode_all_settings', 10, 2 );
function catalog_mode_add_section( $sections ) {
$sections['catalog-mode'] = __( 'Catalog Mode', 'text-domain' );
return $sections;
}
function catalog_mode_all_settings( $settings, $current_section ) {
if ( $current_section == 'catalog-mode' ) {
$settings_catalog_options = array();
// Add Title to the Settings
$settings_catalog_options[] = array( 'name' => __( 'WooCommerce Catalog Mode', 'text-domain' ), 'type' => 'title', 'desc' => __( 'This turns WooCommerce into a catalog.', 'text-domain' ), 'id' => 'catalog_mode' );
// Add second text field option
$settings_catalog_options[] = array(
'name' => __( 'Catalog Mode', 'text-domain' ),
'id' => 'catalog_mode',
'type' => 'checkbox',
);
$settings_catalog_options[] = array( 'type' => 'sectionend', 'id' => 'catalog_mode' );
return $settings_catalog_options;
} else {
return $settings;
}
}
I'm lost right now..
Anyone?
There is a little mistake in your code, where each setting component need a unique identifier (id)… I have updated your code and your custom option is now saved.
add_filter( 'woocommerce_get_sections_advanced', 'catalog_mode_add_section' );
function catalog_mode_add_section( $sections ) {
$sections['catalog-mode'] = __( 'Catalog Mode', 'text-domain' );
return $sections;
}
add_filter( 'woocommerce_get_settings_advanced', 'catalog_mode_all_settings', 10, 2 );
function catalog_mode_all_settings( $settings, $current_section ) {
if ( $current_section == 'catalog-mode' ) {
$settings_catalog_options = array();
// Add Title to the Settings
$settings_catalog_options[] = array(
'name' => __( 'WooCommerce Catalog Mode', 'text-domain' ),
'type' => 'title',
'desc' => __( 'This turns WooCommerce into a catalog.', 'text-domain' ),
'id' => 'wc_catalog_mode_title'
);
// Add second text field option
$settings_catalog_options[] = array(
'name' => __( 'Catalog Mode', 'text-domain' ),
'type' => 'checkbox',
'id' => 'wc_catalog_mode',
);
$settings_catalog_options[] = array(
'type' => 'sectionend',
'id' => 'wc_catalog_mode_end'
);
return $settings_catalog_options;
}
return $settings;
}
Then in woocommerce_is_purchasable and woocommerce_variation_is_purchasable filters, you will use it this way:
add_filter('woocommerce_is_purchasable', 'product_is_purchasable_filter_callback', 10, 2 );
add_filter( 'woocommerce_variation_is_purchasable', 'product_is_purchasable_filter_callback', 10, 2 );
function product_is_purchasable_filter_callback( $purchasable, $product ) {
if( 'yes' === get_option('wc_catalog_mode') ) {
$purchasable = false;
}
return $purchasable;
}
Code goes in function.php file of the active child theme (or active theme). Tested and works.
You could use "products" section instead of "advanced" replacing your hooks with:
woocommerce_get_sections_products
woocommerce_get_settings_products
I've been trying to hook this function in one of the "Order Hooks" of the Woocommerce Checkout page:
add_action( 'woocommerce_checkout_before_order_review', 'add_box_conditional' );
function add_box_conditional ( $checkout ) {
woocommerce_form_field( 'test', array(
'type' => 'checkbox',
'class' => array('test form-row-wide'),
'label' => __('conditional test'),
'placeholder' => __(''),
), $checkout->get_value( 'test' ));
}
If i try to get the value of the custom box in any order hooks, the order info just hangs and stops loading. I've tried with another type of custom fields and the same happens.
Example
If I hook the function outside the order contents works perfectly. The custom check box will be used to add a fee (post validation), as it is a very important option for our shop I want it inside the order details, so it can have a strong focus. Is there a way to make the function work on these hooks, or should I put it anywhere and move it with a simple but not so clean CSS overwritte?
You can't just get the value like that $checkout->get_value( 'test' ));.
Hook woocommerce_checkout_create_order and get the value from $_POST there. Then add a custom fee to the order if the checkbox was checked.
Like this:
function add_box_conditional() {
woocommerce_form_field( 'test', array(
'type' => 'checkbox',
'class' => array( 'test form-row-wide' ),
'label' => __( 'conditional test' ),
'placeholder' => __( '' ),
) );
}
add_action( 'woocommerce_checkout_before_order_review', 'add_box_conditional' );
function edit_order( $order, $data ) {
if( ! isset( $_POST[ 'test' ] ) ) {
return;
}
$checkbox_value = filter_var( $_POST[ 'test' ], FILTER_SANITIZE_NUMBER_INT );
if( $checkbox_value ){
$fee = 20;
$item = new \WC_Order_Item_Fee();
$item->set_props( array(
'name' => __( 'Custom fee', 'textdomain' ),
'tax_class' => 0,
'total' => $fee,
'total_tax' => 0,
'order_id' => $order->get_id(),
) );
$item->save();
$order->add_item( $item );
$order->calculate_totals();
}
}
add_action( 'woocommerce_checkout_create_order', 'edit_order', 10, 2 );