I recently faced the problem that I had to extend the Wordpress Editor (also known as Wordpress TinyMCE) with a lot of different custom buttons for different modules. As each module has its own JavaScript code that handles the button functionality, you can’t just take one skeleton file and exchange the function names. So you may go with adding the JavaScript file for each module manually - or do this programmatically.
How to “batch add” Custom Buttons to the Wordpress Editor
To add a custom button to the Wordpress Editor you have to add three different filters to your functions.php
file. The first one, admin_head
, checks if current user is allowed to edit post or pages and if the rich text editor (TinyMCE) is activated. The following two, mce_external_plugins
and mce_buttons
are needed to load the JavaScript file and register the new button within the editor interface. Pretty much for one simple button, huh? So I wrote this loop that is capable of adding 10, 50 or even 100 custom buttons to the editor.
<?php
/**
* Module loader for custom buttons that need advanced TinyMCE support
*/
function kvh_tinymce_module_loader()
{
$tinymce_modules = [
'youtube_player' => 'youtube-player',
'review' => 'review',
'usercard' => 'usercard',
'cta' => 'cta',
];
$i = 10;
foreach ($tinymce_modules as $module => $directory) {
$module = 'kvh_' . $module . '_btn';
// Hooks your functions into the correct filters
add_action('admin_head', function () use ($module, $directory, $i) {
// Check user permissions
if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) {
return;
}
// Check if WYSIWYG is enabled
if ('true' == get_user_option('rich_editing')) {
// Load the JS file that handles the button
add_filter('mce_external_plugins', function ($plugin_array) use ($module, $directory) {
$plugin_array[$module] = get_template_directory_uri() . "/modules/$directory/_editor.js";
return $plugin_array;
}, $i);
// Add the button to the editor UI
add_filter('mce_buttons', function ($buttons) use ($module) {
array_push($buttons, $module);
return $buttons;
}, $i);
}
});
// Load the shortcode handler
require(get_template_directory() . "/modules/$directory/_shortcode.php");
// Unset the variables
unset($module, $file);
$i++;
}
}
kvh_tinymce_module_loader();
What is the code doing?
- First, all modules are defined in the
$tinymce_modules
array. PHP then loops trough this array. $module
gets the predefined identifier for the button that consists of the prefix kvh_, the module name and the suffix _btn. This just makes sure the custom button gets a unique identifier.- Then, the admin_head filter is called which is able to inject the new button inside the admin header.
- Three checks are performed to check if the module needs to be loaded. If the user can’t edit posts and the richtext editor (aka TinyMCE) is not loaded, the module doesn’t need to be loaded.
- After that the two register filters are called:
mce_external_plugins
andmce_buttons
mce_external_plugins
dynamically loads the corresponding JavaScript file based of the passed directory name.mce_buttons
dynamically registers the new button based on the unique identifier we created above.
The main difference between the regular approach of using filters and this one is, that we are using PHP closures and anonymous functions here. Normally you would do something like this:
function kvh_register_ytplugin_js($plugin_array)
{
$plugin_array['embed-yt'] = get_template_directory_uri() . '/modules/youtube/_editor.js';
return $plugin_array;
}
As this approach would bloat our functions file with hundreds of different functions, each named after the plugin that needs to be loaded, we use this:
add_filter('mce_external_plugins', function ($plugin_array) use ($module, $directory)
{
$plugin_array[$module] = get_template_directory_uri() . "/modules/$directory/_editor.js";
return $plugin_array;
}, $i);
where we only pass the module name ($module
) and the corresponding directory ($directory
) to the function.
Additional notes
- Make sure that you set the correct folder structure. You may also load the plugin JavaScript file by doing something like this:
get_template_directory_uri() . "/editorplugins/$module.js";
- The
$i
variable is used to prevent that the functions overwrite the TinyMCE buttons array.$i
increases the priority of themce_buttons
filter with each module imported. If you want to alter the buttons earlier or later make sure to choose a priority that is below or above the last$i
value.
The full code is also available on Github.