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

Optimizing MySQL InnoDB

This is something pretty short and useful for many mysql InnoDB users. Pretty much you will come across optimizing MySQL InnoDB due to performance issues or MySQL is causing a lot of 'slow' sql queries throwing in your way. Of course, there are pros and cons in doing every type of optimisation such as sacrificing reliability and etc.

MySQL InnoDB Configuration

Before i began explaining what the heck did i do, if you are lazy and just wish to try out whether my configuration works, just head over to your MySQL my.cnf file in /etc/my.cnf and place this on [mysqld]

P.S: this is a linux configuration

[mysqld]
innodb_flush_method=O_DIRECT
innodb_lock_wait_timeout=1
innodb_autoextend_increment=512
innodb_io_capacity = 100
innodb_thread_concurrency = 32
innodb_flush_log_at_trx_commit=2
innodb_read_io_threads = 32
innodb_write_io_threads = 32
innodb_buffer_pool_size=400M
innodb_file_per_table=1
innodb_stats_on_metadata=0

The above configuration will most likely helps to smoothed out most of your InnoDB problens. Especially if you are getting a 10-50 seconds for MySQL slow log.

MySQL InnoDB Configuration Explanation

Now let's go through one by one and explain what each does.

innodb_flush_method

Screen Shot 2015-09-15 at 7.34.14 PM

innodb_flush_method defines the method used to flush data to the InnoDB data files and log files, which can affect I/O throughput. If you look at the image, the default value is NULL, and we have changed it to O_DIRECT to better control I/O throughput. We basicall still using fsync but only for write. read we will use a O_DIRECT instead of fsync. If you are interested to more head over to stackoverflow

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_flush_method

innodb_lock_wait_timeout

Screen Shot 2015-09-15 at 7.43.52 PM
The timeout in seconds an InnoDB transaction waits for a row lock before giving up. The default value is 50 seconds. A transaction that tries to access a row that is locked by another InnoDB transaction waits at most this many seconds for write access to the row before issuing an error. We have set it 1 seconds as this is the only time we can wait for a row being lock or we will just fail the transaction if not we will have a pile of long queue in a busy server.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_lock_wait_timeout

innodb_autoextend_increment

Screen Shot 2015-09-15 at 8.12.07 PM
The increment size (in MB) for extending the size of an auto-extending shared tablespace file when it becomes full. Mainly for sharding setting

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_autoextend_increment

innodb_io_capacity

Screen Shot 2015-09-15 at 8.14.53 PM
An upper limit on the I/O activity performed by the InnoDB background tasks, such as flushing pages from the buffer pool and merging data from the insert buffer. The default value is 200. For busy systems capable of higher I/O rates, you can set a higher value at server startup, to help the server handle the background maintenance work associated with a high rate of row changes. For systems with individual 5400 RPM or 7200 RPM drives, you might lower the value to the former default of 100.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_io_capacity

innodb_thread_concurrency

Screen Shot 2015-09-15 at 8.17.16 PM
InnoDB tries to keep the number of operating system threads concurrently inside InnoDB less than or equal to the limit given by this variable (InnoDB uses operating system threads to process user transactions). Once the number of threads reaches this limit, additional threads are placed into a wait state within a “First In, First Out” (FIFO) queue for execution. Threads waiting for locks are not counted in the number of concurrently executing threads.

We have 32 CPU, hence, the 32 value. But you can lower this to ensure that it doesn't suck up all the resources.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_thread_concurrency

innodb_flush_log_at_trx_commit

Screen Shot 2015-09-15 at 8.20.07 PM
If the value of innodb_flush_log_at_trx_commit is 0, the log buffer is written out to the log file once per second and the flush to disk operation is performed on the log file, but nothing is done at a transaction commit. When the value is 1 (the default), the log buffer is written out to the log file at each transaction commit and the flush to disk operation is performed on the log file. When the value is 2, the log buffer is written out to the file at each commit, but the flush to disk operation is not performed on it.

Basically we are trying to tell InnoDB to not work too hard by setting it to '2'.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit

innodb_read_io_threads

Screen Shot 2015-09-15 at 8.24.21 PM
The number of I/O threads for read operations in InnoDB. The default value is 4. We set it to 32.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_read_io_threads

innodb_write_io_threads

Screen Shot 2015-09-15 at 8.22.32 PM
The number of I/O threads for write operations in InnoDB. The default value is 4. We set it to 32.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_write_io_threads

innodb_buffer_pool_size

Screen Shot 2015-09-15 at 8.26.44 PM
The size in bytes of the memory buffer InnoDB uses to cache data and indexes of its tables. The larger you set this value, the less disk I/O is needed to access data in tables. On a dedicated database server, you may set this to up to 80% of the machine physical memory size. But you most likely won't be able to set to 80%, in our case, we just set it to 400M. (it can goes up to few GB but that depends on your mysqltuner advises would be better)

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size

innodb_file_per_table

Screen Shot 2015-09-15 at 8.28.56 PM
If innodb_file_per_table is disabled (the default), InnoDB creates tables in the system tablespace. If innodb_file_per_table is enabled, InnoDB creates each new table using its own .ibd file for storing data and indexes, rather than in the system tablespace. This is to prevet shits from happening.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_file_per_table

innodb_stats_on_metadata

Screen Shot 2015-09-15 at 8.31.59 PM
When this variable is enabled (which is the default, as before the variable was created), InnoDB updates statistics during metadata statements such as SHOW TABLE STATUS or SHOW INDEX, or when accessing the INFORMATION_SCHEMA tables TABLES or STATISTICS. This is a bitch when you have a lot of InnoDB which will keep updating the statics and may cause your simple SQL to runs for more than 10-50 seconds.

For more information visit http://dev.mysql.com/doc/refman/5.1/en/innodb-parameters.html#sysvar_innodb_stats_on_metadata

Proxmox NoVNC not working

Well, if you are having problem with NoVNC not working on your proxmox and has been ignoring it up until now, its time to make it work. NoVNC basically uses web socket and html5 to allow you to remote access your virtual machine. So make sure you use a browser such as Chrome instead of Safari which has a full compatibility of web socket implementation on the browser. If not, you will most likely get yourself an error such as this,

TASK ERROR: command '/bin/nc -l -p 5900 -w 10 -c '/usr/sbin/qm vncproxy 100 2>/dev/null'' failed: exit code 1

Due to compatibility issue, Proxmox NoVNC might not work with the default install. All you need to do is to find out which NoVNC works for your current Proxmox installation and down/upgrade it! And for me, the version NoVNC 0.47 works for me so i downgraded it from 0.53 by doing the following,

wget http://download1.proxmox.com/debian/dists/wheezy/pvetest/binary-amd64/novnc-pve_0.4-7_amd64.deb
dpkg -i novnc-pve_0.4-7_amd64.deb

And it will do the rest, and if you would like try other version, just head down to the following link

http://download1.proxmox.com/debian/dists/wheezy/pvetest/binary-amd64/

to get all the binary you need.