Синхронизация стилей сайта с theme.json
Поговорим как программно обновить раздел "styles" в theme.json взяв их из текущих настроект сайта. В (FSE теме) WordPress основные настройки дизайна хранятся в файле theme.json.
При этом часть этих параметров можно изменить через редактор сайта WordPress, через раздел «Стили».
Проблема в том, что изменения из редактора не записываются обратно в файл темы theme.json. WordPress сохраняет их в базе данных.
Из-за этого итоговый дизайн темы состоит из двух частей:
- базовые стили из
theme.json; - пользовательские изменения из базы данных.
Иногда требуется перенести настройки, сделанные через редактор, обратно в файл темы. Например, чтобы сохранить их в Git, перенести на другой сайт или сделать частью новой версии темы.
Что делает код
Класс получает пользовательские стили, объединяет их со стилями из текущего theme.json и записывает результат обратно в файл.
При этом заменяется только раздел styles.
Остальные части theme.json, например settings, остаются без изменений. Их исходное форматирование также сохраняется.
Код файла: Theme_Json_Styles_Updater.php
GitHub<?php
/**
* Updates the root `styles` section in the theme.json file.
*
* The class retrieves user styles saved through the Site Editor,
* merges them with the styles defined in theme.json, and replaces only
* the `styles` section. Formatting of the other sections remains unchanged.
*
* Usage:
*
* $updater = new Theme_Json_Styles_Updater(
* theme_json_file: get_stylesheet_directory() . '/theme.json',
* indent: ' '
* );
*
* $updater->update();
*
* Version: 1.0
*/
final class Theme_Json_Styles_Updater {
public function __construct(
private readonly string $theme_json_file,
private readonly string $indent = ' '
) {}
public function update(): void {
$theme_json = $this->read_theme_json();
$styles = $this->get_merged_styles( $theme_json );
$new_styles = $this->encode_styles( $styles );
$theme_json = $this->replace_root_object( $theme_json, 'styles', $new_styles );
$this->write_theme_json( $theme_json );
}
private function read_theme_json(): string {
$contents = file_get_contents( $this->theme_json_file );
if ( false === $contents ) {
throw new RuntimeException( 'Unable to read theme.json.' );
}
return $contents;
}
private function get_merged_styles( string $theme_json ): array {
$theme_data = json_decode(
$theme_json,
true,
512,
JSON_THROW_ON_ERROR
);
$user_data = WP_Theme_JSON_Resolver::get_user_data()->get_raw_data();
return array_replace_recursive(
$theme_data['styles'] ?? [],
$user_data['styles'] ?? []
);
}
private function encode_styles( array $styles ): string {
$json = wp_json_encode(
$styles,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
);
if ( ! $json ) {
throw new RuntimeException( 'Unable to encode styles.' );
}
$json = preg_replace_callback(
'/^( +)/m',
fn( $match ) => str_repeat( $this->indent, strlen( $match[1] ) / 4 ),
$json
);
return $this->indent_json( $json );
}
private function indent_json( string $json ): string {
return preg_replace( '/^/m', $this->indent, $json ) ?: $json;
}
private function replace_root_object( string $json, string $property, string $replacement ): string {
$start = $this->find_root_object_start( $json, $property );
$end = $this->find_object_end( $json, $start );
return substr_replace(
$json,
ltrim( $replacement ),
$start,
$end - $start
);
}
private function find_root_object_start( string $json, string $property ): int {
$property_position = strpos( $json, "\"$property\"" );
if ( false === $property_position ) {
throw new RuntimeException( "Property \"$property\" was not found." );
}
$object_start = strpos( $json, '{', $property_position );
if ( false === $object_start ) {
throw new RuntimeException( "Object \"$property\" was not found." );
}
return $object_start;
}
private function find_object_end( string $json, int $object_start ): int {
$depth = 0;
$in_string = false;
$escaped = false;
$length = strlen( $json );
for ( $position = $object_start; $position < $length; $position++ ) {
$character = $json[ $position ];
if ( $in_string ) {
if ( $escaped ) {
$escaped = false;
} elseif ( '\\' === $character ) {
$escaped = true;
} elseif ( '"' === $character ) {
$in_string = false;
}
continue;
}
if ( '"' === $character ) {
$in_string = true;
} elseif ( '{' === $character ) {
$depth++;
} elseif ( '}' === $character && 0 === --$depth ) {
return $position + 1;
}
}
throw new RuntimeException( 'Unable to find the end of the object.' );
}
private function write_theme_json( string $theme_json ): void {
if ( ! file_put_contents( $this->theme_json_file, $theme_json ) ) {
throw new RuntimeException( 'Unable to write theme.json.' );
}
}
}Код файла: usage.php
GitHub<?php $updater = new Theme_Json_Styles_Updater( theme_json_file: get_stylesheet_directory() . '/theme.json', indent: ' ' ); $updater->update();
Теперь запускаем код
Создаём объект класса, передаём путь к theme.json и вызываем метод update():
$updater = new Theme_Json_Styles_Updater( get_stylesheet_directory() . '/theme.json', ' ' ); $updater->update();
Второй аргумент задаёт отступ:
' '- два пробела;"\t"- табуляция.
Заметки
Как работает обновление
Сначала класс читает текущее содержимое файла theme.json.
Затем класс получает пользовательские настройки через:
WP_Theme_JSON_Resolver::get_user_data()
Этот метод возвращает данные, сохранённые WordPress после изменений в редакторе сайта.
Из полученных данных используется только раздел styles.
Стили из базы объединяются со стилями из файла. При совпадении значение из базы данных приоритетнее.
Например, в файле указано:
{
"styles": {
"color": {
"text": "#111111",
"background": "#ffffff"
}
}
}
А через редактор сайта пользователь изменил только цвет текста:
{
"styles": {
"color": {
"text": "#333333"
}
}
}
После объединения получится:
{
"styles": {
"color": {
"text": "#333333",
"background": "#ffffff"
}
}
}
Почему не перезаписывается весь theme.json
Самый простой вариант выглядел бы так:
- Прочитать весь файл.
- Преобразовать его через
json_decode(). - Изменить раздел
styles. - Сохранить весь файл через
json_encode().
Но в этом случае PHP полностью изменит форматирование файла.
Например, компактная запись:
"margin": { "top": "20px", "bottom": "20px" }
может превратиться в:
"margin": {
"top": "20px",
"bottom": "20px"
}
Также могут измениться отступы и расположение переносов строк во всём файле.
Поэтому класс находит начало и конец только корневого объекта styles, а затем заменяет этот фрагмент в исходной строке.
Так форматирование остальных разделов остаётся нетронутым.
Важный момент
После переноса настроек пользовательские стили останутся в базе данных.
Это значит, что WordPress продолжит применять их поверх theme.json. Обычно результат останется тем же, потому что в файл были записаны такие же значения.
Но если затем вручную изменить эти параметры в theme.json, сохранённые настройки из базы могут продолжить их перекрывать.
Поэтому после успешного переноса может потребоваться сбросить пользовательские стили в редакторе сайта или удалить соответствующую запись wp_global_styles.