Hide the download button for audio and video blocks

Last week I was asked for help on a website project. In this project, the website embeds some audio files into the page. This is a great feature of WordPress making it so easy to embed media files. But some people dislike, that on some browsers it’s do easy to download those files.

The “download” button in Chrome

In the Chrome browsers, it’s simpler than in any other browsers. By default, any audio and video tag would add a “download” button to the options (the three dots, next to the controls). In this particular project, the button should be removed, to make it at least a little harder to download a file and not “encouraging” anyone to do so.

Hiding the button using the “controlList” attribute

Chrome is the only browser with such a button (I know of) and forunately it also has an easy way to disable it: the controlList attribute. This attribute can have different/multiple values and one of them is the nodownload value.

You may now this, that you can simply add this attribute manually to the block using the “Edit as HTML” view. But as soon as you switch back to the “Edit visually” view, the block will be defect and you can only choose to either recover it (which removes the attribute) or convert it to HTML.

Adding for a feature request in Gutenberg

As this issue was probably not new, I searched for existing tickets and found two. The first one was asking to add a toggle to show the download button. This one referred to a second ticket about the general idea of disabling the download button, also by introducing a toggle.

In this second ticket some good arguments were made against such a toggle. As only Chrome has this button, such a toggle would only “work” in Chrome. And it would not even really work. Because even the attribute will not prevent downloads.

If a audio or video file is embedded with an audio or video HTML tag using a file from the media library, it can be downloaded. It will actually be “downloaded” automatically, once the media is played.

Silently removing the button with a plugin

So even though you cannot prevent the download, you might still want to hide the button in this case you can filter the block rendering and add the attribute there. In the simplest form, it will look like this:

function hide_download_buttons_on_embeds_render_block( $block_content, $block ) {
	if ( 'core/audio' === $block['blockName'] ) {
		$block_content = str_replace(
			'<audio ',
			'<audio controlsList="nodownload" ',
			$block_content
		);
	}
	if ( 'core/video' === $block['blockName'] ) {
		$block_content = str_replace(
			'<video ',
			'<video controlsList="nodownload" ',
			$block_content
		);
	}

	return $block_content;
}
add_filter( 'render_block', 'hide_download_buttons_on_embeds_render_block', 10, 2 );

You might have to write a bit more code, if the HTML tags in your WordPress installation already uses some other attribute values.

Conclusion

While it’s not possible to prevent downloads of audio and video files from the media library in such a way, it might still be something you want to add to your page.

If you do want to allow downloads – let’s say for episodes of a podcast – it’s much better to actively add a download button using the “File” block. This will not only present a consistent button in browser, it will also make it a lot easier, obvious and accessible to download the media file.

As always, the solution from this blog post can be found as a GIST where you can download the solution as a ZIP file and install it as a plugin.

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 WordPress.org 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.

Preparation

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() {
	register_taxonomy(
		'deletion_prevention_tax',
		array( 'post' ),
		array(
			'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 ) ) ) {
		return;
	}

	$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(
			'publish',
			'pending',
			'draft',
			'future',
			'private',
		),
		'tax_query'      => array(
			array(
				'taxonomy' => 'deletion_prevention_tax',
				'field'    => 'slug',
				'terms'    => (string) $post->ID,
			),
		),
		'posts_per_page' => 1,
		'fields'         => 'ids',

	);
	$posts = get_posts( $args );

	if ( ! empty( $posts ) ) {
		wp_die(
			wp_kses_post(
				sprintf(
					__(
						/* 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>.',
						'deletion-prevention'
					),
					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.

Conclusion

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 }>
            <TextControl
                label={ __( 'Message', 'gutenpride' ) }
                value={ attributes.message }
                onChange={ ( val ) => setAttributes( { message: val } ) }
            />
        </div>
    );
}

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.

Conclusion

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.