WordPress и домены-синонимы (алиасы)

{% import 'macros/index.njk' as macro with context %}

{{ macro.task ('сделать так, чтобы один сайт на WordPress с одной БД открывался по множеству разных доменов и не сойти с ума.') }}

Ситуация такая: есть сайт. Внутри — WordPress и свой мир. У сайта есть команда разработки. Для каждого разработчика придумано своё окружение и работы ведутся на dev-домене. dev0.*.tld, dev2.*.tld, ну и пока не закончатся разработчики или цифры.

Ещё есть обычные зеркала сайта — маркетинг придумал домен в другой доменной зоне, глобальные, региональные и какие угодно. И при этом всё это живёт на одной БД, ну, да. Не спрашивайте. Так бывает. Реальность достаточна сурова и объясняется контекстом (который до конца никому не понятен) или фразой типа: «так устроена жизнь».

И вот я совсем не понимаю, зачем в WordPress в базе данных в пугающе многих местах, захардкожен домен сайта. При варианте разработки «сделал локально → перенёс на удалённый сервер», человечество изобрело для этого костыля другие костыли или просто в лоб заменяют SQL-запросом все совпадения домена. Потрясающе.

Константы WP_SITEURL и WP_HOME

Решение (трюк?) с переопределением констант WP_SITEURL и WP_HOME работают не так, как нужно: они переопределяются, но не изменяются. И чаще всего вы получите при обращении к dev0.*.tld запрос ресурсов, статики и всего, с абсолютным урлом основного домена. На dev-окружениях такое и бесит и недопустимо.

// wp-config.php

define( 'WP_HOME',    'https://' . $_SERVER['HTTP_HOST'] );
define( 'WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST'] );

«Умная переопределялка» для хоста через $_SERVER['HTTP_HOST'] будет вести себя точно также из-за абсолютных ссылок в БД.

/**
 * Вторая попытка определения хоста для дев- и прод-доменов
 */
switch ( strtolower( $_SERVER['HTTP_HOST'] ) )
{
  case 'dev.7site.tld':
    define('WP_SITEURL', 'https://dev.7site.tld');
    define('WP_HOME',    'https://dev.7site.tld');
    break;
    
  case '7site.tld':
    define('WP_SITEURL', 'https://7site.tld');
    define('WP_HOME',    'https://7site.tld');
    break;

  default:
    define('WP_SITEURL', 'https://site.tld');
    define('WP_HOME',    'https://site.tld');
    break;
}

Разумеется, при этом менять что-то в самой БД — нельзя. При этом хочется. А нельзя.

Кажется, логичным: заменять то, что нужно и там, где надо (дорогостоящая операция по производительности). Так уже работает, например, для медиафайлов на dev-доменах: медиа загружены на основной домен, менеджер при тестировании смотрит окружение разработчика, но урл для картинки будет заменён на урл боевого домена:

<img
  class="gallery__img"
  alt="<?= $item['alt']; ?>"
  draggable="false"
  src="<?= str_replace( 'dev1.', '', $item['src'] ); ?>">

Что же делать

Выкинуть из кода все переопределения констант.

Создать файл wp-domains.php в корне. Внутри описать магию с ob_start + str_replace.

<?php
/**
 * https://github.com/MihanEntalpo/WordpressGitTools/blob/master/wp-domains.php
 */

//Проверим, не запускался ли данный скрипт ранее:
if (defined("WP_DOMAINS_INITIALIZED")) return;
const WP_DOMAINS_INITIALIZED = 1;

//Домены на котором может работать сайт, локальные и удалённые,
$domains_enabled = array(
  "site.tld",
  "7site.tld",
  "dev.7site.tld"
);


//Добавим в массив $_SERVER все необходимые для проверки ключи,
//которых там может не быть
$_SERVER = array_replace(
  array(
    "HTTPS" => "", "HTTP_HTTPS" => "", "REQUEST_SCHEME" => "", "SERVER_PORT" => ""
  ),
  $_SERVER
);

//Проверим, используется ли https?
$https =
  (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
    || (!empty($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] != 'off')
    || $_SERVER['REQUEST_SCHEME'] == 'https'
    || $_SERVER['SERVER_PORT'] == 443;


//Составим адрес хоста
$site_addr = ($https ? "https://" : "http://") . $_SERVER['HTTP_HOST'] . "/";

//Заменим адрес хоста на тот, что мы вычислили
if (!defined("WP_HOME")) define("WP_HOME", $site_addr);
if (!defined("WP_SITEURL")) define("WP_SITEURL", $site_addr);


//Добавим обработчик выдачи сайта, который будет заменять во всех ссылках
//домены на текущий домен
ob_start(function ($data) use ($domains_enabled, $https)
{
  $current_host = $_SERVER['HTTP_HOST'];
  $replace = array();
  $scheme = $https ? "https" : "http";
  foreach ($domains_enabled as $domain) {
    if ($current_host == $domain) continue;
    $replace["http://$domain/"] = "$scheme://$current_host/";
    $replace["https://$domain"] = "$scheme://$current_host";
    $replace["//$domain/"] = "//$current_host/";
    $replace["//$domain"] = "//$current_host";
  }
  return str_replace(array_keys($replace), array_values($replace), $data);
});

Подключить файл wp-domains.php в файлы wp-config.php и wp-load.php.

require_once __DIR__ . '/wp-domains.php';

Проверено на хостинге timeweb.ru, четырёх доменах-синонимов и трёх поддоменах.