Disable delete for Woocommerce order

I have some problem recently with Woocommerce order being deleted by Admin and we have no idea who did it. And that annoys me. Hence, i went ahead and disable Woocommerce delete for any order for all users. Pretty much i do not want any delete to happen for any order regardless it is intentionally or accidentally.

Disable Woocommerce Delete

In order to disable woocommerce delete for order, you need to hook in to the 2 action hook which is wp_tash_post and before_delete_post as shown below,

// disable delete entirely
function restrict_post_deletion($post_ID){
    $type = get_post_type($post_ID);
    if($type == 'shop_order'){
        echo "You are not authorized to delete this page.";
        exit;
    }
}
add_action('wp_trash_post', 'restrict_post_deletion', 10, 1);
add_action('before_delete_post', 'restrict_post_deletion', 10, 1);

what the above does is to get the post_id of the post you are deleting and see what type it is. If the type if a shop_order, do not allow them to delete by exiting the script entirely and show a message to the user so that they will stop doing silly things.

Woocommerce After an Order Before Payment Hook

There are times when you want to do some action like sending the order info to another database or third party integration before payment took place. of course, after payment took place, you might still initial another type of hook which is not explain here, there are tons of them if you google around. However, before a payment is made and after a checkout is place, an order is created. This hook is often ignored and not mention around. And this is the Woocommerce hook i am going to demonstrate here.

2 Woocommerce action hook after an order is made

There are actually two action hook that you can use here which are

woocommerce_checkout_order_processed
woocommerce_new_order

Both this hook, allows you to initial your custom function immediately after an order is made such as the one below,

  add_action( 'woocommerce_checkout_order_processed', 'my_status_pending',  1, 1  );

or

  add_action( 'woocommerce_new_order', 'my_status_pending',  1, 1  );

Do bear in mind that the priority here is placed at the highest as compare to the default 10. If you just use the default priority, the chances of your payment getting directly marked as paid rather than going through the normal process of Woocommerce is high. It happens to me as i couldn't figure out how come all order are being marked as paid without going through any payment selected. It is all due to the action hook used above which you have to take note of the priority to prevent yourself losing money due to unpaid invoice marked as paid automatically.

New order hook

The argument parameter is marked as 1 which is the default since we only really need the order id of the hook as shown below,

add_action( 'woocommerce_new_order', 'my_status_pending',  1, 1  );
function my_status_pending($order_id){
// do your magic here
}

Do remember to NOT place any $woocommerce->cart->empty_cart() sentence within these methods as it will remove the item in the cart and leave other checkout method unable to proceed further. Leave the empty_cart instruction to the payment gateway to handle it.

Empty Woocommerce Cart before adding new item

If you happen to only wants to allow 1 item in your cart and remove all other cart item before adding one, this article might be your saver. Apparently there are a few article that uses a hook call 'woocommerce_add_to_cart' where it 'should' remove the item before adding it. But it doesn't work for newer version of woocommerce. However, there is another hook call 'woocommerce_add_to_cart_validation'. Therefore, if you would like to remove an item before adding a new item into your cart in Woocommerce, you should do something like this.

// before addto cart, only allow 1 item in a cart
add_filter( 'woocommerce_add_to_cart_validation', 'woo_custom_add_to_cart_before' );

function woo_custom_add_to_cart_before( $cart_item_data ) {

    global $woocommerce;
    $woocommerce->cart->empty_cart();

    // Do nothing with the data and return
    return true;
}

This will remove everything during validation before adding the item into wWocommerce cart. Try it!

Woocommerce hook custom button on admin order page

Another Woocommerce hook that i search high and low for it but in the end dig it out from the source code itself. What happen if you would like to create a custom button on Woocommerce admin order page? Something like the image below?

Screen Shot 2015-09-16 at 7.42.19 PM

Looks like something you need? It's pretty easy by using a hook call 'woocommerce_order_item_add_action_buttons'.

woocommerce_order_item_add_action_buttons woocommerce button hook

Basically this hook allows you to add custom button similar to the one woocommerce has and ensuring that you don't hack or edit the core code to get what you want. Here is a snippet of what i did.

// add new button for woocommerce
add_action( 'woocommerce_order_item_add_action_buttons', 'action_woocommerce_order_item_add_action_buttons', 10, 1);
// define the woocommerce_order_item_add_action_buttons callback
function action_woocommerce_order_item_add_action_buttons( $order )
{
    echo '<button type="button" onclick="document.post.submit();" class="button generate-items">' . __( 'Magic Button Appear!', 'hungred' ) . '</button>';
    // indicate its taopix order generator button
    echo '<input type="hidden" value="1" name="renew_order" />';
};
// resubmit renew order handler
add_action('save_post', 'renew_save_again', 10, 3);
function renew_save_again($post_id, $post, $update){
    $slug = 'shop_order';
    if(is_admin()){
            // If this isn't a 'woocommercer order' post, don't update it.
            if ( $slug != $post->post_type ) {
                    return;
            }
            if(isset($_POST['renew_order']) && $_POST['renew_order']){
                    // do your stuff here after you hit submit
                }
    }
}

dump the above snippet to your function.php file and you should see the "magic button appear!" button on your woocommerce admin order page.

A little bit of explanation here, what the above code does it create a button which the woocommerce_order_item_add_action_buttons hook does the trick

// add new button for woocommerce
add_action( 'woocommerce_order_item_add_action_buttons', 'action_woocommerce_order_item_add_action_buttons', 10, 1);
// define the woocommerce_order_item_add_action_buttons callback
function action_woocommerce_order_item_add_action_buttons( $order )
{
    echo '<button type="button" onclick="document.post.submit();" class="button generate-items">' . __( 'Magic Button Appear!', 'hungred' ) . '</button>';
    // indicate its taopix order generator button
    echo '<input type="hidden" value="1" name="renew_order" />';
};

I've place a hidden button to identify the submit is hit from this button. Next, since woocommerce admin order page is also a post page, all i have to do is to attached it with a post handler hook.

// resubmit renew order handler
add_action('save_post', 'renew_save_again', 10, 3);
function renew_save_again($post_id, $post, $update){
    $slug = 'shop_order';
    if(is_admin()){
            // If this isn't a 'woocommercer order' post, don't update it.
            if ( $slug != $post->post_type ) {
                    return;
            }
            if(isset($_POST['renew_order']) && $_POST['renew_order']){
                    // do your stuff here after you hit submit
                }
    }
}

and make sure that this handle only runs if its a shop_order type and we are good to go! That's all! Enjoy!

How to find next date removing weekend and public holiday starting from today in PHP

Ok, so here is a snippets of finding out the next business or working day excluding public holiday that i used in a project. Its pretty neat, but holidays date changes every year and each country holiday is different from each other. Therefore, the holidays are being placed in an array instead. Without further ado, here's the snippets.

// get today date
$tmpDate = date('Y-m-d');
// all the holidays
$holidays = array('2015-01-01', '2015-01-02', '2015-02-04', '2015-02-18', '2015-02-19', '2015-02-20', '2015-02-21', '2015-02-22', '2015-02-23', '2015-02-27',
    '2015-02-28', '2015-03-05', '2015-03-08', '2015-03-12', '2015-03-20', '2015-03-21', '2015-03-29', '2015-04-03', '2015-04-04', '2015-04-05',
    '2015-04-06', '2015-04-07', '2015-05-01', '2015-05-03', '2015-05-04', '2015-05-10', '2015-05-11', '2015-05-25', '2015-06-03', '2015-06-19',
    '2015-06-20', '2015-06-21', '2015-06-28', '2015-08-08', '2015-08-20', '2015-08-28', '2015-09-03', '2015-09-23', '2015-09-26', '2015-09-27',
    '2015-09-28', '2015-10-09', '2015-10-10', '2015-10-21', '2015-10-25', '2015-11-12', '2015-11-26', '2015-12-22', '2015-12-25');
// 1 day
$i = 1;
// for the next business day from today
$nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
    ' +'.$i.
    ' Weekday'));
// check whether the date of the business day is in public holiday
while (in_array($nextBusinessDay, $holidays)) {
    $i++;
    // move the business date forward since its a public holiday
    $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
        ' +'.$i.
        ' Weekday'));
}

Now, the code above will find the next working day. How about telling me how to find 10 days after the working day? Pretty much the same just repeat the same logic and place it below it,

    // get today date
    $tmpDate = date('Y-m-d');
    // all the holidays
    $holidays = array('2015-01-01', '2015-01-02', '2015-02-04', '2015-02-18', '2015-02-19', '2015-02-20', '2015-02-21', '2015-02-22', '2015-02-23',
        '2015-02-27', '2015-02-28', '2015-03-05', '2015-03-08', '2015-03-12', '2015-03-20', '2015-03-21', '2015-03-29', '2015-04-03', '2015-04-04',
        '2015-04-05', '2015-04-06', '2015-04-07', '2015-05-01', '2015-05-03', '2015-05-04', '2015-05-10', '2015-05-11', '2015-05-25', '2015-06-03',
        '2015-06-19', '2015-06-20', '2015-06-21', '2015-06-28', '2015-08-08', '2015-08-20', '2015-08-28', '2015-09-03', '2015-09-23', '2015-09-26',
        '2015-09-27', '2015-09-28', '2015-10-09', '2015-10-10', '2015-10-21', '2015-10-25', '2015-11-12', '2015-11-26', '2015-12-22', '2015-12-25');
    // 1 day
    $i = 1;
    // for the next business day from today
    $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
        ' +'.$i.
        ' Weekday'));
    // check whether the date of the business day is in public holiday
    while (in_array($nextBusinessDay, $holidays)) {
        $i++;
        // move the business date forward since its a public holiday
        $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
            ' +'.$i.
            ' Weekday'));
    }
    // working day + 10 working day excluding holidays
    $tmpDate = $nextBusinessDay;
    // 1 day
    $i = 1;
    // next business day excluding weekdays after 10 days
    $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
        ' +10 weekdays'));
    // this is the format we want it to look like
    $date = date("m月d日", strtotime($tmpDate.
        " +10 weekdays"));
    // is the next business day after 10 day a public holiday?
    while (in_array($nextBusinessDay, $holidays)) {
        // if it is, move the day by 1 
        $nextBusinessDay = date('Y-m-d', strtotime($nextBusinessDay.
            ' +'.$i.
            ' weekdays'));
        // get the format we want, actually this can be thrown outside of the loop
        $date = date('m月d日', strtotime($nextBusinessDay));
        $i++;
    }

And as you can see, i DID NOT OPTIMIZE the code. i'm sorry. don't hate me, but i do share it, so, forgive me. Anyway, the code above can be reduce by half if you dump it into a function and just reuse the logic. i kinda lazy without doing that. IM SO SORRY! but if you did optimize it and dump it into a neat function, do share it out in the comment and i'll dump it right here!

*** UPDATE ****

Ok basically the above code doesn't remove the issue when there are days when the holidays fall between 2 dates. Anyhow, the more appropriate code that takes care of ALL holidays are

// find latest working day
$tmpDate = date('Y-m-d');
$holidays = array('2016-02-06','2016-02-07','2016-02-08','2016-02-09','2016-02-10','2016-02-11','2016-02-12','2016-02-13','2016-02-14','2016-02-27','2016-02-28','2016-02-29','2016-04-02','2016-04-03','2016-04-04','2016-04-05','2016-06-09','2016-06-10','2016-06-11','2016-06-12','2016-09-15','2016-09-16','2016-09-17','2016-09-18','2016-10-09','2016-10-10','2016-10-11');
$i = 1;
$nextBusinessDay = date('Y-m-d', strtotime($tmpDate . ' +' . $i . ' Weekday'));

while (in_array($nextBusinessDay, $holidays)) {
  $i++;
  $nextBusinessDay = date('Y-m-d', strtotime($tmpDate . ' +' . $i . ' Weekday'));
}
// working day + 10 working day excluding holidays
$tmpDate = $nextBusinessDay;
$startDate = new DateTime( $tmpDate );    //intialize start date
$endDate = new DateTime( '2016-12-31' );    //initialize end date
$holiday = array('2016-02-06','2016-02-07','2016-02-08','2016-02-09','2016-02-10','2016-02-11','2016-02-12','2016-02-13','2016-02-14','2016-02-27','2016-02-28','2016-02-29','2016-04-02','2016-04-03','2016-04-04','2016-04-05','2016-06-09','2016-06-10','2016-06-11','2016-06-12','2016-09-15','2016-09-16','2016-09-17','2016-09-18','2016-10-09','2016-10-10','2016-10-11');
$interval = new DateInterval('P1D');    // set the interval as 1 day
$daterange = new DatePeriod($startDate, $interval ,$endDate);
// find all the dates that are working days
foreach($daterange as $date){
        if($date->format("N") <6 AND !in_array($date->format("Y-m-d"),$holiday))
                $result[] = $date->format("Y-m-d");
}
// now we want 10 working days starting from 0 (hence the 9 )
echo $result[9];

Basically, we first find the next business day as the 'starting' date and also initial the 'end' date which is the end of 2016 in this case, the 'results' will gives me a list of working dates. From the list of work date, i need 10 working days, and since its an array, something like 10th working day can be simply print out like the one show in the code