Accessibility is not a feature

The yearly Global Accessibility Awareness Day is still some weeks away – this year it be celebrated on 20 May 2021 – but some recent discussions made be pick this topic for my next blog post.

It’s not just a number, it’s people!

In a recent discussion on twitter regarding a corona contact tracing app, the importance of an accessible app was discussed once more. I can’t tell you how many time I’ve read statements like these:

Read more →

Repairing the WordPress database

Earlier this week one website was not responding and showing an error that the connection to the database was not possible. Such an error is usually a sign, that the credentials are invalid. But no changes have been made to either the credentials or the configuration file. After activating the WP_DEBUG mode I found out that the database was corrupt.

Repairing the database with the WP-CLI

My first approach was using the WP-CLI. There you have an option to optimize the tables, which can also sometimes repair errors:

wp db optimize

This command is usually been used when large amounts of rows where deleted but the table size was not reduced. But it will not always also fix issues with the tables. Fortunately there is another command for this task:

Read more →

Show comments of private posts in the comments widget

In September 2017 I wrote a blog post on how to show tags from private posts in the tags widget. Three weeks ago I got a comment (on the German blog post) if something similar would be possible for comments from private posts. In this posts I would like to present you a simple solution to that comment.

Filter the comments query arguments

By default the comments widget will only show five latest approved comments from public posts. This is how the query arguments look like:

Read more →

Get detailed download stats for plugins

Recently I was checking the downloads of a plugin in the plugin directory. In the “Advanced View” of a plugin you will find a “Downloads Per Day” graph with the numbers from the past 267 days. Let’s take a look at the stats for the poplar Antispam Bee plugin:

Downloads per day for Antispam Bee

When you hover over the graph, you will find the date an number of downloads of that day. But I wanted to know the summary of the downloads in the first few weeks after the latest release (the spike in the graph) and writing down all numbers manually to add them up would have been a bit too much work. So how could I get those numbers more easily?

Read more →

Better security for WordPress with secure server headers

There are many options to make a WordPress website more secure. For some of those options you might use additional plugins. But there is an easy way to increase the security by setting only some server settings. But before I go into details, let’s find out how to get a current security status of your site.

Mozilla Observatory

An excellent tool to test your website for security is the Mozilla Observatory tool. This tool scans not only the headers you server is sending, it will also scan TLS and SSH settings and will use some third-party tools to give you a brief overview. A result might look like this, before you adjust any settings:

Read more →

Debugging memory usage

Last week, an issue was reported for a site. It only occurred sometimes and the result was an Error 500. Activating the WP_DEBUG mode I was quickly able to find out that the memory limit was hit. The only fix was an increase of this limit, as the functionality on the pages used quite a lot memory and an optimization was not easy to find.

In those cases it would be nice to be able to find out how much you have to increase the limit. So how much would a page load usually take. There are several ways to find this out.

The danegerous way

One simple solution would be to decrease the memory limit until you always get a memory limit error. Then you slightly increase the limit until you only get the error some times. This is the value the actual usage would likely be. Then you add some more to that limit, so you don’t run into it again.

Read more →

Set the debug level using error_reporting

Everyone knows that. You work on a website and you run into issues. Sometime you even get a critical error like this:

But in some cases, you don’t get the error on every single request. Then you have to activate the debug mode and log all errors.

Activating the debug mode

To activate the debug mode for WordPress you simply have to set some constants in your wp-config.php file. These are the defaults:

Read more →

Prevent deletion of post type items that are still used

This is probably an issue may of you have tun into. You embed a post type items into a page or post and then you delete this item without knowing it was embedded somewhere. For this example I will use the popular contact form plugin “Contact Form 7”, but it works with any other post type.


We install the Contact Form 7 plugin, create a form and place it into a page using the Gutenberg block. The page’s content will look like this:

<!-- wp:contact-form-7/contact-form-selector {"id":39,"title":"Contact form 1"} -->
<div class="wp-block-contact-form-7-contact-form-selector">[contact-form-7 id="39" title="Contact form 1"]</div>
<!-- /wp:contact-form-7/contact-form-selector -->

We can see the ID for the custom post type wpcf7_contact_form used in the block attributes and the inner shortcode the plugin is still using.

Deleting a post type items

If we now navigate to the “Contact -> Contact Forms” overview we can use the “Bluk actions” to delete the contact form. When we then open the page in the frontend, where the contact form was previously embedded, we get this, quite funny, result:

[contact-form-7 404 "Not Found"]

Instead of rendering the contact form, we will get a “broken shortcode”. For other post types the “error handling” might look differently or even break your site.

Protect post type items from being deleted

So how can we easily prevent deleting such an item if it’s still used somewhere? We would have to search all other post types for this item. If we do that with the “post content” looking for the markup, this will be a very costly operation and for some embeddes it might not easily work. So we will get use some help to make it faster and more reliable.

Using a “helper taxonomy”

We will introduce a taxonomy for this deletion prevention. This taxonomy needs no UI and does not have to be public. These few lines are enough to register it:

function deletion_prevention_register_taxonomy() {
		array( 'post' ),
			'hierarchical' => false,
			'public'       => false,
add_action( 'init', 'deletion_prevention_register_taxonomy' );

We don’t have to register it for every post type we want to use it with, registering it for posts is enough. Now we can add this taxonomy.

Add the helper taxonomy to posts or pages

For our example we parse for all blocks, when a post type is saved. If we find a contact form block, we use the ID of that contact form as the term name:

function deletion_prevention_save_post( $post_ID, $post, $update ) {
	if ( ! in_array( $post->post_type, array( 'post', 'page', true ) ) ) {

	$blocks = parse_blocks( $post->post_content );

	$forms = array();
	foreach ( $blocks as $block ) {
		if ( 'contact-form-7/contact-form-selector' === $block['blockName'] ) {
			$forms[] = (string) $block['attrs']['id'];

	wp_set_object_terms( $post_ID, $forms, 'deletion_prevention_tax' );
add_action( 'save_post', 'deletion_prevention_save_post', 10, 3 );

This callback limits the check for posts and pages only and it only searches for the contact-form-7/contact-form-selector. If you want to prevent the deletion of multiple embedded post types, you might want to use additional taxonomies, other term names or some term meta.

Now that we save this taxonomy, we know exactly where our contact forms are embedded. If we want to delete a contact form, we have to find those posts or pages.

Prevent the deletion

Every time a post type is deleted, some actions are called. One is called just before the deletion is being executed. This is where we hook in:

function deletion_prevention_delete_check( $delete, $post, $force_delete ) {
	deletion_prevention_check( $post );
add_action( 'pre_delete_post', 'deletion_prevention_delete_check', 10, 3 );

Some post types can also be send to trash (Contact Form 7 does not support that), so we might also want to prevent trashing of these items:

function deletion_prevention_trash_check( $trash, $post ) {
	deletion_prevention_check( $post );
add_action( 'pre_trash_post', 'deletion_prevention_trash_check', 10, 2 );

Now you can see why we are using another new function for the check.

Check for current embeds of the item

Now we are at the final stage. We will search for any embeds of the post type item we are trying to delete or trash by using a taxonomy query on posts and pages using the ID as the term slug:

function deletion_prevention_check( $post ) {
	$args = array(
		'post_type'      => array( 'post', 'page' ),
		'post_status'    => array(
		'tax_query'      => array(
				'taxonomy' => 'deletion_prevention_tax',
				'field'    => 'slug',
				'terms'    => (string) $post->ID,
		'posts_per_page' => 1,
		'fields'         => 'ids',

	$posts = get_posts( $args );

	if ( ! empty( $posts ) ) {
						/* translators: %1$s: link to a filtered posts list, %2$s: link to a filtered pages list */
						'This item as it is still used in <a href="%1$s">posts</a> or <a href="%2$s">pages</a>.',
					admin_url( 'edit.php?post_type=post&taxonomy=deletion_prevention_tax&term=' . $post->ID ),
					admin_url( 'edit.php?taxonomy=deletion_prevention_tax&term=' . $post->ID )

If your query returns a single post or page, we know that the item is still used. In this case we wp_die to stop the current request which will prevent the deletion of the item. In the message we will also place two links to the posts and pages list, filtered by the embedded item, so we can quickly find out where they are still used, so we can remove them and then safely delete them.


Accidentally deleting a post type item that is still used in other places can happen quite easily and there is currently nothing in core to prevent something like this. Using this approach with a helper taxonomy introduces a simply way to prevent such accidents. The same approach could also be used to prevent setting an embedded item to an non-public status. Something similar would also work preventing the deletion of attachments (although they are also a post type, the same hooks will not work). I hope that this final blog post of 2020 will also help you in some projects. The code from this blog post is available as a plugin in a GIST, so you can try it out yourself or modify it for your needs.

Extract strings from Gutenberg blocks for translations

While developing my first more complex blocks I ran into different issues. One of them was extracting the strings for my translations. In my previous blog post I showed you how to get those strings for PHP files. Those methods also work for the JavaScript files as the use “the same functions”, or at least aliases that have the same name.

Translate strings in a Gutenberg block

Let’s start to look at an example of a string translation in a block. This example comes straight from the Create a Block Tutorial and shows an “Edit” component:

import { TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
export default function Edit( { attributes, className, setAttributes } ) {
    return (
        <div className={ className }>
                label={ __( 'Message', 'gutenpride' ) }
                value={ attributes.message }
                onChange={ ( val ) => setAttributes( { message: val } ) }

The __ function is imported from the @wordpress/i18n component. This code works perfectly fine for translating the string in line 8. It only fails when you try to extract this string into PO file.

Compiled JavaScript file

But why does this example not work when trying to extract the strings? When you work with components like this you usually split up your JavaScript code into multiple files. You then import all those files into a main JavaScript file and then you build the JavaScript file that is used in the browser with something like @wordpress/scripts like explained in the JavaScript Build Setup chapter. This will then create a file build/index.js and the string from the example above will look like this:

// ...
/* harmony import */ var _wordpress_i18n__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @wordpress/i18n */ "@wordpress/i18n");
// ...
function Edit(_ref) {
  var attributes = _ref.attributes,
      className = _ref.className,
      setAttributes = _ref.setAttributes;
  return Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])("div", {
    className: className
  }, Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])(_wordpress_components__WEBPACK_IMPORTED_MODULE_1__["TextControl"], {
    label: Object(_wordpress_i18n__WEBPACK_IMPORTED_MODULE_2__["__"])('Message', 'block-i18n'),
    value: attributes.message,
    onChange: function onChange(val) {
      return setAttributes({
        message: val

In line 11 we will find the string again. But it’s not wrapped in a plain __ function. Instead the complied JavaScript files uses some weird function names from the components imported by webpack. As this function/syntax is not known to the wp i18n make-pot command or the X-Poedit-KeywordsList that Poedit is using, the string will not be found when parsing the files.

Solution: Use a different syntax to import components

There is an easy fix for that. You can import components in different ways. Simply replace the import of the i18n functions in line 2 with this “import”:

const { __ } = wp.i18n;

This will then result in the following complied JavaScript file which will use the plain __ function (in line 10) that is known to the parser:

// ...
var __ = wp.i18n.__;
function Edit(_ref) {
  var attributes = _ref.attributes,
      className = _ref.className,
      setAttributes = _ref.setAttributes;
  return Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])("div", {
    className: className
  }, Object(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__["createElement"])(_wordpress_components__WEBPACK_IMPORTED_MODULE_1__["TextControl"], {
    label: __('Message', 'block-i18n'),
    value: attributes.message,
    onChange: function onChange(val) {
      return setAttributes({
        message: val

Interestingly, when I removed the import without replacing it, it worked as well. That might be due to the fact that the __ function was already imported by another import and was therefor available. But I would recommend to always define which external components and functions you use in your own JavaScript files. In the same way you can also replace the first line like this:

const { TextControl } = wp.components;

Other tutorials always use this method to import components from WordPress/Gutenberg, so I would highly recommend to do it like this as well. I haven’t had an issue with them and the result complied JavaScript files were even a little smaller in size.


Translating strings in a Gutenberg block (or similar JavaScript code for WordPress) is super easy, as you can simply use the same functions you already know from PHP. But if you do the imports “wrong”, extracting the strings for Poedit does not work.

As I have seen some plugins who do it this way and still have working translations there might be a way to extract them anyways. If you know how they do it, I would highly appreciate if you can leave a comment explaining us, how it works.

Translate a plugin or theme without additional software or plugins

If you want to translate a plugin or theme, I would always recommend to use the free Poedit software which is available on Windows, Linux and Mac. Even though the Pro version has some special WordPress specific feature, you don’t necessarily need it. But I can also highly recommend it. The one-time-payment is quite a good deal and it will save you a lot of time, especially translating themes, which have many similar strings.

But this blog post is not about how to use Poedit to translate plugins and themes. I want to show you how you can translate them using just just commands on a terminal. So even if you can’t translate using additional software (or plugins), it’s possible.

Create a translation template

The first step is creating a POT (Portable Object Template) file with all the strings your plugin/theme is using. When I use Poedit, I usually skip this step and use a language specific template that will allow Poedit to scan for all translatable strings. The easiest way is using a WP-CLI command to create such a file:

wp i18n make-pot . languages/text-domain.pot

When you execute this command, it will scan all PHP and JavaScript files for string with the text domain equal to the plugin/theme folder name and create a POT file in the languages folder (make sure this folder exists). The command knows all the different functions that WordPress is using for translation (the same functions I have in my template above).

Create a locale specific translation

Next you create a translation file for a specific language by copying the POT file into a PO (Portable Object) file. This file has to be prepended with the locale:

cp languages/text-domain.pot languages/text-domain-de_DE.po

You can now open up this file in a text editor and translate the strings manually. In the header of the file you can also define the language but it’s technically not necessary.

Generate the binary translation file

WordPress is using MO (Machine Object) files to load translations. There is also a command to do this task which is available on many operating systems (or included in git bash, cygwin or similar on Windows):

msgfmt languages/text-domain-de_DE.po -o languages/

This is all you need to generate the translations. You can now check them on your WordPress installation (make sure you used the corresponding load_textdomain function to load translations for your text domain in your plugin/theme).

Bonus: Updating translations

Any time you add more strings or you change some of them, you have to update your translation files. When you use software like Poedit (and you have properly configured it), you can simply scan for those new string. When you just use the terminal, you can also achieve this. Just run the command again:

wp i18n make-pot . languages/text-domain.pot

But this will simply just overwrite the template file with a new version. As POT files do not contain any translated string (as the are not for a specific language), that not an issue. But how do you update your localized translation file, without translating all strings again? For this, you can use the merge argument:

wp i18n make-pot . languages/text-domain-de_DE.po --merge=languages/text-domain-de_DE.po

This will scan all PHP and JavaScript files from the current directory, merge in the current translation file and write everything back into this file. Only those strings that are not already in that file will be added. The already translated strings will be left as they are. The only downside: this will not remove any strings that are no longer in your PHP or JavaScript files.


When you really want to easily translate plugins or themes, use Poedit or a similar solution. But if you can’t use them, now you should be able to translate them using some simple commands.

One area, where the wp i18n make-pot command might be very useful is in your CI/CD workflow. You setup a task wich runs the command making sure that the POT file is always up to date with your latest development.