A simple post. Some of you have asked me what I have in the Cotswold Photo WordPress functions.php file for our child theme (called Togger). So here it is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
<?php 
$theme = array(
  'theme_name' => 'Togger',
  'theme_slug' => 'togger',
);
 
require_once( get_template_directory() . '/wonderfoundry/wonderworks.php' );
 
add_action( 'wp_enqueue_scripts', 'cp_manage_styles_and_scripts', 99 );
 
function cp_manage_styles_and_scripts() {
// remove PrettyPhoto css from Ultimatum
  wp_deregister_style( 'prettyphoto' );
  wp_dequeue_style( 'prettyphoto' );
  wp_dequeue_style( 'ubermenu-basic' );
  wp_deregister_style( 'ubermenu-basic' );
//ultimatum loads prettyphoto scripts that need to be disabled
  wp_dequeue_script( 'fancybox' );
  wp_deregister_script( 'fancybox' );
  wp_dequeue_script( 'enable-lightbox' );
  wp_deregister_script( 'enable-lightbox' );
  wp_dequeue_script( 'lightbox-script' );
  wp_deregister_script( 'lightbox-script' );
  wp_dequeue_script( 'jquery-prettyphoto' );
  wp_deregister_script( 'jquery-prettyphoto' );
// Add Darkly themed Ubermenu style sheet
  wp_register_style( 'ubermenu_cp', get_stylesheet_directory_uri() . '/css/ubermenu_custom.min.css', false, '1.0.0' );
  wp_enqueue_style( 'ubermenu_cp' );
}
 
if ( function_exists( 'is_woocommerce' ) ) {
  function df_woocommerce_single_product_image_html($html) {
    $html = str_replace('data-rel="prettyPhoto', 'rel="lightbox', $html);
    return $html;
  }
  add_filter('woocommerce_single_product_image_html', 'df_woocommerce_single_product_image_html', 99, 1); // single image
  add_filter('woocommerce_single_product_image_thumbnail_html', 'df_woocommerce_single_product_image_html', 99, 1); // thumbnails
 
  add_action( 'wp_enqueue_scripts', 'manage_woocommerce_styles_and_scripts', 99 );
 
  function manage_woocommerce_styles_and_scripts() {
//dequeue parts of WooCommerce not required in any event
    wp_dequeue_style( 'woocommerce-general' );
    wp_deregister_style( 'woocommerce-general' );
    wp_dequeue_style( 'woocommerce-layout' );
    wp_deregister_style( 'woocommerce-layout' );
    wp_dequeue_style( 'woocommerce_prettyPhoto_css' );
    wp_deregister_style( 'woocommerce_prettyPhoto_css' );
    wp_dequeue_script( 'prettyPhoto' );
    wp_deregister_script( 'prettyPhoto' );
    wp_dequeue_script( 'prettyPhoto-init' );
    wp_deregister_script( 'prettyPhoto-init' );
//dequeue scripts and styles
    if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
//remove generator meta tag
//      remove_action( 'wp_head', array( $GLOBALS['woocommerce'], 'generator' ) );
//      remove_action( 'wp_enqueue_scripts', array( $GLOBALS['woocommerce'], 'frontend_scripts' ) );
      wp_dequeue_style( 'woocommerce-smallscreen' );
      wp_deregister_style( 'woocommerce-smallscreen' );
      wp_dequeue_style( 'woocommerce-multiple-addresses-plugin-styles' );
      wp_deregister_style( 'woocommerce-multiple-addresses-plugin-styles' );
      wp_dequeue_script( 'wc-add-to-cart' );
      wp_deregister_script( 'wc-add-to-cart' );
      wp_dequeue_script( 'woocommerce' );
      wp_deregister_script( 'woocommerce' );
      wp_dequeue_script( 'wc-cart-fragments' );
      wp_deregister_script( 'wc-cart-fragments' );
      wp_dequeue_script( 'woocommerce-multiple-addresses-plugin-script' );
      wp_deregister_script( 'woocommerce-multiple-addresses-plugin-script' );
    } else {
// Add Darkly themed WooCommerce style sheet
      wp_register_style( 'woocommerce_cp', get_stylesheet_directory_uri() . '/css/woocommerce_custom.css', false, '1.0.0' );
      wp_enqueue_style( 'woocommerce_cp' );
    }
    if ( ! is_bbpress() ) {
//remove bbPress scripts and atyles
      wp_dequeue_style( 'bbp-default' );
      wp_deregister_style( 'bbp-default' );
      wp_dequeue_style( 'bbp-default-bbpress' );
      wp_deregister_style( 'bbp-default-bbpress' );
      wp_dequeue_script( 'bbpress-editor' );
      wp_deregister_script( 'bbpress-editor' );
    }
 
  }
}
 
// Add Product Reviews section to Admin
if ((is_admin() )) {
  add_action('admin_menu', 'add_product_reviews'); 
 
  function add_product_reviews() { 
    global $menu, $product_reviews;
    $post_type = 'product';
    add_menu_page(__('Product Reviews'),__('Product Reviews'), 'manage_woocommerce', "edit-comments.php?post_type={$post_type}",'','',58.5);
  }    
 
  function separate_comments_and_review( $clauses, $wp_comment_query ) {
    global $wpdb;
    if ( ! $clauses['join'] )
      $clauses['join'] = "JOIN $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID";
    if(!empty($_GET['post_type']) &&$_GET['post_type'] == 'product' )
      {
    if ( ! $wp_comment_query->query_vars['post_type'] ) 
          $clauses['where'] .= $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", 'product' );
      } else {
    if ( ! $wp_comment_query->query_vars['post_type' ] ) 
          $clauses['where'] .= $wpdb->prepare( " AND {$wpdb->posts}.post_type != %s", 'product' );
      }
      return $clauses;
    }
 
  function change_comment_status_link( $status_links ) {
    if( isset( $_GET['post_type'] ) ) 
    {
      $status_links['all'] = '<a href="edit-comments.php?post_type=product&comment_status=all">All</a>';
      $status_links['moderated'] = '<a href="edit-comments.php?post_type=product&comment_status=moderated">Pending</a>';
      $status_links['approved'] = '<a href="edit-comments.php?post_type=product&comment_status=approved">Approved</a>';
      $status_links['spam'] = '<a href="edit-comments.php?post_type=product&comment_status=spam">Spam</a>';
      $status_links['trash'] = '<a href="edit-comments.php?post_type=product&comment_status=trash">Trash</a>';
    }
    return $status_links;
  }
 
  function check_current_page( $screen ) {
    if ( $screen->id == 'edit-comments' ) // Checking comment page
    {
      add_filter( 'comments_clauses', 'separate_comments_and_review', 10, 2 );
      add_filter( 'comment_status_links', 'change_comment_status_link' );
      if( isset( $_GET['post_type'] ) ) 
      {
        function product__comment_columns( $columns ) {
          $columns['comment'] = __( 'Review' );
          return $columns;
        }
        add_filter( 'manage_edit-comments_columns', 'product__comment_columns' );
      }
    }
  }
 
  add_action( 'current_screen', 'check_current_page', 10, 2 );
  add_filter('menu_order','change_label');
 
  function change_label($stuff) {
    global $menu,$submenu;
    global $wpdb;
    $sql = "SELECT count(*) FROM $wpdb->comments LEFT OUTER JOIN $wpdb->posts ON ($wpdb->comments.comment_post_ID = $wpdb->posts.ID)
      WHERE comment_approved = '0' AND comment_type = '' AND post_password = '' AND post_type != 'product' ORDER BY comment_date_gmt DESC";
    $comments = $wpdb->get_col($sql);
    $sql1 = "SELECT count(*) FROM $wpdb->comments LEFT OUTER JOIN $wpdb->posts ON ($wpdb->comments.comment_post_ID = $wpdb->posts.ID)
      WHERE comment_approved = '0' AND comment_type = '' AND post_password = '' AND post_type = 'product' ORDER BY comment_date_gmt DESC";
    $reviews = $wpdb->get_col($sql1);
    $menu[25][0]= 'Comments<span class="awaiting-mod count-'.$comments[0].'"><span class="pending-count">'.$comments[0].'</span></span>';
    $menu[58.5][0]= 'Reviews<span class="awaiting-mod count-'.$reviews[0].'"><span class="pending-count">'.$reviews[0].'</span></span>';
    return $stuff;
  }
}
 
// Remove Gravatar (taken from a Buddy Press board, hence bp_ naming
function bp_remove_gravatar ($image, $params, $item_id, $avatar_dir, $css_id, $html_width, $html_height, $avatar_folder_url, $avatar_folder_dir) {
  $default = get_stylesheet_directory_uri() .'/_inc/images/bp_default_avatar.jpg';
  if( $image && strpos( $image, "gravatar.com" ) ){
    return 'avatar';
  } else {
    return $image;
  }
}
 
add_filter('bp_core_fetch_avatar', 'bp_remove_gravatar', 1, 9 );
 
function remove_gravatar ($avatar, $id_or_email, $size, $default, $alt) {
  $default = get_stylesheet_directory_uri() .'/_inc/images/bp_default_avatar.jpg';
  return "{$alt}";
}
 
add_filter('get_avatar', 'remove_gravatar', 1, 5);
 
function bp_remove_signup_gravatar ($image) {
  $default = get_stylesheet_directory_uri() .'/_inc/images/bp_default_avatar.jpg';
  if( $image && strpos( $image, "gravatar.com" ) ){
    return 'avatar';
  } else {
    return $image;
  }
}
 
add_filter('bp_get_signup_avatar', 'bp_remove_signup_gravatar', 1, 1 );
 
// Defer jQuery Parsing using HTML5′s defer property
if (! is_admin() ) {
  function defer_parsing_of_js ( $url ) {
    if ( FALSE === strpos( $url, '.js' ) ) return $url;
    if ( strpos( $url, 'jquery.js' ) ) return $url;
    return "$url' defer ";
  }
  add_filter( 'clean_url', 'defer_parsing_of_js', 11, 1 );
}
 
//turn heatbeat api off (speeds up admin-ajax.php)
 
function my_deregister_heartbeat() {
    global $pagenow;
 
    if ( 'post.php' != $pagenow && 'post-new.php' != $pagenow ) {
        wp_deregister_script('heartbeat');
        wp_register_script('heartbeat', false);
    }
}
add_action( 'admin_enqueue_scripts', 'my_deregister_heartbeat' );
 
// the next 2 commented out functions (leave commented out on a working site, uncomment it for a test site) will show the hooks being loaded on a page
 
/*function dump_hook( $tag, $hook ) {
    ksort($hook);
 
    echo "<pre>>>>>>\t$tag<br>";
 
    foreach( $hook as $priority => $functions ) {
 
    echo $priority;
 
    foreach( $functions as $function )
        if( $function['function'] != 'list_hook_details' ) {
 
        echo "\t";
 
        if( is_string( $function['function'] ) )
            echo $function['function'];
 
        elseif( is_string( $function['function'][0] ) )
             echo $function['function'][0] . ' -> ' . $function['function'][1];
 
        elseif( is_object( $function['function'][0] ) )
            echo "(object) " . get_class( $function['function'][0] ) . ' -> ' . $function['function'][1];
 
        else
            print_r($function);
 
        echo ' (' . $function['accepted_args'] . ') <br>';
        }
    }
 
    echo '</pre>';
}
 
function list_hooks( $filter = 'wp_footer' ){
    global $wp_filter;
 
    $hooks = $wp_filter;
    ksort( $hooks );
 
    foreach( $hooks as $tag => $hook )
        if ( false === $filter || false !== strpos( $tag, $filter ) )
            dump_hook($tag, $hook);
}*/
 
// The next function (leave commented out on a working site, uncomment it for a test site) will show the assets being loaded on a page (css and js)
 
/*function inspect_scripts_or_styles() {
    echo "<br />JS assets:<br />";
    global $wp_scripts;
    foreach( $wp_scripts->queue as $handle ) :
        echo $handle . '<br />';
    endforeach;
    echo "<br />Style assets:<br />";
    global $wp_styles;
    foreach( $wp_styles->queue as $handle ) :
        echo $handle . '<br />';
    endforeach;
}
 
add_action( 'wp_print_scripts', 'inspect_scripts_or_styles' );*/

Enjoy.

Do a Google search for WooCommerce and admin-ajax.php and you will find a lot of WordPress users complaining about slow site loading. In the case of our front page, WooCommerce’s javascript files were making repeated calls to admin-ajax.php and adding about 4 seconds to the load time. Having done all I could to speed our site up, this was quite depressing. In my last post you will have read about how I was trying to tidy up the loading of stylesheets and javascripts. In the end I did get the Firefox extension called Dust-Me Selectors working. It seems it did not like my shiny new 64 bit Windows 8.1 machine, but was OK with my old 32 bit Windows 7 machine.

I now have a bit of code that shows the program names for all the stylesheets and javascripts that load on a page. I have made sure only one version of each of PrettyPhoto, Font Awesome, Jquery and Bootstrap is loaded, and now the styles and scripts from WooCommerce and bbPress plugins, and their associated extended plugins, only load on the pages that they need to. The overall result is that, depending on when the test is run, the site home page can load in about a second, compared to ten times that we I had started out.

Why on earth a plugin writer would not do this as part of their coding is beyond me. For one plugin, WooCommerce Tabs Pro, I posted the simple hack needed to their plugin code to do this, in the hope that they will incorporate this into future builds.

Within this series of fixes, I also turned off the heartbeat code in WordPress. Almost all the changes were done in our functions.php file of our child theme.

WebPagetestHow do you know that your site is suffering from bloated WordPress CSS? Our site currently compresses and merges the multiplicity of css files into one big file. Use a site speed test tool like WebPagetest. Once your site has been tested, take a look at the Filmstrip view. Ours has a long bar where the main css loads.

Not good. That’s nearly 3 seconds! So, if you are, like me, using Firefox, make sure you have an extension called Dust-Me Selectors. It will let you know how much (and which styles) of your css tags are not used by your site. Warning! You will need a sitemap xml file for it to work with and it may take a while! On my site it found 22 stylesheets with nearly 9,500 style selectors, of which 95% are not used anywhere on the site!

Not loading those style selectors would shave 3 seconds off the page load time. Indeed, there may also be some duplicated selectors, removing these might save even more time. When you consider that our home page takes 4-4.5 seconds to load, that is a free turbo boost. Bloated WordPress CSS? Yes.

I am taking the unusual step of adding to this post. Firstly, I could not get Dust-Me Selectors to work reliably. It kept crashing Firefox when asked to clean the files. Also, I could see no way to export the used selectors as a CSS file. I then had a good look around for other thoughts and ideas. Looking at the filmstrip image above, you can see that it is the default stylesheet for the site that is the real problem. So, I need to focus on that stylesheet in particular. Secondly, I can see stylesheets being loaded that are either not used at all, or are used by a plugin on one page only. It is possible to de-register a stylesheet and then re-register it only for the page it is used on. For unused stylesheets, simply de-queue them. You can do both of these things in your functions.php file. Where you can also de-queue the default theme stylesheet and load your own customized, much smaller one.

The key requirement is to find a tool that will check the entire site, not just one page. I can only find one such tool, at a site called Unused CSS. It is a paid for service. Given that you are likely to be using the tool many times, the annual $59 fee seems the best option. You can have a free trial of one website to get a feel. I have registered and asked for this site to be checked, so I am now waiting for the results, which they email you about 30 minutes later.

A final note, after I checked through all the data from Unused CSS, was that the site delay of over 2 seconds was due to one file created by a WordPress plugin called Simple Custom CSS. With the custom css transferred to the Ultimatum Theme Custom CSS section of the Theme Layout, the site is now loading almost OK. Except it still has a First Byte Time that is far too high. More digging needed.

A final, final edit to this post. The First Time to Byte speed of our server is OK, but getting that data to the end user’s computer can add many seconds. I tested the response time from our server (in Herndon, VA, USA) to a test point a few miles away (Dulles, VA) and also to London, UK. The test page took as much as 3 times longer to load to the more distant London. It is clear we will need a CDN to improve this.

How to get a faster WordPress powered website? Now that I am (reasonably) happy with the look and feel of things, I have turned my focus to performance. The website as it was took far too long to provide the first byte of data to the visitor’s browser, and then took far too long to follow with the content. The result in real world testing would be that a visitor would not wait for the site to load, and would go elsewhere, or give up. A faster WordPress server? Maybe.

I think the server is pretty much as fast as it can be and is not lacking in resources. Tests I have run show that the server is not having to work at all. So, only a few variables exist to slow things down:

  • The data uplink speed from the server. Currently at 100 mbps, it could be upgraded to 1 gbps (ten times faster). I need to find the cost.
  • Every time a page is requested, WordPress has to make it from the database. That takes time. Better to cache that page if it is unlikely to change from one visit to the next. I have tried W3TotalCache and WP-FFPC, but I noticed that our site security software, Wordfence, has 2 cache types available as an option. I can’t get the advanced Falcon working, as our site is not using .htaccess, and so the rules needed for Falcon need re-writing (I have posted a support ticket for this help), but the basic cache works fine. Much faster than W3TotalCache and WP-FFPC. No extra cost there.
  • Those static files (stylesheets, javascripts and cached pages) from our own server are still too slow. The online guides I have read suggest that using a Content Delivery Network (CDN) will speed this up. As our host is a Cloudflare partner, I have asked for details to upgrade to Cloudflare Pro, which will mean that Apache server will need mod_cloudflare adding. Hopefully BBGN, our host, can do that. The most that this will cost is $5 per month (75% off the normal price).
  • Physical distance. No matter where your server is in the world, the data has to travel all the way to the visitor’s computer, and that takes time. A CDN holds copies of your files in dozens of places in the world, again reducing the load time.

Hopefully, all this, when I get details and implement them, will give us a faster WordPress.

WordPress and Apache Server are, at best, uncomfortable bedfellows. I know, WordPress.com uses nginx instead of Apache, but they have the advantage of controlling the installed environment. In the real world, nginx is not yet ready for the great masses to use. If you have Cpanel on your hosting solution, and you have WHM (the control panel that is, in effect, one layer above CPanel), you probably have been given a root password.

That means you can (at least in Windows I can) use a program like PuTTy to gain root access to your server. So, instead of having a .htaccess file to handle things like headers, you can put these in the root httpd.conf file. Or, more likely, put them in the post_virtualhosts_global.conf file, that your http.conf file (the file that sets up Apache) calls to allow you to modify Apache.

And then something very weird happened. Bits of my WordPress admin panel were not working (such as, the menu panel showed no menus). So I went to find out if the menu had disappeared from the database. I opened Adminer to be given an error saying my temporary disk was full. I Opened WHM and went the Show Disk Usage option, and lo, the tmpDSK, at 4GB, was FULL!!

I went into the root using PuTTy and used the command:

rm -rf /tmp/*

Which emptied the tmpDSK. For now.

We have been having server problems. We actually have 4 domains on the server; 2 have their own static IP address and 2 share the one used by Cotswold Photo. One of those with their own static IP address simple will not load. The tech guys at Big Brain Global Networks have been on this for over a day now, as yet with no resolution. [Note, a few hours later and it is resolved]

So, whilst that is going on, I have been honing the site performance. The server has been upgraded to Apache 2.4 and PHP5.5. We are still using memcache in PHP, but APC has been replaced by APCu because PHP5.5 includes Zend Opcache (although you have to enable it). WordPress has been upgraded to 4.0. We have removed W3 Total Cache, as it was causing intermittent issues, and have used 3 plugins instead; WP-FFPC (in APCu mode), Autoptimize and APCu Object Cache Backend. The latter isn’t really a plugin, it is a cache that sits in the wp-content folder.

Oh, and WooCommerce is now at 2.2.2. I am off to take a close look at the Apache http.conf file to see if I can see what the problem is. This means logging in to the unix server and navigating my way around a command line interface. It’s like stepping back over 30 years – not nice. Whilst I am at it, what other plugins have been added to our Word Press installation?

  • Better Font Awesome
  • Block Bad Queries (BBQ)
  • Remove query strings from static resources
  • Fast Secure Contact Form (replacing Contact Form 7 and two associated plugins)

Nose back to grindstone for now.