a week ago I've started to build a WooCommerce store which should have the option to share orders between two customers. Here is a little diagram I've made for you so you can understand how it should be (Please take a look at it):
So here is what I did step for step
User B clicks a button -> (Save second user id to the order):
$related_order = $_POST['related_order'];
$current_user = wp_get_current_user();
$userID = $current_user->ID;
update_post_meta($related_order, 'second_user_id', $userID);
User B clicks another button -> (Remove second user id from the order)
$related_order = $_POST['related_order'];
$current_user = wp_get_current_user();
$user_id = $current_user->ID;
delete_post_meta($related_order , 'second_user_id');
These two steps (show and hide the order for user B) are working fine - I've tested it. The field gets set and unset.
Now it's getting worse:
I've searched a lot and asked some people to find the function in WooCommerce which gets all orders for a customer. After I found it I've tried to change it so that the second_user_id field gets checked to. I simply thought that I can show the order from customer A to B this way:
function woocommerce_account_orders( $current_page ) {
$current_page = empty( $current_page ) ? 1 : absint( $current_page );
$customer_orders = wc_get_orders( apply_filters( 'woocommerce_my_account_my_orders_query', array(
'customer' => get_current_user_id(),
'second_user_id' => get_current_user_id(),
'page' => $current_page,
'paginate' => true,
) ) );
wc_get_template(
'myaccount/orders.php',
array(
'current_page' => absint( $current_page ),
'customer_orders' => $customer_orders,
'has_orders' => 0 < $customer_orders->total,
)
);
}
The method is located in: https://docs.woocommerce.com/wc-apidocs/source-function-woocommerce_account_orders.html#2465-2486
As you can see I've tried to add the second_user_id to the query which returns the orders for a customer. This is my custom field saved in each order:
[5] => WC_Meta_Data Object (
[current_data:protected] => Array (
[id] => 3477
[key] => second_user_id
[value] => 2
)
After that did not worked I've got help from a friendly guy which told me, that this can't work the way I did it. There are permission which must be given to the user B to view the order from A and a lot of other stuff... So is there someone who can help me with this part? What is wrong with my implementation and how can I change the functionality the way I need it?
if we take look at the responsbile function which WooCommerce use to query the customer you will WooCommerceselect all orders which related to the user based on the user id.
Functions source code:
function woocommerce_account_orders( $current_page ) {
$current_page = empty( $current_page ) ? 1 : absint( $current_page );
$customer_orders = wc_get_orders( apply_filters( 'woocommerce_my_account_my_orders_query', array(
'customer' => get_current_user_id(),
'page' => $current_page,
'paginate' => true,
) ) );
wc_get_template(
'myaccount/orders.php',
array(
'current_page' => absint( $current_page ),
'customer_orders' => $customer_orders,
'has_orders' => 0 < $customer_orders->total,
)
);
}
}
If you want to alter this query and add specific order id's which is not belong to this user beside the default orders we need to do some sort of hack to this query and the possible option i can think of now is by using post__in instead of getting the orders by customer id.
we are going to use the woocommerce_my_account_my_orders_query filter to alter the query.
i will assume the additional orders for that user is stored in wp_usermeta table
The Solution will be:
add_filter( 'woocommerce_my_account_my_orders_query', 'modify_my_order_query' , 20, 1 );
function modify_my_order_query( $q ) {
global $wpdb;
$current_user_id = get_current_user_id();
//Check if the user have addtional orders
$secondary_post_ids = get_user_meta( $current_user_id, 'additional_orders', false );
if ( ! empty( $secondary_post_ids ) ) {
// Get All Orders ID for the main customers
$prepare_query = $wpdb->prepare(
" SELECT post_id FROM {$wpdb->postmeta}
WHERE meta_key LIKE %s and meta_value LIKE %d ",
'_customer_user',
$current_user_id
);
$results = $wpdb->get_results( $prepare_query, ARRAY_A );
$main_post_ids = wp_list_pluck( $results, 'post_id' );
// Merge All Orders IDs
$all_posts_ids = ( isset( $secondary_post_ids[0] ) ) ? array_merge( $main_post_ids, $secondary_post_ids[0] ) : $main_post_ids;
//Modify the my Order Query to include all orders ID including the additional ones
unset( $q['customer'] );
$q['post__in'] = $all_posts_ids;
}
return $q;
}
you will see in the above solution first we check if the current user have additional order if yes then we select all his default orders id and merge the array with his additional order is and then we modified the query so we can get all those orders.
Now the only things left is to allow the user to see his additional orders and to do so we need to use user_has_cap hook.
add_filter( 'user_has_cap', 'give_permissions' , 10, 3 );
function give_permissions( $allcaps, $cap, $args ) {
if ( isset( $cap[0] ) && $cap[0] == 'view_order' ) {
$get_additional_ids = get_user_meta( get_current_user_id(), 'additional_orders', false );
if ( isset( $get_additional_ids[0] ) && in_array( $args[2], $get_additional_ids[0] ) ) {
$allcaps[ $cap[0] ] = true;
}
}
return ( $allcaps );
}
i believe this solution will achieve your target goal.
Related
I have split up my code into part 1 , part 2, and part 3
part 1 and 2 are working just fine... they are checking post types and scanning for metadata
Its only Part 3 that I am completely missing the bill
/*remove Edit Trash, and quickedit of pages with specific metavalues for certain roles
*/
add_filter( 'page_row_actions', 'remove_page_row_actions', 10, 2 ); // 2, not 1
add_filter( 'post_row_actions', 'remove_page_row_actions', 10, 2 ); // 2, not 1
// pass $post object as second argument.
function remove_page_row_actions( $actions, $post ) {
//PART 1 SPECIFY USERS
global $current_user;
get_currentuserinfo();
if(
(
//specify roles except administrator (but you need to add child roles based off administrator ex. adminsub)
(
!current_user_can('administrator')
||
current_user_can('adminsub')
)
||
// specify a list of role(s)
(
current_user_can('adminsub')
||
current_user_can('author')
)
)
)
{
// PART 2 SEARCH WHICH TYPES OF POSTS TO GET A SPECIFIC METADATA FROM
$post_types_affected = array('page' , 'post'); // I ADDED POST HERE STILL WORKS
// print_r($post_types_affected); // use this to see which posts are affected
//only get post IDs for a metavalue which has ALL of the following EXACT values (I changed it to just 1)
//$array_of_values_to_match = array( '1', '1', '1' );
$array_of_values_to_match = array( 1 );
$metakey_to_match = 'checkboxmeta1';
$args = array(
'post_type' => get_post_types(),
'meta_query' => array(),
'numberposts' => -1,
);
// if there's more than 1 value, add the relation arg
if( 1 < count( $array_of_values_to_match ) ){
$args['meta_query']['relation'] = 'AND';
}
// for each of the array values, add a meta query for that value
foreach( $array_of_values_to_match as $val ){
$args['meta_query'][] = array(
'key' => $metakey_to_match,
'value' => $val,
'compare' => '='
);
}
$postlistarray = get_posts( $args );
//end of function which an array or post arrays which each contain a post ID and a metakey of 1
//must create an array column, becuase that is what $x = array(111,212) does
$post_ids_affected = array_column($postlistarray, 'ID');
//PART 3 check array_column for post IDs, and remove functionality below from posts which had the metadata
if ( in_array( $post->post_type, $post_types_affected )
&& in_array( $post->ID, $post_ids_affected ) ) {
unset( $actions['inline hide-if-no-js'] );
unset( $actions['edit'] );
unset( $actions['trash'] );
unset( $actions['pa_duplicator'] );
}
}
return $actions;
}
what I am trying to do is for these roles from 1 when interacting with posts from 2, I want to remove ALL EDIT and TRASH capabilities.
so obviously right now ive just removed some actions lol such as edit and quickedit.. but I just realized the user can just click on the page edit.php link, or even just change the url to soemthing like
https://mywebsite/wp-admin/post.php?post=2752&action=edit
or
remotely trash or edit it some other way through URL or other method..
how do I just completely remove the edit and trash capability of that role for part 3, so that its view only? and maybe view and comment moderation?
I hope I didnt waste alot of time by trying to hook into page_row_actions and post_row_actions lol... maybe I need to be putting this code into like admin_init or something but I am sort of a noob lol
after about 8 hours.. I realized a plugin call advanced access manager, which is free, has 2 toggle checkboxes on each page for this exactly lol.
why reinvent the wheel I Guess
please could you help me to find
how to edit automatic order notes generated by system (related to items changed through the order ) i couldn't find it's file
what i want to do
add quantity next to deleted item ( if i have 10 items in order and i have deleted 3 i want to see number of deleted in order notes
add total after deleted or added
Try this:
Solution 1:
add order note from checkout
add_filter( 'woocommerce_checkout_fields', 'woo_custom_order_notes_checkout_fields' );
function woo_custom_order_notes_checkout_fields( $fields )
{
$fields['order']['order_comments']['placeholder'] = 'Your Special notes';
$fields['order']['order_comments']['label'] = 'Add your special note txt';
return $fields;
}
When you place an order with custom text and after the order is placed you will be able to see custom order notes in order detail/edit orders on the dashboard.
Solution 2:
Get/ print order all notes on the order view/edit page after clicking on the update button.
// order comment/notes by id
function woo_get_comment_by_id( $comment_id ) {
$comment = get_comment( intval( $comment_id ) );
if ( ! empty( $comment ) ) {
return $comment->comment_content;
} else {
return '';
}
}
add_action( 'save_post_shop_order', 'wpo_wcol_order_notes', 10, 1 );
function wpo_wcol_order_notes ( $order_id ) {
$args = array(
'post_id' => $order_id,
'orderby' => 'comment_ID',
'order' => 'DESC',
'type' => 'order_note',
// 'number' => 1
);
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
$notes = get_comments( $args );
if($notes){
foreach($notes as $note){
$comment_id = $note->comment_ID ?:0;
if( $comment_id ){
echo woo_get_comment_by_id( $comment_id ).'<br>';
}
}
}
exit; // after test you should removed
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
}
You can also change the action "save_post_shop_order" accordingly as per requirements.
In WooCommerce I have a specific product which price is set to 0 (the product id is 2938).
When adding that product to cart via href="http://yourdomain.com/?add-to-cart=2938", I'm trying to set the price dynamically based on the current user's custom post (it's a somewhat complicated calculation based on what posts the user has created, otherwise i'd just use groups/bundles.)
Based on "Change cart item prices in Woocommerce 3" answer code, here's what I've got:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price', 20, 1);
function add_custom_price( $cart ) {
// ID of the product I've set up
$product_id_to_change = 2938;
$user_id = get_current_user_id();
$studio_args = array(
'post_type' => 'studio',
'post_status' => 'published',
'posts_per_page' => 1,
'author' => $user_id
);
$studio_query = new WP_Query( $studio_args );
foreach( $studio_query as $studio ) {
$studio_listing_name = get_the_title();
$studio_listing_option = get_post_meta( $studio->ID, 'wpcf-listing-option', true );
}
if ( $studio_listing_option = array( 1, 2 ) ) {
$new_price = 180;
} elseif ( $studio_listing_option = array( 3, 4 ) ) {
$new_price = 345;
} elseif ( $studio_listing_option = array( 5, 6, 7 ) ) {
$new_price = 690;
}
$new_name = 'Payment for 2020 Listing: ' . $user_id . ' - ' . $studio_listing_name . ' - ' . 'Listing Option ' . $studio_listing_option;
// This is necessary for WC 3.0+
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Avoiding hook repetition (when using price calculations for example)
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $item ) {
$item['data']->set_price( $new_price );
$item['data']->set_name( $new_name );
}
}
Important to note that each user can only have 1 studio. I think my foreach and if ( $studio_listing_option = array( 1, 2 ) ) check is wrong/can probably be done better. Any ideas on how I can get it working better?
How can I restrict the calculation to just product ID 2938?
Also, $studio_listing_name returns blank, and $studio_listing_option returns an array. Can I improve on that to get it working properly?
First, it's difficult to help as we don't know anything about your "studio" custom post type and how the data is set on it for the customers.
There are some mistakes and missing things in your code like in your IF statements, where you should need to use in_array() conditional function.
I have tried to guess how the data is set in wpcf-listing-option meta data for your studio custom post type and I suppose that is a number between 1 (one) and 7 (seven).
Also in your code, when you loop through $studio_query, for $studio_listing_name and $studio_listing_option you will always get the values from the last item from the loop… so there's something wrong in your logic.
In the following code I am targeting your specified product Id only (without any guaranty, it should help even if it doesn't work completely):
add_action( 'woocommerce_before_calculate_totals', 'customize_cart_item_details', 20, 1);
function customize_cart_item_details( $cart ) {
// This is necessary for WC 3.0+
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Avoiding hook repetition (when using price calculations for example)
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 || ! is_user_logged_in() )
return;
// ID of the product I've set up
$defined_product_id = 2938;
$customer_id = get_current_user_id();
// Get "studio" custom post objects
$studio_query = get_posts( array(
'post_type' => 'studio',
'post_status' => 'published',
'posts_per_page' => 1,
'author' => $customer_id
) );
// Get the first "studio" post for the current user
$studio = reset( $studio_query );
$studio_name = $studio->post_title;
$studio_option = get_post_meta( $studio->ID, 'wpcf-listing-option', true );
if ( in_array( $studio_option, array( 1, 2 ) ) ) {
$new_price = 180;
} elseif ( in_array( $studio_option, array( 3, 4 ) ) ) {
$new_price = 345;
} elseif ( in_array( $studio_option, array( 5, 6, 7 ) ) ) {
$new_price = 690;
}
$new_name = sprintf(
__( "Payment for 2020 Listing: %s - %s - Listing Option %s", "woocommerce" ),
$customer_id, $studio_name, $studio_option
);
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
// Only for the defined product ID (or variation ID)
if ( in_array( $defined_product_id, [ $cart_item['product_id'], $cart_item['variation_id'] ] ) ) {
$cart_item['data']->set_price( $new_price );
$cart_item['data']->set_name( $new_name );
}
}
}
I'm trying to implement a feature where customers receive a new user role after a certain amount of orders have been made, but all those orders must have been made within the same year.
I was able to assign the user role based on nth number of orders but can't seem to go beyond to where the date needs to be taken into consideration, can someone point me in the right direction or point out what I might be missing.
This is what I have tried so far.
function change_user_role_on_order_status_completed( $order_id ) {
$order = new WC_Order( $order_id );
$user_id = $order->user_id;
$order_this_year = false;
$current_date = date('Y');
$total_orders = get_posts( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => $user_id,
'post_type' => 'shop_order',
) );
if ( $total_orders > 1 ) {
foreach ($order->get_items() as $item_key => $item_values):
// Get the item date
if ($item_date = $item_values->get_date_completed()->format ('Y') == $current_date) {
$order_this_year = true;
}
endforeach;
if ($order_this_year) {
$user = new WP_User( $order->user_id );
// Set role editor
$user->set_role( 'customer_club' );
}
}
}
add_action( 'woocommerce_order_status_completed', 'change_user_role_on_order_status_completed', 10, 1 );
Using WC_Order_Query here is a much lighter and simple way to change the user role based on yearly orders count:
add_action( 'woocommerce_order_status_completed', 'change_user_role_on_order_status_completed', 10, 2 );
function change_user_role_on_order_status_completed( $order_id, $order ) {
// Here set the minimal order count
$min_orders_count = 3;
// The WC_Order_Query
$queried_orders = wc_get_orders( array(
'limit' => -1,
'customer_id' => $order->get_customer_id(),
'date_paid' => '>=' . date('Y') . '-01-01', // or 'date_created'
'return' => 'ids'
) );
if ( sizeof($queried_orders) >= $min_orders_count ) {
// Get an instance of the customer WP_User Object
$user = $order->get_user();
// Change the user role
$user->set_role( 'customer_club' );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Because of a project I need help from you. I've searched a lot but I can't find a solution.
I'm trying to edit a WooCommerce function named
woocommerce_account_orders
I've added the field
mycustom_id
to the the orders meta-data object because I need to get all orders which has the current logged in user in the field mycustom_id:
(mycustom_id = current_user_id())
The check for the customer should stay. I just need to add this other current_user_id check.
This sould stay as it is:
'customer' => get_current_user_id()
. This is my not working code snippet:
function woocommerce_account_orders( $current_page ) {
$current_page = empty( $current_page ) ? 1 : absint( $current_page );
$customer_orders = wc_get_orders( apply_filters( 'woocommerce_my_account_my_orders_query', array(
'customer' => get_current_user_id(),
'mycustom_id' => get_current_user_id(),
'page' => $current_page,
'paginate' => true,
) ) );
wc_get_template(
'myaccount/orders.php',
array(
'current_page' => absint( $current_page ),
'customer_orders' => $customer_orders,
'has_orders' => 0 < $customer_orders->total,
)
);
}
The method is located in: https://docs.woocommerce.com/wc-apidocs/source-function-woocommerce_account_orders.html#2465-2486
How can I add this feature to the function a smart way like a filter and how can I pass my custom parameter the right way to the function? I've saved the parameter as an order_meta attribute:
[5] => WC_Meta_Data Object (
[current_data:protected] => Array (
[id] => 3477
[key] => mycustom_id
[value] => 2
)
Thank you for your help. I've tried so much but I'm new in PHP and must lurn a lot..
if you want to change this query you can do it by calling the woocommerce_my_account_my_orders_query filter instead of modifying the core function so you can achieve the required desired as follow:
add_filter( 'woocommerce_my_account_my_orders_query', 'modify_my_order_query', 10, 1 );
function modify_my_order_query( $q ) {
$q['customer'] = [ 1, 3 ]; // you customers ids here
return $q;
}
but this function above will display the order in the list only and if the customer click on that order to see the details he will got an error message as he doesn't have the permission to view the order and you need to modify the view_order capability
for example you can give the user with ID 1 permission to view user ID 3 orders as follow:
function give_permissions( $allcaps, $cap, $args ) {
$user = 3; //user id
if ( $cap[0] == 'view_order' ) {
$allcaps[ $cap[0] ] = true;
}
return ( $allcaps );
}
add_filter( 'user_has_cap', 'give_permissions', 10, 3 );