Get fields from checkout form into calculate_shipping - php

I'm creating a custom shipping calculator for my WooCommerce store. I need to collect some extra form data from the user to calculate the shipping properly.
I have the form fields added to woocommerce_checkout_after_customer_details. I can probably get this into the cart page without much trouble once I figure out the checkout page functionality, so let's focus on this.
I have a class extending WC_Shipping_Method with a calculate_shipping method a la this tutorial.
In that method, I want to use the extra form data (plus the destination) to calculate the shipping for that customer. Right now I just have this adding a dummy rate, which shows up.
I also created a custom cart-shipping.php file to not show any inputs for the available method in the order review cart, just the label and cost. Since the picking happens in my custom form, having options here is unnecessary.
Am I doing this right, or is this approach super hacky?
If this is the correct approach, how do I access the extra form fields inside the calculate_shipping method?
Have tried
Named the final options in my custom form shipping_method_* which triggers the wc-ajax=update_order_review call... which inits my custom shipping method class, but does not appear to ever call the calculate_shipping method anyhow. The only time this method seems to be called is when I'm actually adding a product to the order.
Also tried
Defined all of my possible delivery options and added them in calculate_shipping like $this->add_rate( $rate );. I think then I can somehow select one (force the user into one) when the cart updates via update_order_review ajax call? Again, the set option should be determined by these fields the user interacts with on the checkout form. But I haven't figured out how to set the shipping on that action yet.

calculate_shipping() is not always called
First off, you should know that the calculate_shipping() method of your shipping method class will only be called when the shipping rates are calculated for the first time (in the current WooCommerce session), each time after a product is added to the cart, and whenever the shipping address is changed — from the cart or checkout page.
But of course, a plugin or custom code can programmatically call the method (or re-calculate the rates) at any times. However, the default behavior is as follows:
Rates are stored in the session based on the package hash to avoid
re-calculation every page load.
And the calculate_shipping() method is executed by the WC_Shipping class through its calculate_shipping_for_package() method. In fact, the above quote was taken from the description of WC_Shipping::calculate_shipping_for_package() which calls WC_Shipping_Method::get_rates_for_package() and eventually the calculate_shipping() method in your own class.
How to get the submitted value of a form field in the checkout form from the calculate_shipping() method
So if you have this in the checkout form:
<input type="text" name="my_field"...>
Then you can get the submitted (POSTed) value using either the $_POST superglobal or the WC_Checkout::get_value() method:
$_POST['my_field']
WC()->checkout->get_value( 'my_field' )
It's as simple as that. :)
To ensure calculate_shipping is called
Run something similar to the following to clear out the session information, which will indicate that calculation still needs to be done. Be aware that if you do this on every page, it will mean that the shipping is constantly being recalculated when it doesn't need to be.
$packages = WC()->cart->get_shipping_packages();
foreach( $packages as $package_key => $package ) {
$session_key = 'shipping_for_package_'.$package_key;
$stored_rates = WC()->session->__unset( $session_key );
}

I'm not sure if this is the best way to accomplish this, but currently I am making this work by setting a session value on woocommerce_checkout_update_order_review and then accessing that value in my calculate_shipping method.

Looking at the WooCommerce documentation, it seems their desired way for you to access things such as user location from the calculate_shipping function is to use their Settings API. https://docs.woocommerce.com/document/settings-api/. Read the docs to see exactly how, but here is a simplification:
Read the location using
$this->init_settings();
$this->location = $this->settings['location'];
Write/Set the user's location with this table:
<?php function admin_options() {
?>
<h2><?php _e('You plugin name','woocommerce'); ?></h2>
<table class="form-table">
<?php $this->generate_settings_html(); ?>
</table><?php
}
The table would be made with
function init_form_fields() {
$this->form_fields = array(
'location' => array(
'title' => __( 'Location', 'woocommerce' ),
'type' => 'text',
'description' => __( 'This is the users address.', 'woocommerce' ),
'default' => __( 'none', 'woocommerce' )
)
);
}

Related

WooCommerce hook after order is updated?

Is there a hook I can use when I make a change to someone's order via the admin (such as their address, or a custom meta field)? I read this question but unfortunately woocommerce_process_shop_order_meta is fired before the order is saved, meaning I have no access to the newly updated data. What I need is to be able to use the new data that is saved to the order.
UPDATE: An issue with using save_post_shop_order is that the meta is updated before this is hit, so I can't compare the previously saved meta value, for example:
$metaArray = $_POST['meta'];
foreach($metaArray as $meta => $key) {
$metaArr[$key["key"]] = $key["value"];
}
$meta = get_post_meta($order->ID);
if($meta['coverstart'][0] != $metaArr['coverstart']) {
die("COVER START DATE HAS CHANGED");
}
The die() is never hit, because the script always gets the newly saved value.
Sorry but woocommerce_checkout_update_order_meta is fired after the order is saved… See this extract source code located in WC_Checkout create_order() method:
// Save the order.
$order_id = $order->save(); // <== Order is saved here before
do_action( 'woocommerce_checkout_update_order_meta', $order_id, $data ); <== // The hook
return $order_id;
So in woocommerce_checkout_update_order_meta you can get the saved order data:
by retrieving the WC_Order object from the $order_id argument and using all methods on it.
or using get_post_meta() on with the $order_id argument to get the data saved in wp_postmeta database table.
Then you can update the data with update_post_meta() function…
You can even use woocommerce_checkout_create_order before the data is saved…
You will be able to get the data from the $order argument using all available methods for the WC_Order class (CRUD getters methods).
You will be able to alter this data and saving it using the CRUD setters methods…
Some examples in stackOverFlow
If you need to do that after the order process the hooks to be used can be:
woocommerce_new_order (on newly created order event)
woocommerce_thankyou (on order received page)
woocommerce_order_status_changed (on order status changing event)
And may be some others…
To alter the data when order is saved in backend, you will use save_post_shop_order that has 3 arguments: $post_id, $post and $update…

Add field in woocommerce email setting tab in dashboard

I wanted to add a field in WooCommerce > Settings > Emails tab. In this section there is a list of email template and on clicking any template you can see this screen.
I want to add another field to this section for every template.
I manage to achieve it but I'm feeling its not a proper way.
My code is as follow:
add_filter( 'woocommerce_settings_tabs_email', 'emailText' );
function emailText($args) {
?>
<div class="EmailText">
<label for="EmailText">Email Text</label>
<textarea id="EmailText" class="widefat" name="email_text"></textarea>
</div>
<?php
}
It displays the field but I cannot save anything in it.
The WooCommerce Email Settings tab has a table with all of the available email templates such as 'New Order', 'Canceled Order', 'Failed Order', etc. These are usually defined by extending the WC_Email class. Take a look in /plugins/woocommerce/includes/emails for examples.
As WildProgrammers has pointed out, the 'woocommerce_settings_api_form_fields_{email-id}' can be easily used to add a setting to a specific email class. You can see the definition on the WooCommerce Settings API Code Reference page. It provides us with the array of form fields for the email class.
For this example lets add an additional option to the 'New Order' email template. We can look in the WC_Email_New_Order class constructor (found in class-wc-email-new-order.php) to find/verify the id to replace {email-id} in our hook. In this case the WC_Email_New_Order class id is 'new_order' so to add a setting or form field to the New Order options our hook will look like, add_action( 'woocommerce_settings_api_form_fields_new_order', 'add_my_custom_email_setting',10,1);
Now in the 'add_my_custom_email_setting' function all we need to do is add our new setting to the form_fields array and return it:
function add_my_custom_email_setting($form_fields){
$form_fields['my_custom_id']=['title'=>'My Custom Option',
'description'=>'This is a text area we added to the new order email settings page.',
'type'=>'textarea',
'default'=>''
];
return $form_fields;
}
Take a look at the 'init_form_fields' function in WC_Email class for examples of how to build the options array.
You asked how to add an option to all of the available templates. To do this we could simply manually define a hook for every email class,
add_action('woocommerce_settings_api_form_fields_new_order','add_my_custom_email_setting');
add_action('woocommerce_settings_api_form_fields_failed_order','add_my_custom_email_setting');
add_action('woocommerce_settings_api_form_fields_processing_order','add_my_custom_email_setting');
...
but if we can get a list of all of the id's then we can do it programatically instead. Lucky for us WooCommerce provides us with the 'woocommerce_email_classes' filter which is called right after WC instantiates all of the email classes. Now all we need is a function that iterates over the array of email classes provided by the 'woocommerce_email_classes' filter, get the class id's, and add a hook to 'woocommerce_settings_api_form_fields_{email-id}' for each id:
add_action( 'woocommerce_email_classes', 'add_email_custom_setting');
function add_email_custom_setting($email_class_list){
foreach($email_class_list as $email_class){
add_action( 'woocommerce_settings_api_form_fields_' . $email_class->id,'add_my_custom_email_setting',10,1);
}
return $email_class_list;
}`
We can then use this option in our template files with $email->get_option('my_custom_id');.
You might ask why not simply add the option to the form_fields array in add_email_custom_setting?
Instead of using 'woocommerce_settings_tabs_email' filter try using
'woocommerce_email_settings_after' hook for creating your field.

Magento - Differentiate Normal orders and orders created programatically

I am able to create orders programmatically using my custom module.
I wish to add a flag to differentiate between various types of orders that can be created from admin/websites and orders created from my custom module.
I understand that admin and website orders can be differentiated by
if(!empty($order->getRemoteIp()){
//place online
}
else{
// place by admin
}
However, I still want to differentiate between orders manually placed from admin and orders placed from my custom module.
The following are some solutions I thought of
1) Adding a prefix to the order or order-increment id.
2) Creating a new store during creation of module and add all orders from my custom module using that store. However, I am not sure of what implications this might cause.
3) I am able to change the store name during order creation using
$order->setStoreName('customName');
But, this is not visible in the admin grid or order detail page. I'm guessing they fetch the information for "Purchased From" from the store id.
I am looking for, what could be the best solution from the above, or a better solution if any.
Note: My module is currently compatible with magento v1.4 and above. So I will need a solution that covers most versions.
You could create an order attribute and set it when you create your order.
To create an order attribute you can do like this, in your module folder, in sql/mymodule_setup/mysql4-upgrade-VNUMBER.php:
$installer = $this;
$installer->startSetup ();
$setup = new Mage_Eav_Model_Entity_Setup ( 'core_setup' );
$installer->getConnection()
->addColumn (
$installer->getTable ( 'sales_flat_order' ),
'my_attribute_code', // this is where you set the attribute code
'varchar(255) DEFAULT NULL' );
$setup->startSetup ();
$setup->addAttribute (
'order',
'my_attribute_code',
array (
'type' => 'int', // or text or whatever
'label' => 'My attribute Label'
));
$installer->endSetup ();
Then when dealing with the order, simply use
$order->setMyAttributeCode($value);

Replace WooCommerce Class

WooCommerce has a class "get_shipping_to_display()" in /wp-content/plugins/woocommerce/classes/class-wc-order.php, that keeps outputting $36 in my invoices (using the Print Invoice & Packing List plugin) as the value for shipping... regardless of what the shipping out is actually.
I've narrowed it down to that class, as using "get_shipping()" displays the actual shipping price.
I don't want to modify WooCommerce core files (so as not to cause problems with updates later on), so how do I go about replacing that class with my own class in functions.php so I can try and narrow down what is causing the issue... and what would that look like?
The last line in the function get_shipping_to_display() is:
return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
That means that if you create a filter on woocommerce_order_shipping_to_display you can ultimately output whatever you want (though technically contents of that function will still run first). The $shipping variable contains the text output you are seeing now and $this will have all the properties of the class, including the order $id, $status, etc which you could use to do whatever lookups/calculations you want.

wordpress: how to enable the edit and delete action button in wp_list_table class of wordpress

Hi i am writing a plugin in which i have displayed various entries from database by extending wordpress wp_list_table class.
To display some action links in every single row i have used this function in such following way.
function column_name($item) {
$actions = array(
'edit' => sprintf('Edit',$_REQUEST['page'],'edit',$item['id']),
'delete' => sprintf('Delete',$_REQUEST['page'],'delete',$item['id']),
);
return sprintf('%1$s %2$s', $item['Name'], $this->row_actions($actions) );
}
but from admin page when i click on those links nothing happen only the url changes i searched every ware and most of the examples are using static data in the form of array so what i want to say is how i can make it active by $_GET[] method or is there any other way to do it ?
Back in your settings page, you should add an if statement for the $GET value. (i.e. the callback from your add(sub)menu_page call.
e.g.
if( isset( $_GET['edit-hotel'] ) ) :
// Show my edit hotel form
else :
// Show my WP_List_Table
endif;
You can of course show the list table above your edit form as well if appropriate - this is just an example.
Be aware that the WP_List_table class is marked #access private so shouldn't really be used as part of the API. See this comment from WordPress core developer Andrew Nacin.

Categories