Backend Features
Banner

Backend Features

In this section:

Containerized WordPress

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-FPM service

PHP service processes WordPress PHP scripts. It builds with enabled extensions and libraries recommended for WordPress and also required for smooth performance.

  • GD and Imagick
  • MSMTP
  • Relay (for redis)
  • Composer
  • WP CLI
  • XDebug

NGINX service

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

POSTFIX service

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.

CERTBOT service

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

Other services

These are mostly based on official images, without notable modifications.

  • db / pgdb:
    database service, depending on the choosen compose template.
  • redis:
    object caching service to reduce load on database service
  • phpmyadmin / adminer:
    optional service for visual database management
  • node-custom:
    service used for frontend development, all compilers run with it
In this section:

Backend Classes

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

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',
				],
			]
		);

Users

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.

PictureGenerator

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);
	}

ThemeStyles

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.

ThemeScripts

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');

ThemeBlocks

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.

In this section:

Multilingual Support

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']);
	}

WPBOXTranslations

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:

  • get_current_language(): string
  • set_current_language( string $language = null ): string
  • get_supported_languages(): array
  • get_translated_post_id( int $post_id, string $language ): int
  • get_translated_term_id( int $term_id, string $language ): int

Routing

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 )
	{

RestEndpoints

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).

DatabaseModifications

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);
	}

Build technically perfect websites today

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