Is there a hook that precedes Woocommerce API's duplicate SKU check? - woocommerce

I have a site with Woocommerce and WPML + Multilingual Woocommerce installed. My problem is that I try to insert a product as a translation of a previously entered product without being aware of the ID of the main product. If I enter the ID as translation_of it works; both products share the same SKU and the translation has the SKU field disabled, which is how I want it to work. But I don't want to enter translation_of into the data that gets sent to Woocommerce. I want to only use the SKU and then let Wordpress first check if a product with that SKU already exists and replace sku with translation_of if it does.
This is how I went about it:
add_filter('woocommerce_api_create_product_data', '__create_product_data', -100, 2);
function __create_product_data($data, $api) {
if(isset($data['sku']) && $product_id = wc_get_product_id_by_sku($data['sku'])) {
$product_id = apply_filters('wpml_object_id', $product_id, 'product');
$data['translation_of'] = $product_id;
unset($data['sku']);
}
return $data;
}
But it seems to me that execution arrives at this point long after the SKU has been checked, because I noticed that I can return nothing and I still get product_invalid_sku error back. What would be the correct hook or does such a hook even exist?

My own solution:
add_filter('rest_pre_dispatch', '__rest_pre_dispatch', 10, 3);
function __rest_pre_dispatch($result, $server, $request) {
$sku = $request->get_param('sku');
if ($sku) {
$id = wc_get_product_id_by_sku($sku);
if ($id) {
$product_id = apply_filters('wpml_object_id', $id, 'product');
$request->set_param('translation_of', $product_id);
$request->offsetUnset('sku');
}
}
return $result;
}

Related

WooCommerce update a product when opening single product view

I have to update my product with data that comes from an API query. I would like to update the products data when visiting the single products view. I'm new to WordPress and WooCommerce, but I think this can be done through hooks?
I've tried looking for hooks but haven't had any luck yet
You could do something like the example below.
This will run every time the product is viewed. Of course, you have to add your own API call and adapt the function to your data structure.
add_action('wp', 'update_product_data');
function update_product_data(){
if (class_exists('WooCommerce') && is_product()) {
$product = wc_get_product( get_the_ID() );
$product_id = $product->get_id();
// Call the external API to get the updated product data
$api_url = 'http://example.com/api/product/' . $product_id;
$response = wp_remote_get($api_url);
// Check for successful response and decode the JSON data
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) == 200) {
$data = json_decode(wp_remote_retrieve_body($response), true);
// Update the product's weight and description with the data from the API
$product->set_weight($data['weight']);
$product->set_description($data['description']);
$product->save();
}
}
}

WordPress prevent delete taxonomy

I would like to prevent that some categories are accidentally deleted. For this I use a meta entry for the category to be protected.
I use the following code for this:
// edit: wrong hook! ** add_action( 'delete_term_taxonomy', 'taxonomy_delete_protection', 10, 1 );
add_action( 'pre_delete_term', 'taxonomy_delete_protection', 10, 1 );
function taxonomy_delete_protection ( $term_id )
{
if (get_term_meta ($term_id, 'delete-protect', true) === true)
{
wp_die('Cannot delete this category');
}
}
Unfortunately, instead of my error message, only "Something went wrong" is displayed. Why?
Edit: The `delete_term_taxonomy` is the wrong hook for my code, because it deleted the meta before i can check the meta entry. `pre_delete_term` does fire before anything happens with the category.
The "Why" is because of the following JavaScript that ships with WordPress:
$.post(ajaxurl, data, function(r){
if ( '1' == r ) {
$('#ajax-response').empty();
tr.fadeOut('normal', function(){ tr.remove(); });
/**
* Removes the term from the parent box and the tag cloud.
*
* `data.match(/tag_ID=(\d+)/)[1]` matches the term ID from the data variable.
* This term ID is then used to select the relevant HTML elements:
* The parent box and the tag cloud.
*/
$('select#parent option[value="' + data.match(/tag_ID=(\d+)/)[1] + '"]').remove();
$('a.tag-link-' + data.match(/tag_ID=(\d+)/)[1]).remove();
} else if ( '-1' == r ) {
$('#ajax-response').empty().append('<div class="error"><p>' + wp.i18n.__( 'Sorry, you are not allowed to do that.' ) + '</p></div>');
tr.children().css('backgroundColor', '');
} else {
$('#ajax-response').empty().append('<div class="error"><p>' + wp.i18n.__( 'Something went wrong.' ) + '</p></div>');
tr.children().css('backgroundColor', '');
}
});
The expected response to this POST request is:
'1' if the term was deleted
'-1' if your user doesn't have permission to delete the term.
For all other cases, "Something went wrong" is displayed.
You are terminating the script early with wp_die, yielding an unexpected response, which comes under "other cases".
There isn't a way to provide a custom error message in the notice box here without writing some JavaScript of your own.
This is my current solution, not perfect but it works.
The "Something went wrong" message show up if you delete the taxonomy with the row action. So i unset the "delete" action so it couldn't be triggered this way.
add_filter ('category_row_actions', 'unset_taxonomy_row_actions', 10, 2);
function unset_taxonomy_row_actions ($actions, $term)
{
$delete_protected = get_term_meta ($term->term_id, 'delete-protect', true);
if ($delete_protected)
{
unset ($actions['delete']);
}
return $actions;
}
Then i hide the "Delete" Link in the taxonomy edit form with css. It's still could be triggered if you inspect the site and it's link, but there is no hook to remove this action otherwise.
add_action( 'category_edit_form', 'remove_delete_edit_term_form', 10, 2 );
function remove_delete_edit_term_form ($term, $taxonomy)
{
$delete_protected = get_term_meta ($term->term_id, 'delete-protect', true);
if ($delete_protected)
{
// insert css
echo '<style type="text/css">#delete-link {display: none !important;}</style>';
}
}
Finally the check before deleting the taxonomy. This should catch all other ways, like the bulk action "delete". I didn't found another way yet to stop the script from deleting the taxonomy.
add_action ('pre_delete_term', 'taxonomy_delete_protection', 10, 1 );
function taxonomy_delete_protection ( $term_id )
{
$delete_protected = get_term_meta ($term_id, 'delete-protect', true);
if ($delete_protected)
{
$term = get_term ($term_id);
$error = new WP_Error ();
$error->add (1, '<h2>Delete Protection Active!</h2>You cannot delete "' . $term->name . '"!');
wp_die ($error);
}
}
This solution provides a way to disable all categories from being deleted by a non Admin. This is for anyone like myself who's been searching.
function disable_delete_cat() {
global $wp_taxonomies;
if(!current_user_can('administrator')){
$wp_taxonomies[ 'category' ]->cap->delete_terms = 'do_not_allow';
}
}
add_action('init','disable_delete_cat');
The easiest solution (that will automatically take care of all different places where you can possibly delete the category/term) and in my opinion the most flexible one is using the user_has_cap hook:
function maybeDoNotAllowDeletion($allcaps, $caps, array $args, $user)
{
if ($args[0] !== 'delete_term') return $allcaps;
// you can skip protection for any user here
// let's say that for the default admin with id === 1
if ($args[1] === 1) return $allcaps;
$termId = $args[2];
$term = get_term($termId);
// you can skip protection for all taxonomies except
// some special one - let's say it is called 'sections'
if ($term->taxonomy !== 'sections') return $allcaps;
// you can protect only selected set of terms from
// the 'sections' taxonomy here
$protectedTermIds = [23, 122, 3234];
if (in_array($termId, $protectedTermIds )) {
$allcaps['delete_categories'] = false;
// if you have some custom caps set
$allcaps['delete_sections'] = false;
}
return $allcaps;
}
add_filter('user_has_cap', 'maybeDoNotAllowDeletion', 10, 4);

Woocommerce - Edit account issue

My Woocommerce is setup to generate username automatically. I'm trying to use the code below to change username before save. I would like to change user name to be equal a custom field filled in billing form.
My code is:
function wc_cpf_as_username ( $user_login ) {
if( !empty($_POST['billing_cpf'] ) ) {
$user_login = $_POST['billing_cpf'];
}
elseif (!empty( $_POST['billing_cnpj'] )){
$user_login = $_POST['billing_cnpj'];
}
else{
$user_login = $_POST['billing_email'];
}
return $user_login;
}
add_filter( 'pre_user_login' , 'wc_cpf_as_username' );
The code work to create user, but this code do not work to edit user in my account page (/my-account/edit-account). Woocommerce show success message (Account details changed successfully.), but data is not changed.
I do not know what is the issue.
Could you help me?
Why you are making that complex function if you have a hook available for this. edit_user_profile_update hook i.e. located in /wp-admin/user-edit.php.
update_user_meta($user_id, 'custom_meta_key', $_POST['custom_meta_key']).
update_user_meta thats for update user meta field based on user ID.
add_action('edit_user_profile_update', 'update_extra_profile_fields');
function update_extra_profile_fields($user_id) {
if ( current_user_can('edit_user',$user_id) )
update_user_meta($user_id, 'Custom_field', $_POST['your_field']);
}

Woo-commerce: allow checkout only if the customer is living in a certain city

I am making a woocommerce website for my supermarket which delivers only to one city(Ras Tanura).
Since it is a supermarket store, I replaced Shipping with Delivery. I'm trying to restrict checking out to allow only customers how live in Ras Tanura to choose a payment method. I don't want people to pay for something that can't be delivered to them. Here is what I tried.
add_filter( 'default_checkout_billing_country', 'change_default_checkout_country' );
add_filter( 'default_checkout_shipping_country', 'change_default_checkout_country' );
function change_default_checkout_country() {
return 'SA'; // country code
}
// default checkout state
add_filter( 'default_checkout_billing_state', 'change_default_checkout_state' );
add_filter( 'default_checkout_shipping_state', 'change_default_checkout_state' );
function change_default_checkout_state() {
return 'RT'; // state code
}
// Setting one state only
add_filter( 'woocommerce_states', 'custom_woocommerce_state', 10, 1 );
function custom_woocommerce_state( $states ) {
// Returning a unique state
return array('SA' => array('RT' => 'Ras Tanura'));
}
// Only one country at checkout
add_filter( 'woocommerce_checkout_fields', 'custom_checkout_fields', 10, 1 );
function custom_checkout_fields( $fields ) {
$fields['billing']['billing_city']['type'] = 'select';
$fields['billing']['billing_city']['options'] = array('Ras Tanura' => 'Ras Tanura');
$fields['shipping']['shipping_city']['type'] = 'select';
$fields['shipping']['shipping_city']['options'] = array('Ras Tanura' => 'Ras Tanura');
return $fields;
}
I have added this code to my functions.php under oceanWP theme.
This code does half of the job. It sure sets the default city to Ras Tanura and it can't be changed by customers, but I tried to order from another city and it accepted my order the only thing that this code has done is writing that the order is coming from Ras Tanura even though it wasn't.
how can I make my website knows the location of the user and prevent or accept checkout based on that?
(accepts if the person is in Ras Tanura and prevents if he lives elsewhere)
Note that I have already set the selling location under woocommerce> setting> general to sell to specific countries and I have chosen this country to SA.
Note: this method make use of third party api "https://ip-api.com/" to
request customer data based on ip address, which allow limited request
(as per website documentation it allow 45 HTTP requests per minute
from an IP address.)
Woocommerce provide geolocate customer setting by default,I have make use of some part of Woocommerce Geolocate code to fix issue.
What below code do : It will locate customer based on ip address and if customer is not belongs to specific location(country and city), it will not allow the customer to go to checkout page, redirect customer to cart error page.
Kindly note that in general setting of woocommerce "Default customer location" is set to Geolocate.
Keep your code as it is , and additionally put below code in your functions.php file,
/*
* This function is used to retrieve location data based on ip address,
* note that each service/api has different response format
*/
function geolocate_customer() {
$ip_address = WC_Geolocation::get_external_ip_address();
$geoipdata = get_transient('geoip_' . $ip_address);
if (false === $geoipdata) {
// you can add more service key : service name, value : service-endpoint
$geoip_services = array(
'ip-api.com' => 'http://ip-api.com/json/%s',
);
$geoip_services_keys = array_keys($geoip_services);
shuffle($geoip_services_keys);
foreach ($geoip_services_keys as $service_name) {
$service_endpoint = $geoip_services[$service_name];
$response = wp_safe_remote_get(sprintf($service_endpoint, $ip_address), array('timeout' => 2));
if (!is_wp_error($response) && $response['body']) {
switch ($service_name) {
case 'ipinfo.io':
$geoipdata = json_decode($response['body']);
//this flag is used for error
$flag = ($geoipdata->error) ? true : false;
break;
case 'ip-api.com':
$geoipdata = json_decode($response['body']);
//this flag is used for error, each api may have different response format
$flag = ($geoipdata->status == 'success') ? false : true;
break;
default:
$geoipdata = '';
break;
}
if ($geoipdata && !$flag) {
break;
}
}
}
// This will store geolocation data so that frequent call to api can be reduced.
set_transient('geoip_' . $ip_address, $geoipdata, WEEK_IN_SECONDS);
}
return $geoipdata;
}
Note : replace 'PUT CITY NAME HERE' with city name you got in $geoipdata variable in below function.
/*
* This function is attached to 'woocommerce_before_checkout_form_cart_notices' hook,
* It will check country,city criteria and if criteria is not matched it will add error
* notice so checkout page is loaded with error message(load cart-errors.php template)
*
*/
function add_cart_notice() {
$geoipdata = geolocate_customer();
if (!empty($geoipdata)) {
if ($geoipdata->city != 'PUT CITY NAME HERE' && $geoipdata->countryCode != 'SA') {
wc_add_notice(__('Add your message here', 'woocommerce'), 'error');
add_action('woocommerce_cart_has_errors', 'print_notices');
}
}
}
add_action('woocommerce_before_checkout_form_cart_notices','add_cart_notice');
/*
* This function is used to show error message on cart page.
*/
function print_notices(){
wc_print_notice('Add your message here', 'error');
}
For testing purpose, you can put below code in functions.php file to get city and other details based on ip address.City name can be accessed using $geoipdata->city. This will output data on website.
$geoipdata = geolocate_customer();
var_dump($geoipdata);

Woocommerce "No Shipping methods" message: Customise message based on the Zipcode customer enters

Our store has been setup to process orders only within Sydney city. We manage this in Woocommerce by setting the allowed postcodes for a Flat Rate Delivery.
We are now extending deliveries to other other areas but only via telephone (no online orders for these new postcodes). Woocommerce displays a standard No Shipping message but we would to customise such that
Customer enters a postcode within the city, allow online orders. No
change to current behaviour.
Customer enters a postcode for which
telephone orders are allowed, show a customised message asking the
customer to make the call.
All other postcodes, disallow orders.
Any technical direction will be greatly appreciated.
Thanks.
Below code should help you. Change the value of array variable $zip_array in both the functions as a comma separated list of zip codes, which you want to show a custom message. Also, change the string value of $custom_msg to your custom message. More details, please refer this article.
// For Cart Page.
add_filter( 'woocommerce_no_shipping_available_html', 'wf_customize_default_message', 10, 1 );
// For Checkout page
add_filter( 'woocommerce_cart_no_shipping_available_html', 'wf_customize_default_message', 10, 1 );
function wf_customize_default_message( $default_msg ) {
$zip_array = array(
'30031',
);
if ( in_array( WC()->customer->get_shipping_postcode() , $zip_array) ) {
$custom_msg = "Call us for quotation - 1-800-XXX-XXXX";
if( empty( $custom_msg ) ) {
return $default_msg;
}
return $custom_msg;
}
return $default_msg;
}
add_filter('woocommerce_package_rates', 'wf_remove_shipping_options_for_particular_zip_codes', 8, 2);
function wf_remove_shipping_options_for_particular_zip_codes($rates, $package)
{
global $woocommerce;
$zip_array = array(
'30031',
);
if ( in_array( $woocommerce->customer->get_shipping_postcode() , $zip_array) ) {
$rates = array();
}
return $rates;
}

Resources