WP BOX is bundled with a Docker Compose pipeline, which starts up 8 containers with everything a website might need. You can deploy WP BOX on a remote server, or on your local personal computer without extensive knowledge of server software. You can create a server from your own computer!
Before the installation you can pick a MariaDB or PostgreSQL docker compose template to build the containers and start website.
services:
php-fpm-custom:
depends_on:
- db
user: ${USER_ID}:${GROUP_ID}
image: php-fpm-custom:latest
container_name: php-fpm-custom
links:
- nginx-custom:nginx-custom
- postfix-custom:postfix-custom
build:
context: docker/php-fpm/
args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
POSTFIX_DOMAIN: ${POSTFIX_DOMAIN}
ENABLE_XDEBUG: ${ENABLE_XDEBUG}
restart: always
ports:
- 9000:9000
volumes:
- .:/var/www/site
networks:
- wpbox-network
PHP service processes WordPress PHP scripts. It builds with enabled extensions and libraries recommended for WordPress and also required for smooth performance.
NGINX is a webserver that routes requests to WordPress and static files. Built with variables set during build stage to create configuration files, so you won’t need to edit them manually.
Additional variables will enable SSL certificate and access to database manager software – phpmyadmin or adminer.
services:
nginx-custom:
image: nginx-custom:latest
container_name: nginx-custom
build:
context: docker/nginx/
args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
SITE_DOMAIN: ${SITE_DOMAIN}
DB_HOST: ${DB_HOST}
DB_ADMIN_WEBPATH: ${DB_ADMIN_WEBPATH}
restart: always
volumes:
- .:/var/www/site
- ./certbot/conf/:/etc/letsencrypt/:ro
- ./certbot/www/:/var/www/letsencrypt/:ro
extra_hosts:
- "host.docker.internal:host-gateway"
expose:
- '80'
- '443'
ports:
- 80:80
- 443:443
networks:
- wpbox-network
services:
postfix-custom:
image: postfix-custom:latest
container_name: postfix-custom
build:
context: docker/postfix/
environment:
- DOCKER_SUBNET=${DOCKER_SUBNET}
- POSTFIX_DOMAIN=${POSTFIX_DOMAIN}
- MY_DESTINATION=${MY_DESTINATION}
restart: always
volumes:
- ./postfix:/var/spool/postfix
expose:
- '25'
- '465'
- '587'
networks:
- wpbox-network
Mail Server that works manages emails. It has many features, such as send queues and is widely used overall. WP BOX provides a configuration to integrate postfix with SendGrid (they have a free tier account).
Send emails from your website during development and in production and not worry about lost emails, as whenever it cannot send an email it is still stored in filesystem, until processed.
This is a really handy service, that manages creation of SSL certificates.
If WP BOX is deployed on a server and is linked with a domain name it will generate a Lets Encrypt certificate and automatically renew it periodically before expiration. Otherwise a self-signed certificate will be used.
It will also restart nginx container to apply certificate changes.
services:
certbot-custom:
container_name: certbot-custom
image: certbot-custom:latest
build:
context: docker/certbot/
args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
restart: always
environment:
- USER_ID=${USER_ID}
- GROUP_ID=${GROUP_ID}
- FIRST_USER_EMAIL=${FIRST_USER_EMAIL}
- SITE_DOMAIN=${SITE_DOMAIN}
- CERT_MODE=${CERT_MODE}
- HOOK=docker restart nginx-custom
volumes:
- ./certbot/www/:/var/www/letsencrypt/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
- /var/run/docker.sock:/var/run/docker.sock
services:
node-custom:
container_name: node-custom
image: node-custom:latest
tty: true
stdin_open: true
volumes:
- .:/var/www/site
build:
context: docker/node/
args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
restart: always
command: "tail -f /dev/null"
networks:
- wpbox-network
These are mostly based on official images, without notable modifications.
WP BOX includes many useful classes to interact with core WordPress functionality, following WordPress architecture and doing it the “native” way. You can use them as is, learn from them or extend them to your needs. The main idea is to have a custom class for most core essences, such as posts, taxonomies, additional classes for complex business logic that should provide consistent results and classes for interacting with third-party services.
Some examples follow:
Uploads class makes sets up your default image sizes and makes sure to save disk space – it deletes original large images and modifies database to have your maximum scaled size as the original.
Pretty useful, huh?
class Uploads
{
public function __construct()
{
add_action('after_setup_theme', [ 'WPBOX\Uploads', 'thumbnails_supports' ]);
add_action('after_setup_theme', [ 'WPBOX\Uploads', 'media_uploads_setup' ]);
add_filter('wp_generate_attachment_metadata', [ 'WPBOX\Uploads', 'delete_fullsize_image' ]);
}
class Users
{
public function __construct()
{
add_action('rest_api_init', [ $this, 'rest_hooks' ]);
add_action('init', [ $this, 'ajax_hooks' ]);
add_action('generate_rewrite_rules', [ $this, 'rewrite_rules' ], 999);
}
public function rewrite_rules( \WP_Rewrite $wp_rewrite )
{
$rules[ 'authors/?$' ] = 'index.php?pagename=authors';
$rules[ 'authors/page/([0-9]+)?/?$' ] = 'index.php?pagename=authors&paged=$matches[1]';
$rules[ 'authors/([^/]*)/?$' ] = 'index.php?author_name=$matches[1]';
$wp_rewrite->rules = ( $rules + $wp_rewrite->rules );
}
public function rest_hooks()
{
register_rest_route(
'usermeta',
'/validate',
[
'permission_callback' => '__return_true',
'methods' => 'GET',
'callback' => [
$this,
'validate_confirmation_key',
],
]
);
This awesome class is a must if you plan on building some user dashboard and generally allow users to perform various actions when they are logged in.
It already has methods for email confirmation, routing modifications, ajax logins from custom forms, social logins with third-party systems such as Google or Facebook account.
This is my favourite, because no competitors offer similar functionality. What it does is allows you to generate <picture>
html, with multiple sources in .webp
and original filetype, with each filetype having multiple sizes for different screen sizes with a single call.
As simple as: PictureGenerator::the_picture_html($image_id, $class_names, $max_width, $max_height, $caption, $object_fit);
It is well used in some core Gutenberg blocks via override and in some new blocks shipped with WP BOX. .webp file generation is dependent on WebpExpress plugin, which is installed via composer during installation pipeline.
<figure>
<picture>
<source srcset="https://example.com/app/uploads/2024/05/a-600x600.png.webp 600w"
type="image/webp" media="(max-width: 600px)" sizes="600px">
<source srcset="https://example.com/app/uploads/2024/05/a-600x600.png 600w"
type="image/png" media="(max-width: 600px)" sizes="600px">
<source srcset="https://example.com/app/uploads/2024/05/a-992x992.png.webp 992w"
type="image/webp" media="(max-width: 992px)" sizes="992px">
<source srcset="https://example.com/app/uploads/2024/05/a-992x992.png 992w"
type="image/png" media="(max-width: 992px)" sizes="992px">
<source srcset="https://example.com/app/uploads/2024/05/a.png.webp 1024w"
type="image/webp" media="(max-width: 1024px)" sizes="1024px">
<source srcset="https://example.com/app/uploads/2024/05/a.png 1024w" type="image/png"
media="(max-width: 1024px)" sizes="1024px">
<source srcset="https://example.com/app/uploads/2024/05/a.png.webp 1024w"
type="image/webp" sizes="1024px">
<source srcset="https://example.com/app/uploads/2024/05/a.png 1024w" type="image/png"
sizes="1024px"><img decoding="async" class="text-wrap p-4 html lazy rounded-md h-full w-full object-cover" loading="lazy"
fetchpriority="low" src="https://example.com/app/uploads/2024/05/a.png"
srcset="https://example.com/app/uploads/2024/05/a-600x600.png 600w, https://example.com/app/uploads/2024/05/a-992x992.png 992w, https://example.com/app/uploads/2024/05/a.png 1024w, https://example.com/app/uploads/2024/05/a.png"
sizes="(max-width: 600px) 600px(max-width: 992px) 992px(max-width: 1024px) 1024px" alt=""
title="ComfyUI_temp_rysjp_00008_">
</picture>
</figure>
class ThemeStyles
{
public function __construct()
{
add_action('wp_enqueue_scripts', [ 'WPBOX\ThemeStyles', 'tailwind_styles' ]);
add_action('wp_enqueue_scripts', [ 'WPBOX\ThemeStyles', 'public_libs' ]);
add_action('wp_body_open', [ 'WPBOX\ThemeStyles', 'apply_theme_styles_from_localstorage' ]);
add_filter('safe_style_css', [ 'WPBOX\ThemeStyles', 'safe_style_css' ]);
add_filter('safecss_filter_attr_allow_css', [ 'WPBOX\ThemeStyles', 'safecss_filter_attr_allow_css' ], 10, 2);
add_action('wp_enqueue_scripts', [ 'WPBOX\ThemeStyles', 'deregister_styles' ], 100);
// Admin methods
add_action('admin_enqueue_scripts', [ 'WPBOX\ThemeStyles', 'tailwind_styles' ], 99999);
add_action('enqueue_block_editor_assets', [ 'WPBOX\ThemeStyles', 'tailwind_styles' ], 99999);
add_action('enqueue_block_assets', [ 'WPBOX\ThemeStyles', 'tailwind_styles' ], 99999);
}
A generic class to register actions related to styles. In case of WP BOX – it adds Tailwind CSS, removes default styles and applies some modifications to other methods.
Most importantly it is automatically registering styles in specific directories –/styles/dist/
for parent and child themes, and enqueuing some of the required ones globally.
Similar to ThemeStyles, this generic class does everything needed for registering and enqueuing scripts and libs.
It enqueues non-block related public and admin scripts globally and registers libs, so they are automatically enqueued when listed as dependency in Gutenberg blocks.
class ThemeScripts
{
public function __construct()
{
add_action('admin_enqueue_scripts', [ $this, 'admin_scripts' ], 1);
add_action('wp_enqueue_scripts', [ $this, 'public_libs' ], 1);
add_action('wp_enqueue_scripts', [ $this, 'public_scripts' ], 1);
}
class ThemeBlocks
{
public function __construct()
{
add_action('init', [ 'WPBOX\ThemeBlocks', 'theme_gutenberg_blocks' ]);
add_filter('should_load_remote_block_patterns', '__return_false'); //remove remote patterns from internet in editor
add_filter('block_categories_all', [ 'WPBOX\ThemeBlocks', 'register_block_categories' ]);
add_action('after_setup_theme', [ 'WPBOX\ThemeBlocks', 'blocks_supports' ]);
}
private static function register_blocks( string $directory_path, bool $child_theme = false )
{
$block_files = glob($directory_path . '**/*.block.js');
$theme_directory_uri = $child_theme ? get_stylesheet_directory_uri() : get_template_directory_uri();
$theme_directory_path = $child_theme ? get_stylesheet_directory() : get_template_directory();
if (! empty($block_files)) {
foreach ($block_files as $block_file) {
$block_json = dirname($block_file) . '/block.json';
$block_basename = basename($block_file, '.block.js');
$block_name = str_replace('_', '-', $block_basename);
$has_frontend_script = file_exists(dirname($block_file) . '/assets/js/script.min.js');
Notable class responsible for unified Gutenberg block management across the project. It has all the logic to scan the directories find block related files, register blocks, register their frontend scripts with dependencies listed in block folders and such.
There other similar classes that do the same for overridden blocks, block patterns and block variations – ThemeBlocksOverrides
, ThemeBlocksPatters
and ThemeBlocksVariations
, respectively.
This is a big achievement for WP BOX, it is yet a smaller alternative to premium plugins, however it features simple, fast and clean code that modifies queries naturally, allows simple posts and categories linking, and works with templates inside Site Editor, which was recently introduced when block-based themes became a thing.
Check out some of the classes in this mu-plugin:
class WPBOXTranslations
{
private static ?string $current_language = null;
public static string $default_language = 'en';
public static string $posts_table_name;
public static string $terms_table_name;
public static array $non_translatable_post_types = [
'attachment',
'revision',
'wp_template',
'purchase'
];
public static array $non_translatable_taxonomies = [
'wp_theme',
'wp_template_part_area',
'purchase-status'
];
public function __construct()
{
global $wpdb;
self::$posts_table_name = $wpdb->prefix . 'translations_post_relations';
self::$terms_table_name = $wpdb->prefix . 'translations_term_relations';
if(defined('WP_CLI') && WP_CLI) return;
if(get_theme_mod('wpbox_translations_v') != 1 ) {
add_action('after_setup_theme', [ 'WPBOXTranslations\DatabaseModifications', 'seed_translation_tables' ]);
}
new WPBOXTranslations\RestEndpoints();
new WPBOXTranslations\DatabaseModifications();
new WPBOXTranslations\AdminComponents();
new WPBOXTranslations\Routing();
new WPBOXTranslations\RTL();
add_action('after_setup_theme', [ $this, 'load_theme_strings' ]);
add_filter('pre_determine_locale', [ $this, 'set_wordpress_locale']);
}
Top level class, that is loaded first during initialization. It manages some initial setup and provides public methods as single entrypoint for calls outside of the mu-plugin.
Common public methods are:
Handles all rewrite rules modifications, that are required for multilingual functionality,
such as understanding links with /language/ prefix right after the domain.
It also modifies slugs generated by WordPress, to reflect those rewrite rules changes.
And features ability to show original post if no translation is found, while keeping the user on the translated version of the website.
class Routing
{
public function __construct()
{
add_action('generate_rewrite_rules', [ $this, 'rewrite_rules' ], 999); //translation priority should be higher than regular post types and terms rewrite rules priority
add_filter('author_rewrite_rules', [ $this, 'author_rewrite_rules' ], 999); //translation priority should be higher than regular author rewrite rules priority
add_filter('query_vars', [ $this, 'add_language_query_var' ], 1);
add_filter('wp_unique_post_slug', [ $this, 'change_post_slug' ], 999, 6);
add_filter('wp_unique_term_slug', [ $this, 'change_term_slug' ], 999, 3);
add_filter('wp_update_term_data', [ $this, 'apply_change_term_slug' ], 10, 4);
add_filter('term_link', [ $this, 'change_term_link' ], 999, 3);
add_filter('post_link', [ $this, 'change_post_link' ], 999, 2);
add_filter('post_type_link', [ $this, 'change_post_link' ], 999, 2);
add_filter('page_link', [ $this, 'change_page_link' ], 999, 2);
add_filter('redirect_canonical', [ $this, 'disable_some_canonical_redirects' ], 999, 1 );
add_action('template_redirect', [ $this, 'show_original_post_if_no_translation' ], 11); //run a little bit earlier
}
class RestEndpoints
{
public function __construct()
{
add_action('rest_api_init', [ $this, 'templates_data_routes' ]);
add_action('rest_api_init', [ $this, 'get_translation_meta_route' ]);
add_action('rest_api_init', [ $this, 'set_translation_meta_route' ]);
add_filter('rest_pre_dispatch', [ $this, 'set_l_in_rest' ], 1, 3);
}
public function set_l_in_rest( mixed $result, \WP_REST_Server $wp_rest_server, \WP_REST_Request $request )
{
This magic class hooks into REST API to provide multilingual features, this mostly comes handy when Gutenberg blocks need to access data from database, when for example you are building a block that allows user selection of a post.
It is also used in component made for Editor, which allows changing the language of current object (post, taxonomy, term, template).
Most important class here that modifies SQL queries through hooks. Adds join statements and where statements to select results from database which match current language.
It has a very simple concept, which covers everything that goes through WP_Query.
class DatabaseModifications
{
public function __construct()
{
add_action('init', [ $this, 'register_post_meta' ]);
add_action('init', [ $this, 'register_term_meta' ]);
add_action('pre_get_posts', [ $this, 'modify_post_query' ], 999);
add_action('pre_get_posts', [ $this, 'modify_navigation_query' ], 999);
add_action('pre_get_posts', [ $this, 'modify_templates_query' ], 999);
add_action('pre_get_terms', [ $this, 'modify_term_query' ], 999);
}
public static function modify_templates_query( $query )
{
if (isset($query->query['post_type']) && $query->query['post_type'] !== 'wp_template_part') {
return;
}
add_filter('posts_join', [ 'WPBOXTranslations\DatabaseModifications', 'add_join_to_posts_sql' ], 1);
add_filter('posts_where', [ 'WPBOXTranslations\DatabaseModifications', 'add_where_to_posts_sql' ], 2);
}
Save time, money and nerves by investing in awesome code base early and be prepared when your project needs it.
50% discount applies on purchases in December 2024/January 2025. Happy New Year!
Visit pricing details page