Jan 292016

WP comment nesting logoIt’s not like a lot of people are actively commenting on this weblog, but there are at least two posts which do have quite a few replies (well, for my standards), the one about using [48GB of RAM on an X58 chipset mainboard], and the other about [SSD TRIM, exFAT, EXT3/4, ASPI and UDF 2.5 on Windows XP / XP x64]. To make it possible for users to interact and discuss in a better fashion, I had enabled nested comments. However, the comments took too much horizontal space away, limiting the usable depth of nesting levels. The last (10th) comment would be like ~2cm narrow, and extremely hard to read.

Finally, I thought I should really fix that. So I found that the corresponding CSS code for nested comments was in my themes’ subfolder, in a file called wp-content/themes/<theme name>/style.css. By inspecting my website with the [Vivaldi] web browser (based on Chromium), i found that the CSS class ul.children was likely to blame, as the comments are actually a combination of ordered and unordered HTML lists, see here:

  1. ul.children { padding-left: 20px; }

On top of that, <ul> gets a wide margin-left set by default as well, worsening the situation. This resulted in child comments indenting by something like 40 pixels. For nothing. So I fixed that:

  1. ul.children {
  2.         padding-left: 0px;
  3.         margin-left: 5px;
  4. }

That way it looks much, much more compact and much nicer. This gave me enough space to make the nesting levels even deeper without sacrificing readability (like it was before at 10 levels), so I decided to go for 16 levels. Better than nothing.

The WordPress guys however – in their infinite wisdom – limited the depth to 10 levels, so I was already at the maximum?! Back to inspecting with Vivaldi – the comment settings page this time. That way I found out that the limit is set in wp-admin/options-discussion.php. This is the corresponding code:

  1. <?php
  2. /**
  3.  * Filter the maximum depth of threaded/nested comments.
  4.  *
  5.  * @since 2.7.0.
  6.  *
  7.  * @param int $max_depth The maximum depth of threaded comments. Default 10.
  8.  */
  9. $maxdeep = (int) apply_filters( 'thread_comments_depth_max', 10 );
  11. $thread_comments_depth = '</label><label for="thread_comments_depth"><select name="thread_comments_depth" id="thread_comments_depth">';
  12. for ( $i = 2; $i <= $maxdeep; $i++ ) {
  13.         $thread_comments_depth .= "<option value='" . esc_attr($i) . "'";
  14.         if ( get_option('thread_comments_depth') == $i ) $thread_comments_depth .= " selected='selected'";
  15.         $thread_comments_depth .= ">$i</option>";
  16. }
  17. $thread_comments_depth .= '</select>';

Yeah, ok. So I just changed the $maxdeep assignment to this:

  1. $maxdeep = (int) apply_filters( 'thread_comments_depth_max', 16 );

And with that we’re done, as everything seems to be working properly:

Nesting more than 10 WordPress comments

Nesting more than 10 WordPress comments without relying on some additional CPU-hungry plugin.

Of course, I now need to keep track of my changes, because updating either WordPress itself or my theme will revert my changes back to the previous behavior. But since I haven’t modified my WordPress installation that much so far, I can still live with patching everything back in manually after an upgrade.

And another tiny improvement made…

Update: I just noticed, that sometimes, very long “words” (like HTTP or FTP links without any spaces in them) would overflow the barriers of the div layers they were sitting in in the comments, so while I was dealing with restoring my modifications after a theme update, I fixed the word wrapping as well. The related CSS code sits in wp-content/themes/<theme name>/style.css again. So, I look for the following part…

  1. .comment-body p { line-height: 1.5em; }

…and change it to this:

  1. .comment-body p {
  2.         line-height: 1.5em;
  3.         word-break: break-word;
  4. }

Now even very long words like links will be wrapped properly! With word-break: break-word; only words without spaces will be broken where necessary, so normal sentences with whitespaces for delimiters will still be broken at the spaces, like it should be!

May 032014

Gravatar logoSince I used WordPress as my weblog software, it has come with Gravatar support. Actually, I’m thinking WordPress was probably not the best choice anyway, you know; The huge and heavy PHP code running at a decent speed on a quad Pentium PRO 200MHz 1MB? Not easily done. But I’m gonna talk about Gravatar here, not running modern content management systems on hardware of the mid-90s. So what is Gravatar? Essentially a service that allows you to link a centralized avatar picture of yours into every blog post you make, or any other post on any other Gravatar-enabled site. As such, it would give you a small piece of ID that stays the same across sites. And as you can read on Gravatars own weblog, they’re [tightly interwoven with WordPress] these days.

Now why would I want to get rid of that?

Mind you, I never liked the idea of Gravatars. There is just something fishy about free stuff, especially when it’s a highly centralized service. Not free software, but free services. As a colleague of mine from Malaysia always used to say: There is no free lunch!

The first time I really noticed it (again, after my concerns had faded away) was that the Ghostery plugin reported Gravatar links (Images, JavaScript, CSS etc.) pulling in content from Gravatar servers into this weblog. See [Ghosterys Gravatar report]. Now reading Ghosterys description, you’d find comforting words like these:

Data Sharing:
Data is not shared with 3rd parties.

But also more alarming ones, like:

Data Collected:
Anonymous (Browser Information, Date/Time, Demographic Data, Serving Domains)
Pseudonymous (IP Address (EU PII))

Data Retention:

Your own Gravatar picture would be linked to the email address you provide to them, and when you use it, it will also log your local IP addresses and with it your location, time etc., which makes anything they may data mine PII, personal identifiable information.

There is always an essential question as to why something is and can be free. Registering for a Gravatar does cost nothing. But how? Writing Open Source Software and giving it away for free is comparably easily explained: It only costs the time of some enthusiasts who want to make the world a better place (mostly). But hosting massive amounts of data? That requires servers, bandwidth, storage solutions, which rarely come free for NGOs or non-charity organizations. So they need to make money. How?

Like with other free services, it is quite likely, that Gravatar is not a free product. It is indeed more likely, that it turns the user, data about him or her and his or her networks into its product, selling that very data to the highest bidder, just like Facebook or maybe even Google presumably do. Or many others. Naturally, there are people who are really concerned about privacy and data leaks concerning Gravatar, like [this guy here]. Now if even lawyers are concerned…

Plus, Gravatar still does not allow account deletion. It’s just not possible. So they’ll keep tracking your email address forever, whether with your consent or without…

Luckily, I found a solution provided by the PHP coder [TheDeadMedic] on [StackOverflow], whis is supposed to be used in conjunction with the [Simple Local Avatars] plugin.. Just to make sure, I’ll copy his code over here. The first thing is the modification of your WordPress themes’ functions.php, you can just append the code at the end, and you would need to place a new, local default avatar into your themes images/ directory, called default_avatar.png:

  1. function __default_local_avatar()
  2. {
  3.     // this assumes default_avatar.png is in wp-content/themes/&lt;active theme&gt;/images
  4.     return get_bloginfo('template_directory') . '/images/default_avatar.png';
  5. }
  6. add_filter( 'pre_option_avatar_default', '__default_local_avatar' );

And then, create a new plugin folder like DefaultLocalAvatar/ or whatever in your WordPress plugins folder, and copy the following into a PHP script file inside that folder:

expand/collapse source code
  1. <!--?php 
  3. /**
  4.  * Plugin Name: Disable Default Avatars
  5.  * Plugin URI: http://wordpress.stackexchange.com/questions/17413
  6.  * Description: To be used alongside <a href="http://www.get10up.com/plugins/simple-local-avatars-wordpress/"-->Simple Local Avatars, disabling all default avatars and falling back to a single image. Use the filter <code>local_default_avatar</code> to set the path of the image.
  7.  * Version: 1.0
  8.  * Author: TheDeadMedic
  9.  * Author URI: http://wordpress.stackexchange.com/users/1685/thedeadmedic
  10.  */
  12. if ( !function_exists( 'get_avatar' ) ) :
  13. /**
  14.  * Retrieve the avatar for a user who provided a user ID or email address.
  15.  *
  16.  * @since 2.5
  17.  * @param int|string|object $id_or_email A user ID,  email address, or comment object
  18.  * @param int $size Size of the avatar image
  19.  * @param string $default URL to a default image to use if no avatar is available
  20.  * @param string $alt Alternate text to use in image tag. Defaults to blank
  21.  * @return string <img alt="" /> tag for the user's avatar
  22. */
  23. function get_avatar( $id_or_email, $size = '96', $default = '', $alt = false ) {
  24.     if ( ! get_option('show_avatars') )
  25.         return false;
  27.     static $default_url; // use static vars for a little caching
  28.     if ( !isset( $default_url ) )
  29.         $default_url = apply_filters( 'local_default_avatar', get_template_directory_uri() . '/images/default_avatar.png' );
  31.     if ( false === $alt)
  32.         $safe_alt = '';
  33.     else
  34.         $safe_alt = esc_attr( $alt );
  36.     if ( !is_numeric( $size ) )
  37.         $size = '96';
  39.     $avatar = "<img class="avatar avatar-{$size} photo avatar-default" src="{$default_url}" alt="{$safe_alt}" width="{$size}" height="{$size}" />";
  40.     return apply_filters( 'get_avatar', $avatar, $id_or_email, $size, $default, $alt );
  41. }
  42. endif;
  44. function __limit_default_avatars_setting( $default )
  45. {
  46.     return 'local_default';
  47. }
  48. add_filter( 'pre_option_avatar_default', '__limit_default_avatars_setting' );
  50. if ( is_admin() ) :
  51. function __limit_default_avatars( $defaults )
  52. {
  53.     return array( 'local_default' =&gt; get_bloginfo( 'name' ) . ' Default' );
  54. }
  55. add_filter( 'avatar_defaults', '__limit_default_avatars' );
  56. endif;
  57. ?&gt;

After that, only thing left is to activate the new mini-plugin in your WordPress Dashboard. When done, all Gravatar content will be gone and nothing Gravatarish will be pulled into your weblog when users come to visit. The only downside is that if you do not have user registration enabled – it’s disabled here – all users will receive the local “default_avatar.png” you put into your themes’ images/ folder. But I think that’s a small price to pay for enhanced performance (less connections to remote servers, less JavaScript and CSS!) and enhanced privacy.

If you are allowing anyone from the Internet to register on your weblog site, you can actually enable them to just upload their avatar to your site using the Simple Local Avatars plugin. That way, everything is perfectly decentralized (My decentralization vision is a thing I’m planning to write about in the future), and people can still use their favorite avatar, no data mining included.

As soon as all server-side and client-side caches are clear for good, this here weblog will no longer serve nor allow any Gravatar content whatsoever! Gone for good!

Aug 022013

WP logoSo there is a new WordPress release now, updating 3.5.2 to 3.6.0. Trying to update to a new minor release online worked every single time so far, so I just updated some plugins before, which have been modified to ensure WP 3.6 compatibility, and then went ahead. What WordPress does is download the proper update, unpack it, overwrite and/or patch all script files and then update the database. Before you go ahead with all that, the web updater warns the administrator (me in that case) to backup everything. Since by now I consider this weblog backupworthy I thought: “Nah, better DO that this time”.

I would’ve almost needed that full backup!

The process stalled in the last upgrade phase, which is the database upgrade. My assumption is, that the PHP script doing that finally ran into the maximum amount of time allowed for any single PHP script to execute before getting killed. I am not sure if that’s the reason, but it makes some sense.

Luckily, the online update part of WordPress was still operational and I was able to initiate a full WP core reinstall. This was only possible because I was still logged in! A new login from the outside failed. All interactive frontend parts were effectively broken, only the static HTML cache of the frontend was still live making everything seem “ok” to the occasional visitor. If anybody would’ve tried to comment some article at that point, that person would’ve hit a brick wall right there.

Pure luck that the core reinstall is obviously faster than the upgrade. Now it seems everything is fully on 3.6.0 level and all plugins are still alive. At least I hope so. That was a close one. In the future I will likely do full backups every time I update the core WordPress software. Just in case my suspicion is true, I also gave PHP a little bit more time before a running script is suspected to be an infinite loop and hence worthy of being killed.

Jul 072013

HardeningHardening WordPress – at least it’s easier than hardening steel alloys! It seems the base installation of a WordPress web log (like this one here) is not very well prepared for attacks from the web, especially brute force break-in attempts. After the last attack, I simply did a few file and folder renaming tricks to block people from accessing the corresponding scripts. This was ineffective, as there are obviously multiple attack vectors, not just wp-login.php or wp-admin.

The renaming still worked for a while, but recently, new attacks happened, slowing down the server and even crashing some cached PHP byte code, which essentially meant crashing the whole web server, as PHP is loaded as a module for performance reasons. Needless to say, this was bad. Really bad.

However, a quick search amongst available plugins quickly revealed several solutions, mostly revolving around security by obscurity, which is in this case actually a very valid approach, masking all critical scripts and folders with required ID/URL strings. Wrong string? You get relayed to some arbitrary location. I decided to relay the attacker to his own localhost. With some luck it hits an open port on his or her own machine. ;)

Also, there are other useful plugins, like tarpit-style ones that simply ban IP addresses that fail too many login attempts in case somebody does actually guess the necessary URL strings to access the real login page. There are even ones, that you can tie to the well-known fail2ban scripts on Linux operating systems, that work together with iptables.


Bottom line is: Even if your web log is small, with very few visitors – like mine – you should still lock certain areas down properly and harden the site. Otherwise there will most definitely be shit happening. You may want to look for login page hardening scripts and also ones that can limit login attempts. A masking script and a tarpit-style one should work together quite well, like maybe [Stealth Login Page] and [Limit Login Attempts], which are confirmed to work together very well. There are also others that you could look into, like [Login Security Solution], [Secure Hidden Login] or [Simple Login Lockdown] and many more.

For now, this seems to handle the problem very well!

May 302013

Hack logoTonight at around 10:30PM, XIN.at came under attack by what I assume to be a larger bot network. As a result, all CPU-intensive web applications on my server, including this weblog, forums, CMS’ etc. went offline because of the attacks causing excessive PHP CPU cycle usage and timeouts of all scripts. It seems the primary target of the attacks was this weblogs login page, but the largely inactive forum was also a target obviously. Log file analysis suggests, that this was a distributed brute force attack to break into XIN.at web services (and probably a few thousand other web services on other servers). The attacking hosts were many machines spread across multiple IP pools, so I have to assume that this is some major attack to break into multiple web services at once, for purposes currently not known to me.

Amusingly, the attack failed on this server while remaining in its infancy. As a security measure, runtime of PHP scripts is limited on this server. The attack was so massively parallelized, that it simply overloaded everything, causing all scripts to return HTTP 503 errors to almost all attacking hosts. In laymans terms: This server was simply too SLOW to allow the attackers scripts to ever succeed. I can only laugh about that simple fact. This server was just too old and crappy for this shit. ;)

Well, still, for that reason, this weblog and most of XIN.at’s web services went offline for around 2½ hours. For now, it is back online, and hopefully the brute force attacks have ceased by now, as my monitoring would suggest. We’ll see.