I am trying to override the account_display_name field within the my-account (logged in user) section which I am undertaking through two hooks. The first removes the required attribute, and the second updates the display_name field based on the input of first and last name.
This filter allows me to remove the required="required" attribute from the display_name field and works as intended:
add_filter('woocommerce_save_account_details_required_fields', 'audp_myaccount_required_fields');
function audp_myaccount_required_fields( $required_fields ) {
unset( $required_fields['account_display_name'] );
return $required_fields;
}
This next action is intended to allow me to save the submitted account_first_name and account_last_name as the display_name:
add_action( 'profile_update', 'audp_myaccount_display_name', 20, 2 );
function audp_myaccount_display_name( $display_name ) {
global $current_user;
if ( isset( $_POST['account_first_name'] ) && isset( $_POST['account_last_name'] ) ) {
if ( ! empty( $_POST['account_first_name'] ) && ! empty( $_POST['account_last_name'] ) ) {
wp_update_user(
array (
'ID' => $current_user->ID,
'display_name' => sanitize_text_field( $_POST['account_first_name'] ) . ' ' . sanitize_text_field( $_POST['account_last_name'] ),
)
);
}
}
}
The problem I'm having with this code is the looping after submitting the data. The page 'eventually' returns an Internal 500 Error. If I stop the process (escape) and reload the page, the data has updated. I just don't know how to get out of this loop?
I have researched some options including remove_action( 'profile_update', 'audp_myaccount_display_name', 20, 2 ) before the wp_update_user() and then add_action( 'profile_update', 'audp_myaccount_display_name', 20, 2 ) after wp_update_user() but that has not seemed to work either.
Any assistance would be appreciated!
You should be able to stop endless loops by adding an additional filter, with a default false value, that you check at the top of your function. Stopping your code from being executed when this filter returns true.
Then call that filter before you update the user meta and make it return true. And remove it after updating the user meta. This way the check at the top will return true the second time the functions gets called and stops the endless loop, resulting in your function only running once.
add_action( 'profile_update', 'audp_myaccount_display_name', 20, 2 );
function audp_myaccount_display_name( $display_name ) {
if ( apply_filters( 'prevent_endless_loop_updating_user_meta', false ) ) return; // prevent endless loops
global $current_user;
if ( isset( $_POST['account_first_name'] ) && isset( $_POST['account_last_name'] ) ) {
if ( ! empty( $_POST['account_first_name'] ) && ! empty( $_POST['account_last_name'] ) ) {
add_filter( 'prevent_endless_loop_updating_user_meta', '__return_true' );
wp_update_user(
array (
'ID' => $current_user->ID,
'display_name' => sanitize_text_field( $_POST['account_first_name'] ) . ' ' . sanitize_text_field( $_POST['account_last_name'] ),
)
);
remove_filter( 'prevent_endless_loop_updating_user_meta', '__return_true' );
}
}
}
Related
There are many threads which deal with the topic "custom fields in WooCommerce emails" but I couldn't find the exact case I am struggling with.
I could achieve 50% of this project to display the field as a meta in the order table.
add_action( 'woocommerce_order_item_meta_end', 'custom_product_info', 20, 4 );
function custom_product_info ( $item_id, $item, $order, $plain_text ) {
$id = $item->get_product_id();
$erlebnisidgc = get_field('erlebnis_id',$id);
if($erlebnisidgc){
echo "<p>Here's your Link</p><a href=https://b-ceed.de/?eventid='.$erlebnisidgc'>Testlink</a>";
}
}
So this code works perfectly with the custom field. Problem is that this output isn't shown only in the customer_completed_order email but also in all other emails.
Therefore, I tried this code snippet:
add_action( 'woocommerce_order_item_meta_end', 'custom_product_info', 20, 4 );
function custom_product_info ( $item_id, $item, $order, $plain_text ) {
if ( $email->id == 'customer_completed_order' ) {
$id = $item->get_product_id();
$erlebnisidgc = get_field('erlebnis_id',$id);
if($erlebnisidgc){
echo "<p>Here's your Link</p><a href=https://b-ceed.de/?eventid='.$erlebnisidgc'>Testlink</a>";
}
}
}
But now the output won't be displayed in any email anymore and it triggers a internal server error. Any advice?
$email ($email->id) is not passed as argument to the woocommerce_order_item_meta_end hook, therefore it is undefined
So to target specific email notifications a workaround will be needed, this can be done by creating a global variable via the woocommerce_email_before_order_table hook
So you get:
// Setting global variable
function action_woocommerce_email_before_order_table( $order, $sent_to_admin, $plain_text, $email ) {
$GLOBALS['email_data'] = array(
'email_id' => $email->id, // The email ID (to target specific email notification)
'is_email' => true // When it concerns a WooCommerce email notification
);
}
add_action( 'woocommerce_email_before_order_table', 'action_woocommerce_email_before_order_table', 1, 4 );
function action_woocommerce_order_item_meta_end( $item_id, $item, $order, $plain_text ) {
// Getting the custom 'email_data' global variable
$ref_name_globals_var = $GLOBALS;
// Isset & NOT empty
if ( isset ( $ref_name_globals_var ) && ! empty( $ref_name_globals_var ) ) {
// Isset
$email_data = isset( $ref_name_globals_var['email_data'] ) ? $ref_name_globals_var['email_data'] : '';
// NOT empty
if ( ! empty( $email_data ) ) {
// Target specific emails, several can be added in the array, separated by a comma
$target_emails = array( 'customer_completed_order' );
// Target specific WooCommerce email notifications
if ( in_array( $email_data['email_id'], $target_emails ) ) {
// Get product ID
$product_id = $item->get_product_id();
// Get field
$erlebnisidgc = get_field( 'erlebnis_id', $product_id );
// Has some value
if ( $erlebnisidgc ) {
echo '<p>Here is your Link</p>';
echo 'Testlink';
}
}
}
}
}
add_action( 'woocommerce_order_item_meta_end', 'action_woocommerce_order_item_meta_end', 10, 4 );
Used in this answer:
Determine only for WooCommerce admin email notification
How to target other WooCommerce order emails
I need to automatically set a certain order status (different than processing) when getting a new order.
This is achieved by this function:
add_action('woocommerce_thankyou','change_order_status');
function change_order_status( $order_id ) {
if ( ! $order_id ) { return; }
$order = wc_get_order( $order_id );
if( 'processing'== $order->get_status() ) {
$order->update_status( 'wc-custom-status' );
}
}
This totally works. Now I only need this to happen when a product has a customization.
The way to customize a product is filling an input field before adding to cart. The input is attached to the item data:
// Add custom cart item data
add_filter( 'woocommerce_add_cart_item_data', 'add_custom_cart_item_data', 10, 2 );
function add_custom_cart_item_data( $cart_item_data, $product_id ){
if( isset($_POST['custom_text']) ) {
$cart_item_data['custom_text'] = sanitize_text_field( $_POST['custom_text'] );
$cart_item_data['unique_key'] = md5( microtime().rand() ); // Make each item unique
}
return $cart_item_data;
}
Then the custom text is retrieved and displayed in cart and in the order data using this:
// Display custom cart item data on cart and checkout
add_filter( 'woocommerce_get_item_data', 'display_custom_cart_item_data', 10, 2 );
function display_custom_cart_item_data( $cart_item_data, $cart_item ) {
if ( !empty( $cart_item['custom_text'] ) ){
$cart_item_data[] = array(
'name' => __('Customization', 'woocommerce'),
'value' => $cart_item['custom_text'] // Already sanitized field
);
}
return $cart_item_data;
}
// Save and display custom item data everywhere on orders and email notifications
add_action( 'woocommerce_checkout_create_order_line_item', 'add_product_custom_field_as_order_item_meta', 10, 4 );
function add_product_custom_field_as_order_item_meta( $item, $cart_item_key, $values, $order ) {
if ( isset($values['custom_text']) ) {
$item->update_meta_data('Add on', $values['custom_text'] );
}
}
I'm trying using the if ( isset($values['custom_text']) ) part as a trigger of the function to change the order status only if the product add on is set and other similar methods (like if ( !empty( $cart_item['custom_text'] ) ) but I'm not sure this is the way to go:
add_action('woocommerce_thankyou','change_order_status');
function change_order_status( $order_id ) {
if ( ! $order_id ) {return;}
$order = wc_get_order( $order_id );
if ( isset($values['custom_text']) ) {
if( 'processing'== $order->get_status() ) {
$order->update_status( 'wc-custom-status' );
}
}
}
This above does nothing. Am I anywhere near it with this approach?
EDIT: I tried this too
add_action('woocommerce_thankyou','change_order_status');
function change_order_status( $order_id ) {
if ( ! $order_id ) {return;}
$order = wc_get_order( $order_id );
foreach ( $order->get_items() as $item_id => $item ) {
$allmeta = $item->get_meta_data();
if ( isset($values['custom_text']) ) {
if( 'processing'== $order->get_status() ) {
$order->update_status( 'wc-custom-status' );
}
}
}
}
Your code contains some unnecessary steps as well as some shortcomings
For example, the woocommerce_add_cart_item_data hook contains 3 arguments versus 2
So you get:
// Add custom cart item data
function filter_add_cart_item_data( $cart_item_data, $product_id, $variation_id ) {
// Isset and NOT empty
if ( isset ( $_POST[ 'custom_text' ] ) && ! empty ( $_POST[ 'custom_text' ] ) ) {
$cart_item_data['custom_text'] = sanitize_text_field( $_POST['custom_text'] );
}
return $cart_item_data;
}
add_filter( 'woocommerce_add_cart_item_data', 'filter_add_cart_item_data', 10, 3 );
// Display custom cart item data on cart and checkout
function filter_woocommerce_get_item_data( $cart_data, $cart_item = null ) {
if ( isset ( $cart_item['custom_text'] ) ) {
$cart_data[] = array(
'name' => __( 'Customization', 'woocommerce' ),
'value' => $cart_item['custom_text']
);
}
return $cart_data;
}
add_filter( 'woocommerce_get_item_data', 'filter_woocommerce_get_item_data', 10, 2 );
// Add the information as meta data so that it can be seen as part of the order
// Save and display custom item data everywhere on orders and email notifications
function action_woocommerce_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
if ( isset ( $values['custom_text'] ) ) {
$item->update_meta_data( 'custom_text', $values['custom_text'] );
}
}
add_action( 'woocommerce_checkout_create_order_line_item', 'action_woocommerce_checkout_create_order_line_item', 10, 4 );
// On thankyou page
function action_woocommerce_thankyou( $order_id ) {
// Get $order object
$order = wc_get_order( $order_id );
// Is a WC_Order
if ( is_a( $order, 'WC_Order' ) ) {
// Only when the current order status is 'processing'
if ( $order->get_status() == 'processing' ) {
// Loop trough
foreach ( $order->get_items() as $item ) {
// Get meta
$value = $item->get_meta( 'custom_text' );
// NOT empty
if ( ! empty ( $value ) ) {
// Update status (change to desired status)
$order->update_status( 'cancelled' );
// Stop loop
break;
}
}
}
}
}
add_action( 'woocommerce_thankyou', 'action_woocommerce_thankyou', 10, 1 );
Currently it is just checking if the value exists, to compare it effectively
Change
// NOT empty
if ( ! empty ( $value ) ) {
// Update status
$order->update_status( 'cancelled' );
// Stop loop
break;
}
To
// Compare
if ( $value == 'some value' ) ) {
// Update status
$order->update_status( 'cancelled' );
// Stop loop
break;
}
I need help with dokan actions on Orders page. Currently here i have two actions, to mark order complete or processing. What I want is to create action to mark order cancelled and on-hold.
I have accessed file that contains these two actions and they are in file Ajax.php:
add_action( 'wp_ajax_dokan-mark-order-complete', array( $this, 'complete_order' ) );
add_action( 'wp_ajax_dokan-mark-order-processing', array( $this, 'process_order' ) );
Is there a way to define similar action:
add_action( 'wp_ajax_dokan-mark-order-cancelled', array( $this, 'cancel_order' ) );
?
I already made a custom action that mark the order as shipped(custom status):
first you can add the below to include/Ajax.php > in the __construct function.
add_action( 'wp_ajax_dokan-mark-order-shipped', array( $this, 'ship_order' ) );
Then you have to include the custom function (in the same file):
public function ship_order() {
if ( ! is_admin() ) {
die();
}
if ( ! current_user_can( 'dokandar' ) || 'on' != dokan_get_option( 'order_status_change', 'dokan_selling', 'on' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'dokan-lite' ) );
}
if ( ! check_admin_referer( 'dokan-mark-order-shipped' ) ) {
wp_die( esc_html__( 'You have taken too long. Please go back and retry.', 'dokan-lite' ) );
}
$order_id = ! empty( $_GET['order_id'] ) ? intval( $_GET['order_id'] ) : 0;
if ( ! $order_id ) {
die();
}
if ( ! dokan_is_seller_has_order( dokan_get_current_user_id(), $order_id ) ) {
wp_die( esc_html__( 'You do not have permission to change this order', 'dokan-lite' ) );
}
$order = dokan()->order->get( $order_id );
$order->update_status( 'wc-completed' );
wp_safe_redirect( wp_get_referer() );
die();
}
then make sure that you modify the URL :
'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=dokan-mark-order-shipped&order_id=' . dokan_get_prop( $order, 'id' ) ), 'dokan-mark-order-shipped' ),
Please let me know if you need any help
I've got this custom code working to add a couple of radio buttons into the checkout:
add_action( 'woocommerce_review_order_before_payment', 'display_extra_fields_after_billing_address' , 10, 1 );
function display_extra_fields_after_billing_address () { ?>
<h3 class="delivery-options-heading">Delivery Options <sup>*</sup></h3>
<div class="delivery-options">
<p><input type="radio" name="delivery_option" value="Have the courier leave a card." required /> Have the courier leave a card.</p>
<p><input type="radio" name="delivery_option" value="Leave the consignment without a signature." required /> Leave the consignment without a signature.</p>
</div>
<?php
}
add_action( 'woocommerce_checkout_update_order_meta', 'add_delivery_option_to_order' , 10, 1);
function add_delivery_option_to_order ( $order_id ) {
if ( isset( $_POST ['delivery_option'] ) && '' != $_POST ['delivery_option'] ) {
add_post_meta( $order_id, '_delivery_option', sanitize_text_field( $_POST ['delivery_option'] ) );
}
}
add_filter( 'woocommerce_email_order_meta_fields', 'add_delivery_option_to_emails' , 10, 3 );
function add_delivery_option_to_emails ( $fields, $sent_to_admin, $order ) {
if( version_compare( get_option( 'woocommerce_version' ), '3.0.0', ">=" ) ) {
$order_id = $order->get_id();
} else {
$order_id = $order->id;
}
$delivery_option = get_post_meta( $order_id, '_delivery_option', true );
if ( '' != $delivery_option ) {
$fields[ 'Delivery Date' ] = array(
'label' => __( 'Delivery Option', 'delivery_option' ),
'value' => $delivery_option,
);
}
return $fields;
}
Every part of this works fine except for the required attributes on the radio buttons, letting the user checkout without making a choice, which is not ideal. For some reason the required attributes are being ignored.
How can I force the user to select one of the radio buttons before checking out?
Please add this validation hook along with your code that will validate your option on checkout
function action_woocommerce_after_checkout_validation( $data, $errors ) {
if( !isset( $data['delivery_option'] ) || empty( $data['delivery_option'] ) ) :
$errors->add( 'required-field', __( 'No delivery option has been selected. Please choose delivery option.', 'woocommerce' ) );
endif; //Endif
}
// add the action
add_action( 'woocommerce_after_checkout_validation', 'action_woocommerce_after_checkout_validation', 10, 2 );
This was the solution, slightly different to what Krunal Prajapati suggested. The only difference is the second line:
function action_woocommerce_after_checkout_validation( $data, $errors ) {
if ( empty( $_POST['delivery_option'] ) ) :
$errors->add( 'required-field', __( 'No delivery option has been selected. Please choose delivery option.', 'woocommerce' ) );
endif; //Endif
}
// add the action
add_action( 'woocommerce_after_checkout_validation', 'action_woocommerce_after_checkout_validation', 10, 2 );
Actually I want customers to add unique-phone numbers in the billing address of woo-commerce. if any tries to add / update already existed phone numbers then it should throw an error.
I tried the below code but it is not working. Can anyone give me the correct solution for unique phone numbers in the Woocommerce billing address?
add_filter( 'update_user_meta', 'ts_unique_wc_phone_field');
function ts_unique_wc_phone_field( $errors ) {
if ( isset( $_POST['billing_phone'] ) ) {
$hasPhoneNumber= get_users('meta_value='.$_POST['billing_phone']);
if ( !empty($hasPhoneNumber)) {
$errors->add( 'billing_phone_error', __( '<strong>Error</strong>: Mobile number is already used!.', 'woocommerce' ) );
}
}
return $errors;
}
Your get_users call is wrong. Use
$hasPhoneNumber = get_users(array(
'meta_key' => 'billing_phone',
'meta_value' => $_POST['billing_phone'],
)
);
Careful: you did not mention your meta key in your post. This might be something else than 'billing_phone'. Adapt it as necessary.
This will however allow users to do shenanigans like adding a space/-/+ or something like that to the phone number, reusing it. This might need a function to filter out redundant characters upon meta value insertion, and apply the same function to $_POST['billing_phone'] before the meta query for get_users.
I have setup on one of my sites the same code within two (2) functions - one for woocommerce -> my account, and one at the checkout which checks for validity of the phone number provided specific to my country, and the other to check if the phone number already exists.
add_action( 'woocommerce_save_account_details_errors', 'wc_myaccount_validate_billing_phone', 20, 1); // My Account
function wc_myaccount_validate_billing_phone( $args ){
if ( isset ( $_POST['billing_phone'] ) && !empty ( $_POST['billing_phone'] ) ) {
if ( !preg_match( '/^04[0-9]{8}$/D', str_replace( ' ', '', $_POST['billing_phone'] ) ) ) {
wc_add_notice( __( '<strong>Billing Mobile Phone</strong> is invalid (Example: 0412 345 678).' ), 'error' );
}
$existing_billing_phone = get_users( 'meta_value=' . str_replace( ' ', '', $_POST['billing_phone'] ) );
$current_user = wp_get_current_user();
if ( !empty ( $existing_billing_phone ) ) {
if ( $current_user->billing_phone != str_replace( ' ', '', $_POST['billing_phone'] ) ) {
wc_add_notice( __( '<strong>Billing Mobile Phone</strong> already exists.' ), 'error' );
}
else {
return;
}
}
}
}
add_action('woocommerce_checkout_process', 'wc_checkout_validate_billing_phone'); // Checkout
function wc_checkout_validate_billing_phone() {
if ( isset( $_POST['billing_phone'] ) && !empty( $_POST['billing_phone'] ) ) {
if ( !preg_match('/^04[0-9]{8}$/D', str_replace(' ', '', $_POST['billing_phone'] ) ) ) {
wc_add_notice( __( '<strong>Billing Mobile Phone</strong> is invalid (Example: 0412 345 678).' ), 'error' );
}
$existing_billing_phone = get_users( 'meta_value=' . str_replace(' ', '', $_POST['billing_phone'] ) );
$current_user = wp_get_current_user();
if ( !empty( $existing_billing_phone ) ) {
if ( $current_user->billing_phone != str_replace(' ', '', $_POST['billing_phone'] ) ) {
wc_add_notice( __( '<strong>Billing Mobile Phone</strong> already exists.' ), 'error' );
}
else {
return;
}
}
}
}
As I want to save all phone numbers as 0412345678 (no spaces) and some people enter phone numbers as 0412 345 678, the str_replace() removes this prior to saving.
add_action( 'woocommerce_checkout_update_user_meta', 'wc_checkout_save_billing_phone' );
function wc_checkout_save_billing_phone( $user_id ) {
if ( $user_id && $_POST['billing_phone'] ) {
update_user_meta( $user_id, 'billing_phone', str_replace(' ', '', $_POST['billing_phone'] ) );
}
}
While I have not tested this next part yet, and this example is referenced from this link, if you are wanting to update the admin user area you may want to use something like this.
add_action( 'show_user_profile', 'wc_checkout_validate_billing_phone', 10 );
add_action( 'edit_user_profile', 'wc_checkout_validate_billing_phone', 10 );
Below is a screenshot of the results of trying to change my phone number to one that already exists while in the woocommerce->my account section.