Add id (custom post types) column to page and posts list table in wordpress and make them sortable

WordPress doesn’t show the ID in the page and post table lists by default. To see the id, you have to mouseover the title links which is time consuming. The same method applies for custom post types. I hope wordpress makes it easier to do in the future.

// Add id to page and post and make them sortable
add_filter('manage_posts_columns', 'my_columns_id', 10);
add_action('manage_posts_custom_column', 'my_custom_id_columns', 10, 2);
add_filter('manage_pages_columns', 'my_columns_id', 10);
add_action('manage_pages_custom_column', 'my_custom_id_columns', 10, 2);
add_filter('manage_edit-page_sortable_columns', 'my_custom_id_sortable');
add_filter('manage_edit-post_sortable_columns', 'my_custom_id_sortable');
add_action('admin_head', 'my_custom_id_styling');

function my_columns_id($defaults){
  $new = array();
  $count = 0;
  foreach ($defaults as $k => $v) {
    if ($count == 1) {
      $new['wps_post_id'] = __('ID');
    $new[$k] = $v;
  $defaults = $new;
  return $defaults;

function my_custom_id_columns($column_name, $id){
  if($column_name === 'wps_post_id')
    echo $id;

function my_custom_id_sortable($col) {
    $col['wps_post_id'] = 'ID';
    return $col;

function my_custom_id_styling() {
  echo '<style type="text/css">';
  echo 'th#wps_post_id{width:50px;}';
  echo '</style>';

Language translation in wordpress (tips)

Its important to think of language translation when creating plugins, hence is important. Use __ or _e whenever possible.

I strongly recommend poedit for text translation. In poedit, open an existing language file and save it again under your locale but named differently eg woocommerce-zh_AU.po – see 2 files will be created, ie .mo and .po in the default plugin directory. The trick though is not to save them in the plugin directory or else it is possible that your files could be gone upon plugin updates. Where do you save it then? save it in /wp-content/languages/{plugin_name}/{translated_file}, in my example, it will be /wp-content/languages/woocommerce/woocommerce-zh_AU.po and

Next, define your new locale in wp-config.php

// how about some australian chinese
define ('WPLANG', 'zh_AU');

All done.

customizing wordpress wp_editor handles

you might have noticed that wordpress comes with a tinymce editor. the textarea editor allows you to enlarge or reduce the space downwards with a small handle on the bottom left. What if you want to enlarge it or reduce it upwards?

Let us create a new handle on the topright outside of the wordpress tinymce editor. Let’s say we have a div with id (my_resize) outside of the wp_editor.

jQuery(window).load(function () {
    // make my notes area draggable
    var isMouseDown = false;
    var old_y = 0;
    var new_y = 0;
    var maxheight = jQuery(window).height() * 0.8;
    var minheight = jQuery(window).height() * 0.1;
    var currentheight = 0;

    jQuery('#my_resize').mousedown(function(e) {
        currentheight = parseInt(jQuery("#comment_ifr").css('height'));
        isMouseDown = true;
        old_y = e.pageY;
    jQuery(document).mousemove(function(e) {
        if (isMouseDown) {
            new_y = e.pageY;
            diff = old_y - new_y;
            ht = currentheight+diff;
            // set a max and min for the height
            ht = (ht > maxheight) ? maxheight : ht;
            ht = (ht < minheight) ? minheight : ht;
            jQuery("#comment_ifr").css('height', ht);
    jQuery(document).mouseup(function(e) {
        isMouseDown = false;

suggested css:

#my_resize {
background: url("/wp-includes/js/tinymce/themes/advanced/img/icons.gif") repeat scroll -800px 0 transparent;
cursor: n-resize;
display: block;
float: right;
height: 20px;
width: 20px;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
transform: rotate(-90deg);

WordPress: Paragraphs dissapeared upon form submission

If you try submitting a comment, post or a page and find your line breaks, paragraphs or other tags got stripped, don’t worry.

WordPress is trying to be intelligent. It processes all data submitted by default. This filtering process is managed by kses (see wp-includes/kses.php). Most roles, other than administrator, doesn’t have full control over the html tags submitted (see The library that manages this function is (wp-includes/capabilities.php). To add certain capabilities to certain role, you have to do it manually. There are of course nice plugins to handle this but if you just want to do it for a one role (‘customer’ in this case) for example, its just 2 lines of code.

$role = get_role('customer');

“customer” can now use any html tags in the submission form.

My experience with Magento, Woocommerce and other e-shop options


Magento has been a buzz word in the ecommerce industry for many years now. I worked with magento in my previous life and still get a lot of calls from many job agencies looking for magento developers today. The fact is that good magento developers are hard to find simply because having an in depth understanding of magento requires a bit of time. If you have good object oriented foundation, it  is not as complicated as many claimed. It was smooth sailing for me once I got the concept and the hang of it. The challenge for me was actually teaching the end user how to use it.

Magento was packed with features and was like out of the box. Most people only used probably 10% of what it had to offer. The large code base and number of objects also meant a strong requirement for hardware and server tweaks to get the desired performance. Caching helped but it was not enough. I was puzzled why most people opted for magento as their first choice when they wanted an e-shop. Perhaps this just showed how successful magento’s marketing was – they could brainwash the public into believing that magento was the best solution for everyone. I often warned people that unless they have lots (and I mean LOTS) of budget, magento should be the first thing they should strike from their list. Personally, I was not comfortable with the cost involved to get customisation in magento. You just need to look at the cost of some simple extensions to get a feel of what’s going on.

In conclusion, I was comfortable with the software but not comfortable with money making policy. My gut feeling was that they would face tougher competition from other ecommerce solutions as time went by (and I was right). Relunctantly, I filed a divorce with magento.

So go ahead with magento want a robust ecommerce solution and has too much money to spare.

Hosted solution

Most hosted solution such as shopify requires a monthly fee and is usually very user friendly. This can be a good option for people who requires little customisation and don’t want to host their own shopping cart. I never considered this as a good option for me because I always believed that personalisation and customisation was the key to improve client relationship. Hosted solution means limited customisation. The logic is simple, their software should be generic for everyone and they can’t break their software just to accomodate your requirements.

Woocommerce and Other Ecommerce Software

I have used other software over the years as well but came to like Woocommerce recently. Woocommerce offers the flexibility and robustness that an ecommerce shop should have. As it is open sourced (as with magento community edition), I had full control over the behaviour of the shop. There were enough hooks in the code to do what I want. I could also make it work with other wordpress plugins such as  membership management system, LMS, caching, seo…etc. The only thing I had to be careful was not to make the shop too bloated, ie by adding too much features into it, thereby affecting the performance.

I believe there are other good options out there and time will tell us when one stands out above the rest.

Display avatar only if it exists

In wordpress, hiding avatar if if doesn’t exists sounds like a good idea but it is hard to implement. I haven’t seen a good solution yet.

The problem is that if you do display the avatar even if the email doesn’t exists, people who don’t know about gravatar will find it confusing because they don’t know how to update their profile. However, if you hid the avatar completely, people who knows about gravatar don’t get to see their nice profile pic. Therefore the best solution is to hid it only if the email doesn’t exists. The tricky part is get_avatar() doesn’t tell you if the user is valid or not. If invalid, it just returns a mystery pic.

I have thought of several solutions. The best solution is probably to download the mystery image and md5 it against the dynamically generated avatar pic. This means I have an extra image to save in my existing installation which I don’t want. There is one more less accurate way of doing it, ie to compare the file size. I have wrote a short snippet here:

function display_avatar_only_if_it_exists($avatar) {
  preg_match("/src='([^']+)\'/", $avatar, $matches);
  $file = file_get_contents($matches[1]);
  // if user is unknown, it will by 2174 bytes
  if (strlen($file) == 2174) {
  else {
    return $avatar;
// do this only in the frontend
if (!is_admin()) {
add_filter('get_avatar',  'display_avatar_only_if_it_exists');

wordpress: Fatal error: Maximum function nesting level of ‘100’ reached

If you are running wordpress and have xdebug installed, this is a warning that you have something wrong in functions.php or plugin file. Don’t turn xdebug off. fix the code. One place to look at the ‘save_post’ hook. Take this code for example,

function my_post_update() {
  global $post;
  $post_title = esc_html($_POST['post_title']);
  $post_status = esc_html($_POST['post_status']);
  $post_type_args = array( 'ID' => $post->ID, 'post_status' => $post_status, 'post_title' => $post_title));
  // need to unhook to avoid infinite loop
  remove_action('save_post', __FUNCTION__, 9);
add_action('save_post', 'my_post_update', 9);

I intend to save my post before most of my plugins kicked in, so I put a priority of 9. However, because I run wp_update_post again within the function, my_post_update gets triggered again, resulting in infinite loop. To make sure we trigger the function once only, we have to remove the hook before wp_update_post.

How to set wordpress default language

wordpress language settings can be configured in wp-config.php file (look for the WPLANG definition). The default language is en_US if not specified. So if you just bought a plugin and it only comes with en_GB, you might be wondering why the your content wasn’t translated…. You can either go into wp-config.php and set the WPLANG or you can rename the en_GB file to en_US.

Create sortable custom columns / post types in wordpress admin

Its really nice to be able to use wordpress table sorting feature for your custom post types. There has been a lot of post in this this area. The fact is that its actually really simple.

Say I have a custom post type called “lesson”. Now I want to sort the lesson table based on certain meta_key (say _lesson_parent) of the lesson post.

This is the screenshot of my lesson post type.


In functions.php, I need to add the filters and hooks first.

if (is_admin()) {
    add_filter( 'manage_edit-lesson_columns', 'my_custom_lesson_column' );
    add_filter( 'manage_edit-lesson_sortable_columns', 'my_custom_lesson_column_sortable');
    add_action( 'manage_lesson_posts_custom_column', 'my_add_column_data', 11, 2 );

Now we need to create the 3 functions. Read the comments in the code… Its easy.

function my_custom_lesson_column ($col) {
  // I've created 2 columns here. lesson-order is the menu-order of the posts
  $col['lesson-order'] = _x('Lesson Order', 'column name', 'my-plugin' );
  // lesson-parent is the meta_key of the post. I want to be able to sort this column.
  $col['lesson-parent'] = _x( 'Lesson Parent', 'column name', 'my-plugin' );
  return $col;

function my_custom_lesson_column_sortable($col) {
  // Here, I define my sortable column, ie lesson-parent. The meta_key in the post_meta table is called _lesson_parent
  $col['lesson-parent'] = _x( '_lesson_parent', 'column name', 'my-plugin' );
  return $col;

// add data into the 2 columns. I can define whatever I want to be displayed in the columns here.
function my_add_column_data ( $column_name, $id ) {
  switch ( $column_name ) {
    case 'lesson-parent':
      // get the parent lesson title
      $lesson_parent_id = get_post_meta( $id, '_lesson_parent', true);
    case 'lesson-order':
      // get the menu order the the lesson
      echo get_post($id)->menu_order;

wordpress: Commonly used path reference

Some of the commonly used wordpress path references:

// gives you the url of your theme dir. works for childtheme.

// get child stylesheet

// gives you the root theme folder, ie xxx/wp-content/themes

// this gives you the absolute path

// gives you the plugin url

// gives you the plugin dir path

// ABSPATH refers to the directory in which wordpress is installed, for eg /var/www/htdocs/wordpress

// Absolute path to plugins dir

// Absolute path to plugin URL

// gives your the url of the homepage

another good resource is