I have been trying to add a custom 'select' field to a woocommerce checkout. The select options are comprised of entries in an array, which in turn is comprised of titles and dates from a query of custom post types.
This all works fine, I can add new custom posts and the title and dates are concatenated, added to the array and in turn added to the select drop-down at the checkout, however when I submit the form and complete the order the index of the chosen select field value is added to the meta data, not the value.
I have added images in here to show you what I mean. e.g. if I select the third option from the dropdown 'Tain Farmers Market' which is array index 2 that's what is saved for the order meta instead of the value at that index entry.
The screenshot includes the results of the var dump showing the array contents as well as the select field below it.
image of how the array appears in var dump
the order meta showing in the admin screen, displaying the index not the value
Here is my code covering this section, any pointers would be really handy. I feel there is probably some simple solution to get the value at the chosen index when the order is processed?
/**
* Add custom Pickup Field field to the checkout page
*/
add_filter( 'woocommerce_after_order_notes', 'hiwoo_add_checkout_fields' );
function hiwoo_add_checkout_fields( $checkout ) {
$args = array(
'post_type' => 'pickup-point',
'posts_per_page' => -1,
'post_status' => 'publish'
);
// Custom query to pull in the pickup points.
$query = new WP_Query( $args );
$pickup_comb_option = [];
while ($query->have_posts()) {
$query->the_post();
$postid = $post->ID;
$pickuptitle = get_the_title($post);
$pickupdate = get_post_meta(get_the_id($post), 'available_date', true);
$pickupoption = $pickuptitle . ' - ' . $pickupdate;
array_push($pickup_comb_option, $pickupoption);
}
var_dump($pickup_comb_option);
// Restore original post data.
wp_reset_postdata();
echo '<div id="custom_checkout_field"><h2>' . __('Order Pickup Location/Date') . '</h2>';
woocommerce_form_field( 'pickup_point_options', array(
'type' => 'select',
'class' => array(
'my-field-class form-row-wide'
) ,
'label' => __('Select Pickup Location/Date') ,
'placeholder' => __('Pickup Point') ,
'options' => $pickup_comb_option,
),
$checkout->get_value( 'pickup_point_options' ));
echo '</div>';
} /* Close custom field function */
/**
* Process the checkout
*/
add_action('woocommerce_checkout_process', 'my_custom_checkout_field_process');
function my_custom_checkout_field_process() {
// Check if set, if its not set add an error.
if ( ! $_POST['pickup_point_options'] )
wc_add_notice( __( 'Please select a pickup location from the list.' ), 'error' );
}
/**
* Update the order meta with field value
*/
add_action( 'woocommerce_checkout_update_order_meta', 'my_custom_checkout_field_update_order_meta' );
function my_custom_checkout_field_update_order_meta( $order_id ) {
if ( ! empty( $_POST['pickup_point_options'] ) ) {
update_post_meta( $order_id, 'Pickup Location/Date', sanitize_text_field( $_POST['pickup_point_options'] ) );
}
}
/**
* Display field value on the order edit page
*/
add_action( 'woocommerce_admin_order_data_after_billing_address', 'my_custom_checkout_field_display_admin_order_meta', 10, 1 );
function my_custom_checkout_field_display_admin_order_meta($order){
echo '<p><strong>'.__('Pickup Location/Date').':</strong> ' . get_post_meta( $order->id, 'Pickup Location/Date', true ) . '</p>';
}
The html produced on the checkout page for the select field is:
array(3) {
[0]=>
string(32) "Tain Farmers Market - 2019-12-25"
[1]=>
string(30) "Hi Create Offices - 2019-10-25"
[2]=>
string(27) "Dornoch Stores - 2019-09-26"
}
<select name="pickup_point_options" id="pickup_point_options" class="select " data-placeholder="Pickup Point">
<option value="0">Tain Farmers Market - 2019-12-25</option>
<option value="1">Hi Create Offices - 2019-10-25</option>
<option value="2">Dornoch Stores - 2019-09-26</option>
</select>
According to your HTML I assume the value being saved in your example is "2" instead of "Tainy Farmers Market", right? If so, that is technically correct, as the value of a <select> being submitted on a form submit is always the value in the option's value attribute (<option value="2">) and not the text in between (<option value="2">Some text</option>)
To have it saving the text in between it has to be set inside the value-attribute. Therefor you need to change the option's array from numeric to associative, like so:
$pickup_comb_option = [];
while ($query->have_posts()) {
$query->the_post();
// ...
// change this line
array_push($pickup_comb_option, $pickupoption);
// to:
$pickup_comb_option[$pickupoption] = $pickupoption;
}
To verify this worked check for the generated HTML, it should look something like this:
<select>
<option value="Tain Farmers Market">Tain Farmers Market</option>
</select>
#edit
I should mention this approach has some downsides:
You should ensure that the string in $pickupoption is not used multiple times. It's also necessary that the string is valid for use as an array key (not NULL, not empty). Since the value is being written inside a html-attribute (value="$arrKey") you may also want to check how Woocommerce handle's such things as doublequotes inside the string. Just to avoid possible bugs beforehand.
An alternative approach could be to save an id instead of a string.
Related
This is my current recent order
I wish to add another column "Tracking Number" and it will show woocommerce "note to customer" inside.
result is like : Display last WooCommerce admin order note in customers order history
The difference is without clicking view order and my customer can get to known their tracking number.
But I totally no idea how this work because not familiar with php..
hope to make this done and learn something.
Thanks!
the result will
You Need to create a new column in My order page first
function order_note_in_column( $columns ) {
$new_columns = array();
foreach ( $columns as $key => $name ) {
$new_columns[ $key ] = $name;
// Your Column Name : Change Tracking Number with the Column Heading you Want
if ( 'order-status' === $key ) {
$new_columns['track-number'] = __( 'Tracking Number', 'textdomain' );
}
}
return $new_columns;
}
add_filter( 'woocommerce_my_account_my_orders_columns', 'order_note_in_column' );
Once you create a new column now the second step is to display Data in the column.keep type as internal to show private note, In this way customer notes will not be displayed at frontend
function order_note_value_in_column( $order ) {
//Get Notes by order ID & Here keep type as internal to show private note. In this way customer notes will not be displayed at frontend
$note = wc_get_order_notes([
'order_id' => $order->get_id(),
'type' => 'internal',
]);
// Displaying the latest Note. If no tracking number entered then order status will be displayed in column
print_r($note[0]->content);
}
add_action( 'woocommerce_my_account_my_orders_column_track-number', 'order_note_value_in_column' );
code goes in functions.php tested & works
This might be a stupid question, but the trivial thing is that the most common function fails to work.
By this code I have set up my custom field:
/**
* Extra custom fields
*/
function ccf_create_custom_field() {
$args = array(
'id' => 'custom_cost_field',
'label' => __( 'Product Cost', 'woocommerce' ),
'class' => 'ccf-cost-field',
'type' => 'number',
);
woocommerce_wp_text_input( $args );
}
/* Display Fields */
add_action( 'woocommerce_product_options_general_product_data', 'ccf_create_custom_field' );
/* Save Fields */
add_action('woocommerce_process_product_meta', 'woocommerce_product_custom_fields_save');
function woocommerce_product_custom_fields_save($post_id) {
/* Custom Product Number Field */
$woocommerce_custom_product_number_field = $_POST['custom_cost_field'];
if (!empty($woocommerce_custom_product_number_field))
update_post_meta($post_id, 'custom_cost_field', esc_attr($woocommerce_custom_product_number_field));
}
require_once "custom.php";
This is my custom.php:
<?php while (have_posts()) : the_post(); ?>
<?php wc_get_template_part('content', 'single-product'); ?>
<?php
// Display the value of custom product number field
echo get_post_meta(get_the_ID(), 'custom_cost_field', true);
?>
<?php endwhile; // end of the loop. ?>
This is the function that obtains values:
$costs = (ceil((get_post_meta($product, 'custom_cost_field', true))/100)*85);
Although the custom field has a value saved of for example of 1090, the get_post_meta does not return anything, as the value in my table is always 0 for costs. I absolutely don´t understand this :S.
Any ideas? Am I missing something out?
Okay, this is the answer:
$custom_cost = ceil(get_post_meta(get_the_id(), 'custom_cost_field', true));
For custom fields, always keep the "get_the_id()" in the function and it will work. Thats the whole trick - never keep a variable (product, post) in there.
If you have obtained the ID via a different function before, it still won´t work - you must keep the "get_the_id()" in the function.
I am creating a small plugin to add a 'data-pin-description' attribute to my images. I intend for the client to be able to add that meta data description to any image attachment from admin and then have that meta value be output to the tag on the front end, when the attachment is added to post content. I want the plugin to grab that meta data at the appropriate time and include it with the img tag data- attribute and value, before it's already output. End result should be:
My admin code seems to be accepting and saving the client-entered value just find, however I am having trouble outputting that data. One problem is I have been unable to identify which hook would retrieve and add that data to the tag at the correct time
I've tried querying the attachment posts but I don't know what action or filter to hook into
Here is how I'm adding the meta data in admin:
<?php
//exit if file is called directly
if (! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Adding a custom field to Attachment Edit Fields
* #param array $form_fields
* #param WP_POST $post
* #return array
*/
//add attachment fields
function ad_add_pinterest_fields( $form_fields, $post ) {
$field_value = get_post_meta( $post->ID, 'pin-description', true );
$form_fields['pin-description'] = array(
'value' => $field_value ? esc_textarea($field_value) : '',
'label' => __( 'Pin Description' ),
'helps' => __( 'Add a short description for Pinterest SEO' ),
'input' => 'textarea'
);
return $form_fields;
}
add_filter( 'attachment_fields_to_edit', 'ad_add_pinterest_fields', null, 2 );
//save attachment fields
function ad_save_pinterest_fields( $attachment_id ) {
if ( isset( $_REQUEST['attachments'][$attachment_id]['pin-description'] ) ) {
$pinDescription = sanitize_text_field( $_REQUEST['attachments'][$attachment_id]['pin-description'] );
update_post_meta( $attachment_id, 'pin-description', $pinDescription );
}
}
add_action( 'edit_attachment', 'ad_save_pinterest_fields' );
?>
I expect to retrieve the post_meta of each attachment image for a given post, but have only received errors
to solve this I ended up hooking into image_send_to_editor to modify the image html prior to it's being sent to the editor. this way, with the image html in the editor and correctly displaying my attribute and value, when I click 'update post' the html is saved to the database with those pieces of data. when the post is viewed on the frontend, the output from the database correctly reflects as well.
I successfully build a function, that is able to import data from a CSV into posts of a Custom Post Type. I am able to parse data into the native title and description fields, but the function fails to parse data into a meta box with multiple fields.
Here is a code snippet of the function that are parsing CSV data into post fields:
// Check and see if the current post exists within the database.
$check_post_exists = function( $title ) use ( $wpdb, $postTypeArray ) {
// Get an array of all posts within the custom post type
$posts = $wpdb->get_col( "SELECT post_title FROM {$wpdb->posts} WHERE post_type = '{$postTypeArray["custom-post-type"]}' AND post_status = 'publish'" );
// Check if the passed title exists in array
return in_array( $title, $posts );
}
$i = 0;
foreach ( $posts() as $post ) {
// If the post exists, skip this post and go to the next one
if ( $check_post_exists( $post["zoneid"] ) ) {
continue;
}
$i++;
// Insert the post into the database
$post["id"] = wp_insert_post( array(
"post_title" => $post["zoneid"],
"post_content" => $post["bemaerkning"],
"post_type" => $postTypeArray["custom-post-type"],
"post_punktcat" => array( 4 ),
"post_status" => "publish"
));
// Set post category to the value "4"
wp_set_post_terms($post["id"], 4, 'punktcat', false );
// Parse data into a custom field normally referred
// FOLLOWINF FUNCTION IS UPDATING EVERY FIELD OF THE META BOX WITH THE
VALUE OF '1'
update_post_meta( $post["id"], 'fredningszone_data', true );
}
echo '<div id="message" class="updated fade"><p>' . $i . ' posts have succesfully been imported from CSV!' . '</p></div>';
So the update_post_meta() function in the code snippet above does not provide the data of the zoneid into the custom field fredningszone_data[id].
The way I build my custom field and be seen within the following code snippet:
function fredningszone_meta_box() {
add_meta_box(
'punkt_fredningszone', // $id - Meta box ID (used in the 'id' attribute for the meta box).
'Data for fredningszone', // $title - Title of the meta box.
'punkt_fredningszone_callback', // $callback - Function that fills the box with the desired content. The function should echo its output.
'punkt', // $screen - he screen or screens on which to show the box (such as a post type, 'link', or 'comment').
'normal', // $context - The context within the screen where the boxes should display.
'high' // $priority - The priority within the context where the boxes should show ('high', 'low').
);
}
add_action( 'add_meta_boxes', 'fredningszone_meta_box' );
/**
* Enable and display custom fields.
**/
function punkt_fredningszone_callback() {
global $post;
$meta = get_post_meta( $post->ID, 'fredningszone_data', true ); ?>
<input type="hidden" name="fredningszone_data_nonce" value="<?php echo wp_create_nonce( basename(__FILE__) ); ?>">
<!-- All fields goes below this line -->
<p>
<label for="fredningszone_data[id]">ID</label>
<br>
<input type="text" name="fredningszone_data[id]" id="fredningszone_data[id]" class="regular-text widefat" placeholder="Indtast fredningszonens ID (f.eks 450)" value="<?php echo $meta['id']; ?>">
</p>
I will appreciate, if anyone would have a suggestion on how I could parse the data into one of the fields of my custom metabox with multiple fields.
I am trying to 'check' a checkbox by default in the WooCommerce checkout page.
I'm using the official WooCommerce Checkout Field Editor plugin to add a checkbox but it doesn't give me the ability to have the checkbox 'checked' by default.
I've tried using this code but it's not working:
function custom_override_checkout_fields ( $fields ) {
$fields['additional']['uncheck-to-opt-out-of-our-quarterly-newsletter-neuroscience-matters_field']['default'] = 1;
return $fields;
}
add_filter( 'woocommerce_checkout_fields' , 'custom_override_checkout_fields' );
Any help would be greatly appreciated. Thanks
You should add checkbox on checkout page using below code:
<?php
/**
* Add checkbox field to the checkout
**/
add_action('woocommerce_after_order_notes', 'my_custom_checkout_field');
function my_custom_checkout_field( $checkout ) {
$checked = $checkout->get_value( 'my_checkbox1' ) ? $checkout->get_value( 'my_checkbox1' ) : 1;
echo '<div id="my-new-field"><h3>'.__('My Checkbox: ').'</h3>';
woocommerce_form_field( 'my_checkbox1', array(
'type' => 'checkbox',
'class' => array('input-checkbox'),
'label' => __('I have read and agreed.'),
'required' => true,
), $checked);
echo '</div>';
}
To define the default value of a custom WooCommerce field set up using the woocommerce_checkout_fields hook, it's best to hook another filter function to woocommerce_checkout_get_value or default_checkout_<your_field_name> hooks.
woocommerce_checkout_get_value:
This one is a short circuit filter, which means if you use this, the $value will always start from null and if the returned $value is not null it prevents any attempt to read user's metadata for any previously defined user preferences.
In a sense it is kind of a hard default: for each checkout it uses the value defined in this filter.
add_filter( 'woocommerce_checkout_get_value', 'custom_override_checkout_get_value', 10, 2 );
public function custom_override_checkout_get_value( $value, $field ) {
// $value is null or the result of previous filters
if ( 'uncheck-to-opt-out-of-our-quarterly-newsletter-neuroscience-matters_field' === $field ) {
$value = 1;
}
return $value;
}
default_checkout_<your_field_name>:
This behaves similar to the one above, however this is called after the default value is read from a logged in user's defaults, which may have been set previously as metadata for the given user (e.g. saved during the last checkout if the user has already made a purchase before).
This can be considered a soft default, as with appropriate checks (like below: only set $value if null), the default is only applied if there is no existing user preference.
Also since this uses a dedicated hook name defined by the field name, there's no need to check which field the function is being called for.
add_filter( 'default_checkout_uncheck-to-opt-out-of-our-quarterly-newsletter-neuroscience-matters_field', 'custom_override_default_checkout_field' );
public function custom_override_checkout_get_value( $value ) {
// $value is read from logged in user's meta or null or the result of previous filters
if ( is_null($value) ) {
// Only use default if null, aka. if user has no previous preference saved.
$value = 1;
}
return $value;
}
For more context check out WC_Checkout::get_value() method in wp-content/plugins/woocommerce/includes/class-wc-checkout.php which calls both of these filters.