WordPress prevent delete taxonomy - wordpress

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);

Related

woocommerce_before_order_object_save hook not work [duplicate]

Hello I'm trying to create a function in the mu-plugins to prevent certain users to change order status from specific order statuses to specific order statuses.
I've been looking everywhere and I have tried many different ways, but nothing seems to work.
Actually the function is running using woocommerce_order_status_changed action hook. The thing is that this hook runs after the order status has already been changed, what's causing an infinite loop.
The most useful hook I found seems to be woocommerce_before_order_object_save.
I found "Add an additional argument to prevent 'woocommerce_order_status_changed' from being called" useful related thread on WooCommerce Github.
I tried using #kloon code snippet solution:
add_filter( 'woocommerce_before_order_object_save', 'prevent_order_status_change', 10, 2 );
function prevent_order_status_change( $order, $data_store ) {
$changes = $order->get_changes();
if ( isset( $changes['status'] ) ) {
$data = $order->get_data();
$from_status = $data['status'];
$to_status = $changes['status'];
// Do your logic here and update statuses with CRUD eg $order->set_status( 'completed' );
// Be sure to return the order object
}
return $order;
}
but $changes variable is always an empty array.
I tried to use wp_insert_post_data Wordpress hook, but when I set:
$data['post_status'] = "some status";
it just prevents the whole update (the whole new data) from being saved.
This is the code I would like to run is:
function($data){
if($data['order_status'] == 'comlpeted' && $data['new_order_status'] == 'proccessing'){
// prevent the order status from being changed
$data['new_order_status'] = $data['order_status'];
}
few more if conditions...
return $data;
}
Any help or advise is appreciated.
Based on #kloon code snippet, I have been able to get the old order status and the new order status. Then I can disable any status change from a specific defined order status to a specific defined order status.
With the following code, specific defined user roles can't change order status from "processing" to "on-hold":
add_filter( 'woocommerce_before_order_object_save', 'prevent_order_status_change', 10, 2 );
function prevent_order_status_change( $order, $data_store ) {
// Below define the disallowed user roles
$disallowed_user_roles = array( 'shop_manager');
$changes = $order->get_changes();
if( ! empty($changes) && isset($changes['status']) ) {
$old_status = str_replace( 'wc-', '', get_post_status($order->get_id()) );
$new_status = $changes['status'];
$user = wp_get_current_user();
$matched_roles = array_intersect($user->roles, $disallowed_user_roles);
// Avoid status change from "processing" to "on-hold"
if ( 'processing' === $old_status && 'on-hold' === $new_status && ! empty($matched_roles) ) {
throw new Exception( sprintf( __("You are not allowed to change order from %s to %s.", "woocommerce" ), $old_status, $new_status ) );
return false;
}
}
return $order;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.

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

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;
}

woocommerce_update_product action – fire only once for every product update

Which workaround is there for making the woocommerce_update_product action fire only once?
I've read that it fires twice because it needs to save once internally to retrieve an ID for images/variation saves.
But from a developer experience, this is really not what most need, I guess.
The only workaround I've found so far is to add the action and remove it in the hook directly:
add_action('woocommerce_update_product', 'my_product_update', 10, 2);
function my_product_update($product_id, $product){
remove_action('woocommerce_update_product');
// We'll get here only once!
}
However, this breaks when trying to do bulk edits, making it so that the hook only fires for the first product (because it gets removed afterwards!).
Which other way is there to work around this issue?
Thanks!
Maybe using WordPress Transient can help.
add_action('woocommerce_update_product', 'my_product_update', 10, 2);
function my_product_update($product_id, $product) {
$updating_product_id = 'update_product_' . $product_id;
if ( false === ( $updating_product = get_transient( $updating_product_id ) ) ) {
// We'll get here only once! within 2 seconds for each product id;
// run your code here!
set_transient( $updating_product_id , $product_id, 2 ); // change 2 seconds if not enough
}
}
Use a global variable to do this in memory, no database entry required:
add_action('woocommerce_update_product', 'my_product_update', 10, 2);
function my_product_update($product_id, $product){
global $previous_product_id;
if ($previous_product_id === $product_id){
// We'll get here only once (per product)!
}
$previous_product_id = $product_id;
}
i ran into the same issue (actually my hook was firing 5 times). I found a solution on the following page in the last comment.
Namely, the use of:
$times = did_action('woocommerce_update_product');
if( $times === 1){
// Do some stuff
}
A simpler solution would be to set a $_POST variable and check at the start of the function.
add_action( 'woocommerce_update_product', 'my_action', 10, 1 );
function my_action(){
if( isset($_POST['action_performed']) ){
//Prevent running the action twice
return;
}
// do you stuff here
$_POST['action_performed'] = true;
}

woocommerce_email_recipient_customer_completed_order hook not being invoked

There are a bunch of example of using this woocommerce hook woocommerce_email_recipient_customer_completed_order. So I added it to functions.php, and concatenated a couple example recipients as a test, but only the purchaser receives an email. It doesn't seem like this filter is getting invoked since if I turn on debugging and error_log(...) there's nothing in the log file.
Is there a reason this isn't working? I tried bumping the priority up to 99, but that doesn't work either. The site uses all the defaults and doesn't override any of the templates.
add_filter( 'woocommerce_email_recipient_customer_completed_order', 'custom_woocommerce_add_email_recipient', 10, 2);
function custom_woocommerce_add_email_recipient($recipient, $order) {
$recipient = $recipient . ', foo#example.com, bar#example.com';
return $recipient;
}
As I learn more about Woocommerce it looks like this site only goes as far as processing so I had to use the customer_processing_order mail ID instead to get this to work (thanks to #LoicTheAztec for verifying the customer_completed_order hook works), and instead of using the woocommerce_email_recipient_customer_processing_order hook I used woocommerce_email_headers and checked the mail ID.
add_filter( 'woocommerce_email_headers', 'woocommerce_email_headers_add_recipient', 10, 3);
function woocommerce_email_headers_add_recipient($headers, $mail_id, $order) {
if($mail_id == 'customer_processing_order') {
$member = null;
$memberMail = null;
foreach($order->get_meta_data() as $item) {
if ($item->key === 'member_name') {
$member = $item->value;
$memberMail = $this->getMemberMail($member);
$headers .= "BCC: {$member} <{$member_mail}>\r\n";
break;
}
}
}
return $headers;
}

Wordpress creating plugin for most viewed posts problem?

I just want to create plugin that will when visitor(user,visitor,...) visit some post,remember what post,and to increment counter of that post,I wrote this code,but sometimes,counter is incremented,even post isn't viewed,or post with other Id is added to a table.Can someone help me with this,please.I know that there are plugins for this that I'm trying to do,but still want to write this plugin.
function IncrementPostCount($the_content) {
global $post;
global $wpdb;
if(($post->post_status == 'publish') && (int)$post->ID) {
if(is_single()) { // just for single post - not for page
$postID = (int)$post->ID;
$postTitle = urlencode($post->post_title);
$postLink = urlencode(get_permalink($post->ID));
$oneRow = $wpdb->get_row("SELECT * FROM wp_postovi WHERE postAjDi='$postID'");
if(empty ($oneRow)) {
$postCounter = 1;
$data_array = array(
'readnTimes' => $postCounter,
'linkPost'=>$postLink,
'TitlePost'=>$postTitle,
'postAjDi'=>$postID);
$wpdb->insert('wp_najcitaniji_postovi', $data_array);
}
else {
$postCounter = intval($oneRow->readnTimes) + 1;
$data_array = array('readnTimes' => $postCounter);
$where_array = array('postAjDi'=>intval($oneRow->postAjDi));
$wpdb->update('wp_postovi',$data_array,$where_array);
}
return $the_content;
}
return $the_content;
}
}
add_filter('the_content','IncrementPostCount');
Sorry on my bad english,tnx in advance.
Here's how to do that with the postmeta table.
function IncrementPostCount(){
if(is_single()){
global $wp_query;
$count = get_post_meta( $wp_query->post->ID, 'readnTimes', true );
$count = empty($count) ? 1 : $count + 1;
add_post_meta($wp_query->post->ID, 'readnTimes', $count, true) or update_post_meta($wp_query->post->ID, 'readnTimes', $count);
}
}
add_action( 'template_redirect', 'IncrementPostCount' );
Also, it's better to hook it in earlier. That way, the count is only incremented once per page load (the_content can be fired multiple times per page, even on a single page. template_redirect only fires once per request). Also, if you store the data at template_redirect, you can use the updated view count in the template, giving your visitors an even more accurate view count.
And you don't need to worry about database tables, custom SQL, or any of that.

Resources