tldr:// how to clone an existing WC_Order_Item_Product to a new order?
I have looked through a lot of existing subscription plugins but didnt really get warm with them. So I decided to create something as a learning project. Its not for a customer, I dont expect it to be commercially successful product, but I guess its good to step up my game.
So I have created a custom table already that saves target dates and would like to create new orders from the existing WC_Order_Item_Product-Objects. By simply adding the original Object it will just move them from the old order, which is something I do not want to happen. I wonder how I could 'clone' the object and remove the protected [order_id] which would be overridden by WC_Order->add_item anyway without altering the original database entries. Maybe thats the wrong approach or a bad idea?
I have also tried to find some up2date hint on how to create a full custom WC_Order_Item_Product but was not really successful on that.
What I got atm:
function update ($reminder_id = null) {
global $woocommerce;
global $wpdb;
global $aboprodukt_table_name;
// TODO if reminder id
if ( ! $reminder_id) {
$neworders = [];
// get all reminders from db and loop through them
$aboprodukt_reminders = $wpdb->get_results( 'SELECT * FROM ' . $aboprodukt_table_name . ' ORDER BY ' . $aboprodukt_table_name . '.targetdate ASC');
foreach ($aboprodukt_reminders as $item ) {
// get original order item object to abstract
$order_item = new \WC_Order_Item_Product($item->orderitem);
// get order object
$order = $order_item->get_order();
// get customer id
$customer = $order->get_customer_id();
// TODO abstract order items
// assign user, adresses if not existing yet, copy from last order
if ( ! $neworders[$customer] ) {
// check all reminders
$customer_array = $order->get_data();
$args = array(
'customer_id' => $customer,
'created_via' => 'Aboprodukt',
'add_order_note' => __('Aboprodukt Custom Order', 'aboprodukt'),
);
$neworders[$customer] = \wc_create_order($args);
// TODO Shipping
}
// add order items
$neworders[$customer]->add_item($order_item);
$neworders[$customer]->calculate_totals();
$neworders[$customer]->save();
}
return print_r($neworders,true);}
Not sure if it helps so. but this is the way I've managed to do so.
For understanding: I got an extra "reminder"-table in the database that stores: date, order-item-id. It is removed after its used to create a new order.
function update ($reminder_id = null) {
global $woocommerce;
global $wpdb;
global $aboprodukt_table_name;
// TODO if order id
if ( ! $reminder_id) {
$neworders = [];
// get all reminders from db and loop through them
$aboprodukt_reminders = $wpdb->get_results( 'SELECT * FROM ' . $aboprodukt_table_name . ' ORDER BY ' . $aboprodukt_table_name . '.targetdate ASC');
foreach ($aboprodukt_reminders as $item ) {
// get original order item object to abstract
$order_item = new \WC_Order_Item_Product($item->orderitem);
$order = $order_item->get_order();
$customer = $order->get_customer_id();
// assign user, adresses if not existing yet, copy from last order
if ( ! $neworders[$customer] ) {
// check all reminders
$args = array(
'customer_id' => $customer,
'created_via' => 'Aboprodukt',
'add_order_note' => __('Aboprodukt Custom Order', 'aboprodukt'),
);
$neworders[$customer] = \wc_create_order($args);
// order
$customer_object = new \WC_Customer( $customer );
$types = array('billing','shipping');
foreach ($types as $type) {
foreach ( $customer_object->{"get_{$type}"}() as $key => $value) {
if ( ! empty($value) ) {
if ( is_callable( array( $neworders[$customer], "set_{$type}_{$key}" ) ) ) {
$neworders[$customer]->{"set_{$type}_{$key}"}( $value );
}
}
}
}
// Set Shipping
$shippingold = $order->get_shipping_methods();
$oldshippingitem = new \WC_Order_Item_Shipping( reset($shippingold) );
$newshippingitem = new \WC_Order_Item_Shipping();
$newshippingitem->set_method_id( $oldshippingitem->get_method_id() );
$newshippingitem->set_instance_id( $oldshippingitem->get_instance_id() );
$newshippingitem->set_method_title( $oldshippingitem->get_method_title() );
$newshippingitem->set_total( $oldshippingitem->get_total() );
$neworders[$customer]->add_item( $newshippingitem );
}
// if variation_id > 0 then is simple product, get product
$item_variation_id = $order_item->get_variation_id();
$item_id = $neworders[$customer]->add_product(
wc_get_product(isset( $item_variation_id ) && $item_variation_id > 0 ? $item_variation_id : $order_item->get_product_id() ),
$order_item->get_quantity()
);
// copy metadata for future use
$neworderitem = new \WC_Order_Item_Product($item_id);
$neworderitem->add_meta_data( '_aboprodukt_order', $order_item->get_meta('_aboprodukt_order'), true );
$neworderitem->add_meta_data( '_aboprodukt_timespan', $order_item->get_meta('_aboprodukt_timespan'), true );
$neworderitem->add_meta_data( '_aboprodukt_type', $order_item->get_meta('_aboprodukt_type'), true );
$neworderitem->save();
$neworders[$customer]->calculate_totals();
$neworders[$customer]->save();
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $aboprodukt_table_name
WHERE id = %d",
$item->id
)
);
}
}
// send emails
foreach ( $neworders as $customers => $neworder ) {
$mailer = \WC()->mailer();
\WC()->mailer()->emails['WC_Email_Customer_Invoice']->trigger($neworder->get_id());
$neworder->add_order_note( sprintf( __('%s email notification manually sent.', 'woocommerce'), $mail->title), false, true);
}
return $neworders;
}
Related
I want to add a new custom filter in WordPress network admin
restrict_manage_users for network admin in the user panel
I want a custom filter option not a custom column, i just want to add one filter dropdown with roles
You will need to hook on the ‘restrict_manage_posts‘ filters to add your filter (dropdown) and to ‘parse_query‘ to alter the query according to the filter selection.
For example, if you want to add a filter to display a list of years for the user to select, add the following code in your functions.php file:
if (is_admin()){
//this hook will create a new filter on the admin area for the specified post type
add_action( 'restrict_manage_posts', function(){
global $wpdb, $table_prefix;
$post_type = (isset($_GET['post_type'])) ? quote_smart($_GET['post_type'], true) : 'post';
//only add filter to post type you want
if ($post_type == 'YOUR_POST_TYPE_HERE'){
//query database to get a list of years for the specific post type:
$values = array();
$query_years = $wpdb->get_results("SELECT year(post_date) as year from ".$table_prefix."posts
where post_type='".$post_type."'
group by year(post_date)
order by post_date");
foreach ($query_years as &$data){
$values[$data->year] = $data->year;
}
//give a unique name in the select field
?><select name="admin_filter_year">
<option value="">All years</option>
<?php
$current_v = isset($_GET['admin_filter_year'])? $_GET['admin_filter_year'] : '';
foreach ($values as $label => $value) {
printf(
'<option value="%s"%s>%s</option>',
$value,
$value == $current_v? ' selected="selected"':'',
$label
);
}
?>
</select>
<?php
}
});
//this hook will alter the main query according to the user's selection of the custom filter we created above:
add_filter( 'parse_query', function($query){
global $pagenow;
$post_type = (isset($_GET['post_type'])) ? quote_smart($_GET['post_type'], true) : 'post';
if ($post_type == 'YOUR_POST_TYPE_HERE' && $pagenow=='edit.php' && isset($_GET['admin_filter_year']) && !empty($_GET['admin_filter_year'])) {
$query->query_vars['year'] = $_GET['admin_filter_year'];
}
});
}
Using this technique you can actually add any filter you want.
I hope this will solve your problem.
add_filter( 'wpmu_users_columns', 'my_awesome_new_column' );
add_action( 'manage_users_custom_column', 'my_awesome_column_data', 10, 3 );
// Creates a new column in the network users table and puts it before a chosen column
function my_awesome_new_column( $columns ) {
return my_awesome_add_element_to_array( $columns, 'my-awesome-column', 'Awesome', 'registered' );
}
// Adds data to our new column
function my_awesome_column_data( $value, $column_name, $user_id ) {
// If this our column, we return our data
if ( 'my-awesome-column' == $column_name ) {
return 'Awesome user ID ' . intval( $user_id );
}
// If this is not any of our custom columns we just return the normal data
return $value;
}
// Adds a new element in an array on the exact place we want (if possible).
function my_awesome_add_element_to_array( $original_array, $add_element_key, $add_element_value, $add_before_key ) {
// This variable shows if we were able to add the element where we wanted
$added = 0;
// This will be the new array, it will include our element placed where we want
$new_array = array();
// We go through all the current elements and we add our new element on the place we want
foreach( $original_array as $key => $value ) {
// We put the element before the key we want
if ( $key == $add_before_key ) {
$new_array[ $add_element_key ] = $add_element_value;
// We were able to add the element where we wanted so no need to add it again later
$added = 1;
}
// All the normal elements remain and are added to the new array we made
$new_array[ $key ] = $value;
}
// If we failed to add the element earlier (because the key we tried to add it in front of is gone) we add it now to the end
if ( 0 == $added ) {
$new_array[ $add_element_key ] = $add_element_value;
}
// We return the new array we made
return $new_array;
}
Here is an example : https://wordpress.stackexchange.com/questions/299801/custom-column-under-all-users-multisite-network-admin
I have a site that has tens of thousands of orders, which I need to compare the billing and customer/user emails and show a flag if they don't match. One of the stipulations is that I'm unable to add any metadata to the orders. So my solution is to just add a custom column, and compare the emails on the fly when the orders list is rendered. That works just fine.
add_filter( 'manage_edit-shop_order_columns', 'mismatched_orders_column' );
function mismatched_orders_column( $columns ) {
$columns['mismatched'] = 'Mismatched';
return $columns;
}
add_action( 'manage_shop_order_posts_custom_column', 'mismatched_orders_column_data' );
function mismatched_orders_column_data( $column ) {
global $post;
if ( 'mismatched' === $column ) {
$order = new WC_Order( $post->ID );
$customer = $order->get_user();
$result = '';
$billing_email = strtolower ( $order->get_billing_email() );
$customer_email = '';
if ($customer) $customer_email = strtolower ( $customer->user_email );
if ( $customer && ( $billing_email != $customer_email ) ) {
$result = '<span class="mismatched-order" title="Possible order mismatch">Yes</span>';
}
echo $result;
}
}
My issue is when trying to add sorting. Because I'm not accessing any post metadata, I don't have any easy data to sort via the main query. My solution here was originally to hook into pre_get_posts, grab all the orders in a new WP_Query, then loop through them and add the ones that had mismatched emails to an array for use in post__in.
This works/worked fine on my small dev site, but throws fatal memory errors when trying to loop over any more than about 8 or 9 thousand posts (out of a total of 30-40 thousand). Increasing memory isn't really an option.
add_filter( 'manage_edit-shop_order_sortable_columns', 'mismatched_orders_column_sortable');
function mismatched_orders_column_sortable( $columns ) {
$columns['mismatched'] = 'mismatched';
return $columns;
}
add_action( 'pre_get_posts', 'mismatched_emails_posts_orderby' );
function mismatched_emails_posts_orderby( $query ) {
if( ! is_admin() || ! $query->is_main_query() ) {
return;
}
//Remove the pre_get_posts hook so we don't get stuck in a loop
add_action( 'pre_get_posts', 'mismatched_emails_posts_orderby' );
//Make sure we're only looking at our custom column
if ( 'mismatched' === $query->get( 'orderby') ) {
//Set our initial array for 'post__in'
$mismatched = array();
$orders_list = get_posts(array(
'post_type' => 'shop_order',
'posts_per_page' => -1,
'post_status' => 'any',
'fields' => 'ids'
));
//And here is our problem
foreach( $orders_list as $order_post ) :
//Get our order and customer/user object
$order_object = new WC_Order( $order_post );
$customer = $order_object->get_user();
//Check that billing and customer emails don't match, and also that we're not dealing with a guest order
if ( ( $order_object->get_billing_email() != $customer->user_email ) && $order_object->get_user() != false ) {
$mismatched[] = $order_post;
}
endforeach; wp_reset_postdata();
$query->set( 'post__in', $mismatched );
}
}
I would seriously appreciate any insight into how I could either reduce the expense of the query I'm trying to run, or an alternate approach. Again, just for clarification, adding metadata to the orders isn't an option.
Thanks!
I'm using this code to generate an iframe if a customer has bought product 13372 and it works great:
<?php
// Get the current user data:
$user = wp_get_current_user();
$user_id = $user->ID; // Get the user ID
$customer_email = $user->user_email; // Get the user email
// OR
// $customer_email = get_user_meta( $user->ID, 'billing_email', true ); // Get the user billing email
// The conditional function (example)
// IMPORTANT: $product_id argument need to be defined
$product_id = 13372;
if( wc_customer_bought_product( $customer_email, $user_id, $product_id ) ) {
echo "You have additional listings!";
//iFrame goes here
} else {
echo "You have no additional listings.";
}
?>
Now I need to modify this to check how many times a user bought product ID 13372 and output that many number of iframes. If bought 3 times, output 3 iframes. I'm assuming a foreach loop, but what I've tried doesn't work. I followed this post: How to check how many times a product has been bought by a customer
But the example it doesn't return anything for me, not sure why!
Please try the following. We're looping through each completed shop order of a specific customer and putting all IDs of purchased products into an array. We then loop through that array to see how many times a specific ID appears. We store that value in a $count variable, which is used to determine how many times to output an iframe.
I've commented the code and made function and variable names as clear as possible.
<?php
function so58032512_get_product_purchase_quantity( $product_id ) {
// Array to hold ids of all products purchased by customer
$product_ids = [];
// Get all customer orders
$customer_orders = get_posts( array(
'numberposts' => - 1,
'meta_key' => '_customer_user',
'meta_value' => get_current_user_id(),
'post_type' => 'shop_order', // WC orders post type
'post_status' => 'wc-completed' // Only orders with status "completed"
) );
// Loop through each of this customer's order
foreach ( $customer_orders as $customer_order ) {
$order = wc_get_order( $customer_order );
$items = $order->get_items();
// Loop through each product in order and add its ID to $product_ids array
foreach ( $items as $item ) {
$id = $item['product_id'];
array_push( $product_ids, $id );
}
}
// Variable to count times an ID exists in the $product_ids array
$count = 0;
// Loop through all of the IDs in our $product_ids array
foreach ( $product_ids as $key => $value ) {
// Every time the ID that we're checking against appears in the array
if ( $value == $product_id ) {
// Increment our counter
$count ++;
}
}
// Return the counter value that represents the number of times the
// customer bought a product with our passed in ID ($product_id)
return $count;
}
// Get the current user data:
$user = wp_get_current_user();
$user_id = $user->ID; // Get the user ID
$customer_email = $user->user_email; // Get the user email
// IMPORTANT: $product_id argument need to be defined
$product_id = 13372;
if ( wc_customer_bought_product( $customer_email, $user_id, $product_id ) ) {
$x = 1;
$number_of_iframes_to_show = so58032512_get_product_purchase_quantity( $product_id );
echo 'The customer bought product with ID ' . $product_id . ' ' . $number_of_iframes_to_show . ' time(s).<br>';
while ( $x <= $number_of_iframes_to_show ) {
echo "<br>Here's an iframe!<br>";
$x ++;
}
} else {
echo "You have no additional listings.";
}
?>
I have 2 custom content types created by Pods CMS: fights and events. In the fights content type there is a relationship field for the event. How would I select all fights from the database by a specific event id? I tried querying the pods relationships table manually but that gave me incorrect results.
$fights = pods( 'fights' );
$params = array(
'where' => 'event.id = 3'
);
$fights->find( $params );
// loop through a while( $fights->fetch() ) and use $fights->field() to get the values of each field
That should do it, but you'll want to look at the 'find' documentation for your specific content type case as event.id may not be what you want (you may want event.ID).
http://pods.io/docs/code/pods/
http://pods.io/docs/code/pods/find/
http://pods.io/docs/code/pods/field/
http://pods.io/docs/code/pods/display/
Using this function you can easily display all individual relational fields with shortcode:
function get_pod_fields($atts) {
$a = shortcode_atts( array('field' => '', 'pod'=> '', 'relation'=> '', 'type'=>'', 'as'=>'url'), $atts );
$pod = pods( $a['pod'], get_the_id() );
$related = $pod->field( $a['relation']);
$field = get_post_meta( $related['ID'], $a['field'], true );
if($a['type'] == "image") {
if($a['as'] == "image") {
$field = '<img src="'.$field = $field['guid'].'"></img>';
} else {
$field = $field['guid'];
}
} else {
$field = $field;
}
return $field;
}
add_shortcode( 'podfields', 'get_pod_fields' );
[podfields field="profile_picture" type="image" pod="therapy" as="image" relation="therapist"]
I am building a drupal 7 (7.20) website with a "project" content type which contains multiple image fields (i do not want to use galleries).
I would like to be able to perform a mass delete operation on these fields from the node edit page. From what I have gathered I have to override theme_file_widget_multiple and return a tableselect instead of a table and then use a button plus javascript to delete the images from the database.
Am I missing something obvious ? This seems like alot of work for something so trivial.
EDIT:
I have had some progress with this:
function mytheme_file_widget_multiple($variables) {
$element = $variables ['element'];
$headers = array ();
$headers ['info'] = t ( '' );
$widgets = array ();
foreach ( element_children ( $element ) as $key ) {
$widgets [] = &$element [$key];
}
usort ( $widgets, '_field_sort_items_value_helper' );
$rows [] = array ();
$i = 0;
foreach ( $widgets as $key => &$widget ) {
// Save the uploading row for last.
if ($widget ['#file'] == FALSE) {
$widget ['#description'] = $element ['#file_upload_description'];
continue;
}
$operations_elements = array ();
foreach ( element_children ( $widget ) as $sub_key ) {
if (isset ( $widget [$sub_key] ['#type'] ) && $widget [$sub_key] ['#type'] == 'submit') {
$operations_elements [] = &$widget [$sub_key];
}
}
$information = drupal_render ( $widget );
$rows [$i ++] ['info'] = $information;
}
$output = '';
$output = drupal_render_children ( $element );
$form ['element'] = array (
'#type' => 'tableselect',
'#header' => $headers,
'#options' => $rows,
'#js_select' => TRUE,
'#empty' => t ( 'No data' ),
'#attributes' => array () );
$output .= empty ( $rows ) ? "" : drupal_render ( $form );
return $output;
}
This was included in my theme's template file however the tableselect does not have checkboxes. This is driving me crazy.. Can someone please tell me what I am doing wrong ?
From what I have gathered I have to create a module which overrides the fieldset preprocess functions..
Here are the references:
1) https://drupal.org/node/1905244
This article seems to try to do what you want to.
2) Also check out https://drupal.org/project/views_bulk_operations
this might be useful.
3) if not then you will need to make your own widget:
https://api.drupal.org/api/drupal/modules%21field%21field.module/group/field/7
https://drupal.org/project/examples ( has all the examples you will need )
all the best.