Привет, Habr! Меня зовут Ольга Глеклер. Я уже более 12 лет в веб-разработке. Начинала писать с нуля, работала с различными CMS, последние 8 лет преимущественно с WordPress и уже около 6 лет являюсь контрибьютором. Работала в таких компаниях, как Epam и Yadro. Выступала на конференции HighLoad++. Сегодня расскажу о возможностях WordPress и как это реализовано «под капотом».

Легкость на старте
Для удобства пользователя и разработчика предусмотрены:
5-минутная установка;
автоматическое обновление движка, темы, плагинов и переводов;
режим восстановления после фатальных ошибок (почта должна работать);
переключение на дефолтную тему в случае ошибок в активной теме (при заходе на страницу тем в админке).
Стабильная система обновления
Уже через неделю после релиза версии 6.7 более 40% сайтов успели обновиться. Ещё 26% остались на 6.6 — осторожные пользователи предпочитают обновляться после исправления возможных регрессий. И они могут не спешить — обновления безопасности не откладываются до мажорной версии, а выпускаются в минорных релизах.
Готовые темы для фронта и плагины для расширения функционала
Система «подкладывает соломки» везде, где возможно, поэтому работа с темами и плагинами максимально облегчена:
запуск плагинов в «песочнице» при активации с предотвращением активации в случае ошибок;
проверка совместимости тем и плагинов с PHP и версией WordPress перед установкой (на основе заголовка темы/плагина);
проверка наличия зависимых плагинов (также на основе заголовков).
Компоненты как API
У WP более 60 компонентов, большая часть из которых является API. Например:
WP-Cron API — запуск заданий по расписанию;
Heartbeat API — нотификация о редактировании другим пользователем с возможностью перехвата;
SiteHealth API — встроенный плагин «Здоровье сайта» с информацией о системе и рекомендациями.
Простота «под капотом»
Видимая простота обеспечивается хорошо продуманным функционалом.
Например, если опустить детали, процесс обновления совершает следующие шаги:
автоматический или ручной запуск;
проверка настроек и прав;
закрытие сайта в режим обслуживания;
защита от повторного запуска обновления флагом;
загрузка в память всех классов, которые нужны для работы процесса обновления;
загрузка пакета из репозитория, проверка чексуммы, распаковка;
проверка возможности записи в место назначения;
проверка совместимости с PHP, обязательных расширений и MySQL;
проверка чексуммы для каждого файла;
рекурсивное копирование, пропуская wp-content и файл с версией;
копирование файла с версией;
снятие режима обслуживания;
сброс объектного кеша, ревалидация OPcache;
откат в случае возникновения в процессе ошибки (на любом из пунктов);
снятие флага обновления;
обновление базы данных при подтверждении администратором.
WP_Cron — API, позволяющее выполнять задания по расписанию. Оно имеет три варианта запуска (обращения к wp-cron.php):
По умолчанию: POST-запросом;
Редиректом на фронтенде:
ALTERNATE_WP_CRON;
Серверным кроном
DISABLE_WP_CRON
function spawn_cron( $gmt_time = 0 ) {
...
// Проверка флага выполнения крон и временного интервала
...
$result = wp_remote_post( $cron_request['url'], ... );
...
}
// wp-cron.php
...
// Don't run cron until the request finishes, if possible.
if ( function_exists( 'fastcgi_finish_request' ) ) {
fastcgi_finish_request();
} elseif ( function_exists( 'litespeed_finish_request' ) ) {
litespeed_finish_request();
}
Из кода выше видно, что асинхронность POST–запроса будет обеспечена только если PHP работает в режиме FastCGI или на сервере используется LiteSpeed Web Server. В остальных случаях ответ на запрос не вернётся, пока задания по расписанию не будут выполнены. Для таких вариантов, а также для случаев, когда из-за особенностей конфигурации данный вариант не срабатывает, реализован альтернативный вариант с редиректом на фронтенде.
function spawn_cron( $gmt_time = 0 ) {
...
if ( ... ALTERNATE_WP_CRON ) {
...
ob_start();
wp_redirect( add_query_arg( 'doing_wp_cron', ...) );
echo ' ';
// Flush any buffers and send the headers.
wp_ob_end_flush_all();
flush();
require_once ABSPATH . 'wp-cron.php';
...
}
Третий вариант — отключение псевдо-крона на стороне WordPress и вызов wp-cron.php средствами сервера.
Note: Проверка обновлений и другие функции ядра и плагинов привязаны к крон-вызовам. Вне зависимости от выбранного варианта запуск заданий по расписанию должен производиться. Проверить работоспособность данного функционала можно в разделе Инструменты → Здоровье сайта.
Инструменты разработчика
У WP много дополнительных сервисов и инструментов. Например, WP_CLI — официальный инструмент командной строки (должен быть установлен на сервере). У него есть ряд команд, ускоряющих рутинные операции: замена URL-ов при переносе сайта с одного домена на другой, создание «lorem ipsum»-постов для тестирования и другие. В плагин/тему можно добавлять собственные cli-команды.
Автоматизация деплоя
WordPress имеет официальный Docker-контейнер, но большинство разработчиков предпочитают установку через Composer.
{
"name": "my/sweet-site",
"type": "project",
"repositories": [
{
"type": "composer",
"url": "https://wpackagist.org"
}
],
"require": {
"johnpbloch/wordpress-core-installer": "*",
"johnpbloch/wordpress-core": "^6.7",
"wpackagist-plugin/wp-mail-smtp": "*"
},
"extra": {
"wordpress-install-dir": "wp",
"installer-paths": {
"wp-content/plugins/{$name}": [
"type:wordpress-plugin"
]
}
},
"config": {
"allow-plugins": {
"composer/installers": true,
"johnpbloch/wordpress-core-installer": true
}
}
}
После создания и корректировки composer.json остаётся выполнить несколько простых действий:
скопировать index.php в корень;
откорректировать в нем путь до wp-blog-header.php;
создать wp-config.php в корне на основе wp-config-sample.php;
определить константы
WP_CONTENT_DIR
иWP_CONTENT_URL
Так будет выглядеть root нашего проекта:
..
vendor/
wp/
wp-content/
composer.json
composer.lock
index.php
wp-config.php
Playground
Этот JS-инструмент позволяет запускать WordPress с предустановленными параметрами, плагинами и темой прямо в браузере. Это нужно для тестирования, быстрой разработки (результат работы можно выгрузить) или проверки гипотез. Например, Playground дал возможность проверять PR в GitHub прямо на месте без подтягивания ветки локально. Не все PR можно так протестировать, но там, где это возможно, этот инструмент упрощает и ускоряет разработку.
Совместимость
WP непритязателен к хостингу — он поддерживает PHP версий 7.2-8.3 и MySQL/MariaDB 5.5.5+. При этом рекомендуются версии PHP не ниже 7.4 и MySQL версии 8.0 или выше или MariaDB версии 10.5 или выше.
94.8% сайтов WordPress работают на версиях PHP 7.2-8.3. Решение продолжать поддерживать ту или иную версию PHP зависит от этой статистики. Информация собирается репозиторием планов/тем.
Note: В админке WordPress в разделе Инструменты → Здоровье сайта можно узнать о состоянии системы и возможных проблемах с совместимостью. Также есть специальные Unit-тесты, которые можно запустить на сервере отдельно: PHPUnit Test Runner.
Совместимость с различными версиями PHP обеспечивается функциями полифилами. WP не ставит задачу гарантировать 100% совместимость, поэтому реализует внутри ядра только те функции, которые будут полезны для работы самого ядра. Наличие функции-полифила в нём позволяет плагинам и темам также использовать эти функции, не заботясь о совместимости и не опасаясь, что она будет удалена. Файл с полифилами wp-includes/compat.php в версии 6.7 насчитывает 35 функций.
if ( ! function_exists( 'str_starts_with' ) ) {
/**
* Polyfill for ... function added in PHP 8.0.
*/
function str_starts_with( $haystack, $needle ) {
if ( '' === $needle ) {
return true;
}
return 0 === strpos( $haystack, $needle );
}
}
Обратная совместимость
WordPress содержит небольшое количество устаревших функций. Они не рекомендованы к использованию и при включённом показе/логировании ошибок выдают соответствующую информацию.
/**
* Clears the authentication cookie, logging the user out.
* This function is deprecated.
*
* @since 1.5.0
* @deprecated 2.5.0 Use wp_clear_auth_cookie()
* @see wp_clear_auth_cookie()
*/
function wp_clearcookie() {
_deprecated_function( __FUNCTION__, '2.5.0', 'wp_clear_auth_cookie()' );
wp_clear_auth_cookie();
}
Версия 2.5.0 появилась в марте 2008 года. Функции, признанные устаревшими в этом релизе, всё ещё не удалены из движка, но их немного и влияния на размер ядра они почти не оказывают.
В целом добавление новых функций и любого другого функционала в ядро проходит процесс валидации опытными контрибьюторами. Это сводит к минимуму вероятность попадания в ядро «сырых» и ненужных функций.
Стабильность внутреннего API
Названия основных функций, которыми оперирует WordPress, сформировались с самого начала. Их реализация за это время существенно изменилась, но параметры на входе и результат на выходе не претерпевают существенных изменений, что обеспечивает стабильность API.
function get_posts( $args = null ) {
...
$get_posts = new WP_Query();
return $get_posts->query( $parsed_args );
}
function get_post( ... ) {
...
if ( $post instanceof WP_Post ) {
$_post = $post;
}
...
return $_post;
}
Во избежание регрессий функционал покрывается Unit-тестами. Также в баг-фиксы, если это возможно, входят Unit-тесты во избежание возможной регрессии в будущем.
Гибкость
Гибкость обеспечивают:
еvent driven architecture;
подключаемые функции;
Drop-in-файлы;
базовый функционал для расширения.
Задача — отправить сообщение:
if ( ! function_exists( 'wp_mail' ) ) :
// ...
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
// ...
$pre_wp_mail = apply_filters( 'pre_wp_mail', null, $atts );
if ( null !== $pre_wp_mail ) {
return $pre_wp_mail;
}
// ...
}
endif;
В данном случае функция wp_mail подключается после загрузки плагинов и может быть полностью переопределена. Более того, отправка может быть осуществлена по фильтру pre_wp_mail без переопределения функции.
Функция wp_mail() выступает тем API, которое реализует отправку письма. При этом принципиален только факт отправки.
Note: плагин, который переопределяет функцию, может пренебречь хуком внутри функции. Это обратная сторона высокой кастомизируемости, поэтому сохранение API при переопределении, наличие своего API, следование лучшим практиками и стандартам — признаки зрелых решений, которые стоит принимать во внимание при выборе плагинов.
Порядок действий на событие и управление действиями
WordPress реализует Event driven architecture. В ядре и в хорошо проработанных темах и плагинах предусмотрены хуки, позволяющие влиять на данные в определённые моменты выполнения кода или результат на выходе, а также выполнять дополнительные действия в нужное время.
Есть два типа хуков: filter возвращает те же или модифицированные данные, а action вызывает событие, передает ему данные, но ничего не возвращает.
add_filter( 'heartbeat_received', 'heartbeat_autosave', 500, 2 );
add_filter( 'heartbeat_received', 'wp_check_locked_posts', 10, 3 );
add_filter( 'heartbeat_received', 'wp_refresh_post_lock', 10, 3 );
Хуки добавляются с порядковым номером. Фильтр с номером 10, добавленный первым, будет выполнен первым. Фильтр с номером 500 будет выполнен после всех фильтров с номерами меньше 500 и также всеми фильтрами с номером 500, добавленными ранее.
Многие плагины ставят своей задачей добавить свой хук последним, и если PHP_INT_MAX - 10 ещё оставляет пространство для манёвра, то единственный способ обойти фильтр, добавленный с номером PHP_INT_MAX — убедиться, что хук добавляется позднее с тем же номером.
// Пример из Yoast SEO плагина
public function register_hooks() {
/**
* Allow control of the `wp_robots` filter
* by prioritizing our hook 10 less than max...
*/
\add_filter( 'wp_robots', [ $this, 'add_robots' ], ( \PHP_INT_MAX - 10 ) );
}
Действия можно удалять, но обязательно с тем же номером, с которым они были добавлены.
add_action( 'wp_head', '_wp_render_title_tag', 1 );
remove_action( 'wp_head', '_wp_render_title_tag', 1 );
Одна и та же функция или метод может быть добавлен несколько раз с разными порядковыми номерами.
Удалять методы может быть проблематично — нужно получить объект, из которого он будет вызван.
// Из плагина Elementor
if ( class_exists( 'WPSEO_Frontend' ) ) {
remove_action( 'template_redirect',
[
\WPSEO_Frontend::get_instance(),
'clean_permalink'
],
1
);
}
Иногда фильтр или действие должны быть выполнены только один раз в конкретном месте. Тогда хук вешается перед выполнением действия и убирается после.
// Добавление фильтра с последующим удалением
add_filter( 'should_load_block_editor_scripts_and_styles', '__return_false' );
do_action( 'enqueue_block_assets' );
remove_filter( 'should_load_block_editor_scripts_and_styles', '__return_false' );
Или может возникнуть потребность удалить хук, а затем вернуть его обратно.
// Удаление фильтра с последующим возвратом
remove_filter( 'the_content', 'wpautop', $priority );
add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
Одна из трудностей, связанных с хуками — возможность вызвать бесконечный цикл. Поэтому нужно точно понимать взаимосвязь между событиями.
// Избегаем бесконечного цикла
add_action( 'save_post', 'my_function' );
function my_function( $post_id ) {
if ( ! wp_is_post_revision( $post_id ) ) {
remove_action( 'save_post', 'my_function' );
// вызывает событие save_post
wp_update_post( $my_args );
// снова вешаем хук
add_action( 'save_post', 'my_function' );
}
}
Наличие нескольких action на одно и тоже действие позволяет таргетировать события на конкретный тип данных. Также можно гарантировать порядок выполнения действий за счёт двух событий, которые жёстко следуют одно за другим без возможности смены порядка.
function wp_insert_post( ... ) {
global $wpdb;
$data = apply_filters( 'wp_insert_post_data', ... );
// ...
do_action( "save_post_{$post->post_type}", ... );
do_action( 'save_post', $post->ID, $post, true );
do_action( 'wp_insert_post', $post->ID, $post, true );
// ...
}
Drop-in файлы
В отличие от хуков, количество drop-in файлов ограничено. Предусматривается только один файл для реализации нужного функционала.
Подключение альтернативной базы данных
function require_wp_db() {
global $wpdb;
require_once ABSPATH . WPINC . '/class-wpdb.php';
if ( file_exists( WP_CONTENT_DIR . '/db.php' ) ) {
require_once WP_CONTENT_DIR . '/db.php';
}
if ( isset( $wpdb ) ) {
return;
}
// ...
$wpdb = new wpdb( $dbuser, $dbpassword, $dbname, $dbhost );
}
Класс для работы с базой данных подключается, но глобальный объект $wpdb не инициализируется до подключения drop-in файла db.php. Он может как реализовывать независимую имплементацию подключения к базе данных, так и наследоваться от ранее подключенного класса.
Если нескольким плагинам нужен один тот же drop-in файл, придётся написать «клей», который подружит эти два решения и реализует свой drop-in файл.
Постоянный объектный кеш
// wp-includes/load.php
// Если drop-in файл object-cache.php не был подключен ранее
if ( ! wp_using_ext_object_cache() ) {
require_once ABSPATH . WPINC . '/cache.php';
}
// wp-includes/cache.php
function wp_cache_init() {
$GLOBALS['wp_object_cache'] = new WP_Object_Cache();
}
function wp_cache_add(...) {...}
...
function wp_cache_reset() {...} // 31-я функция
Здесь файл движка, реализующий объектное кеширование, подключается только если drop-in файл object-cache.php не был подключён ранее. Альтернативный вариант должен реализовать все те же 31 функцию, которые представляют API для работы с объектным кешем. Это может быть Redis, Valkey или другая база ключ-значение для постоянного объектного кеширования.
Файловое кеширование
// wp-settings.php
if ( WP_CACHE
// Фильтр для отключения кеша при non-web запросах (WP_CLI)
&& apply_filters( 'enable_loading_advanced_cache_dropin', true )
&& file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ) ) {
include WP_CONTENT_DIR . '/advanced-cache.php';
...
}
Помимо самого drop-in файла должна быть определена константа WP_CACHE. Обычно кеширующие плагины устанавливают и убирают её в wp-config.php в соответствии с действиями пользователя в админке.
Роутинг
register_post_type(
'post',
array(
'labels' => array(),
'public' => true,
...
'rewrite' => false,
'query_var' => false,
...
'show_in_rest' => true,
'rest_base' => 'posts',
'rest_controller_class' => 'WP_REST_Posts_Controller',
)
);
По умолчанию в WordPress есть контентные записи post и page (attachment также можно считать контентом). Для добавления новых типов достаточно вызова одной функции с параметрами.
Note: в отличие от прав пользователей, которые сохраняются в базе данных и их установку можно вызвать единожды при инициализации плагина, регистрация типов происходит при каждом запросе.
После добавления новых типов следует сбросить пермалинки — это можно сделать вручную: Настройки → Постоянные ссылки. Другой вариант — сбросить пермалинки программно при инициализации или изменении версии плагина.
Многие разработчики предпочитают добавлять CPT в тему, так как обычно есть жёсткая зависимость внешнего вида от типа записи. Но формально CPT должны находиться в плагине, так как отвечают не за внешний вид, а за функциональность. При каждом изменении структуры URL-ов, пермалинки нужно сбрасывать. Если мы работаем с контентом и не меняем структуру URL-ов, то сбрасывать ничего не нужно.
Добавление переменных запроса
add_rewrite_tag( '%sitemap%', '([^?]+)' );
add_rewrite_rule( '^wp-sitemap\.xml$', 'index.php?sitemap=index', 'top' );
add_rewrite_rule(
'^wp-sitemap-([a-z]+?)-(\d+?)\.xml$',
'index.php?sitemap=$matches[1]&paged=$matches[2]',
'top'
);
// Далее новый параметр можно использовать
if ( get_query_var( 'sitemap' ) ) {
$sitemap = sanitize_text_field( get_query_var( 'sitemap' ) );
...
}
Когда нам нужны адреса, для которых не предусмотрено автоматического создания правил на основе регистрации как для кастомных типов постов или таксономий (архивов записей), можно добавить правила самостоятельно. Этот пример из ядра демонстрирует добавление sitemap — core использует API собственных компонентов.
Кастомные таблицы в базе данных
function get_my_schema() {
global $wpdb;
...
return "CREATE TABLE {$wpdb->prefix}my_custom_table ... ";
}
function create_my_tables() {
require_once ABSPATH . 'wp-admin/includes/upgrade.php'; // dbDelta.
dbDelta( get_my_schema() );
}
В WordPress предусмотрена работа с кастомными таблицами. Одни плагины создают сводные таблицы для оптимизации запросов, а другие хранят уникальные данные, требующие собственной структуры. Если в таблице происходит изменение, то достаточно вызова функции dbDelta()
для того, чтобы WordPress осуществил миграцию.
Запросы к базе данных «SELECT…», «UPDATE…», «INSERT…» и т.д. следует выполнять только к собственным таблицам с соответствующим API и не пренебрегая встроенной защитой от SQL-инъекций. Для всего остального в системе также реализовано API для обеспечения совместимости.
Базовый функционал
При включении того или иного функционала в ядро WordPress контрибьюторы отвечают на вопрос о том, нужно ли это 80%+ пользователей. Если да, новый функционал будет включён в ядро, если нет — его можно реализовать как поддерживаемый комьюнити плагин.
Многие компоненты начинались как плагины и вошли в ядро только после «обкатки». Проект Gutenberg (Блочный редактор) сейчас продолжает развиваться как плагин и в каждый релиз WordPress идёт его очередная стабильная версия.
При этом есть функционал, который можно реализовать только в ядре.
class wpdb {
...
private function _do_query( $query ) {
...
if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
$this->log_query(
$query,
$this->timer_stop(),
$this->get_caller(),
$this->time_start,
array()
);
}
}
...
}
Здесь класс работы с базой данных имеет базовый функционал для логирования запросов. При положительном значении константы SAVEQUERIES класс будет логировать запросы. Но он никак эту информацию не использует и обработка лога является прерогативой плагина профилирования. Если разрабатывать альтернативное подключение к базе данных, решение должно реализовывать тоже самое API, что и функционал ядра, в этом случае — возможность логирования запросов.
Потребности рынка
Перевод системы на другие языки
Система переводов предусматривает корректную обработку множественного числа для различных языков. WordPress проверяет наличие новых переводов для движка, тем и плагинов в репозитории вместе с проверкой других обновлений и загружает их.
Сейчас в системе переводов WordPress зарегистрировано 202 локали. Не все получают активное внимание контрибьюторов — бывает, что из-за низкого качества переводов пользователи предпочитают английский интерфейс. Но есть локали со 100% переводом движка и огромным количеством контрибьюторов, а также те, что созданы исключительно для развлечения.

Для разработчиков предусмотрена команда сбора строк для перевода плагина или темы средствами WP_CLI в .pot-файл. Из него создаются файлы переводов для конкретной локали — po (Portable Object).
> wp i18n make-pot
WordPress использует mo-файлы (Machine Object), получаемые из po, но этот подход не позволял использовать OPcache. С версии 6.5 система научилась работать с переводами в php, сохранив обратную совместимость и улучшив производительность.
$title = sprintf(
/* translators: 1: Comments count, 2: Post title. */
__( 'Comments (%1$s) on “%2$s”' ),
number_format_i18n( $comments_count->moderated ),
$draft_or_post_title
);
Переводы для JavaScript создаются js/json-файлах и используются аналогично PHP-функциями переводов.
import { __ } from '@wordpress/i18n';
...
return content?.protected ? (
<div { ...blockProps }>
<Warning>{ __( 'This content is password protected.' ) }</Warning>
</div>
) : (
...
);
С одним языком система прекрасно справляется. Когда же требуется создание многоязычных сайтов, это решается двумя способами (с помощью плагинов):
мультисайт, у которого каждый сайт отвечает за определённый язык. Это могут быть поддомены или подпапки;
создание записей, дублирующих основную, на том же самом сайте, но с дополнительным «флагом» другого языка. Это не оптимальное решение, т.к. если плагин мультиязычности перестанет работать, на сайте могут появиться посты на всех имеющихся языках одновременно.
Изначально сайт с одинаковым контентом на нескольких языках не был предусмотрен, поэтому реализация вызывает сложности. Эта задача — последняя из запланированных фаз проекта Гутенберг.
Мультисайт — тысячи сайтов на одном движке
Эту задачу поставили почти сразу. Мультисайт вошел в ядро в версии 3.0 в 2010 году. На данный момент крупнейшим является WordPress.com.
Для создания мультисайта достаточно прописать одну константу WP_ALLOW_MULTISITE
в конфиг и завершить настройку через админку, а движок уже осуществит миграцию.
В зависимости от настроек отдельные сайты могут располагаться на поддоменах или в подпапках. За маршрутизацию отвечает дополнительная таблица в базе данных.
После установки настроек:
таблица пользователей остается общей;
-
добавляется роль Super admin:
управляет правами пользователей для конкретных сайтов;
управляет плагинами, включёнными для всего мультисайта;
добавляется отдельная таблица для маршрутизации запросов к сайтам;
для каждого нового сайта создается свой набор таблиц;
при необходимости их можно перенести на другой сервер (требуется маршрутизация на уровне кода – наследование класса wpdb);
создается drop-in-файл sunrise.php.
Отделение движка от фронта и взаимодействие с другими системами
В версии 4.7 в конце 2016 года REST API, ранее доступный как плагин, вошёл в ядро.
WordPress — RESTful приложение. Это значит, что пользователю доступна вся схема данных и контент через REST. Используя авторизацию можно полностью управлять сайтом, включая создание пользователей. Для них доступны пароли приложений, создание которых также может быть автоматизировано.
Это даёт возможность полностью заменить админку или фронтенд и взаимодействовать с ядром исключительно через REST API.
При создании кастомных типов данных вопрос их добавления к схеме решается одной настройкой.
Note: если данные не видны в схеме для авторизованного пользователя, блочный редактор не сможет с ними работать.
add_action( 'rest_api_init', 'action_add_my_rest_endpoint' );
define( 'MY_NAMESPACE', 'my/v1' );
define( 'MY_ROUTE', 'data' );
function action_add_my_rest_endpoint() {
$route_params = [
'methods' => 'GET',
'callback' => 'rest_get_my_data',
'permission_callback' => 'my_rest_callack',
];
register_rest_route( MY_NAMESPACE, MY_ROUTE, $route_params );
}
Конструктор для редактора
Миссия WordPress — Democratize publishing и проект Гутенберг как раз отвечает этой цели.

Gutenberg — это новый взгляд на управление контентом. Всего в проекте запланировано 4 фазы, и половина уже реализована:
-
простое редактирование 2018-2021
блочный редактор
-
кастомизация 2021-2023
полносайтовое редактирование
паттерны блоков
репозиторий блоков
блочные темы
многоязычность 2025...

Проект Гутенберг отдаёт работу с контентом пользователю и благодаря блочному редактору, паттернам и обширной библиотеке его возможности становятся практически безграничными. На данный момент в репозитории более 1000 тем и более 2000 паттернов, доступных под GPL лицензией.
Для бизнеса, где нужно следовать определённым гайдам и дизайн-системе, есть тонкая настройка и возможность разработки собственных блоков.
Note: динамические блоки позволяют отделить данные от внешнего вида и легко вносить изменения в блоки в процессе развития проекта.
Безопасность
WordPress неизбежно притягивает к себе умы и руки пентестеров, поэтому безопасности уделяется особое и постоянное внимание.
В процессе разработки есть всё, чтобы уязвимости не дошли до релиза:
право на коммит получают только постоянные контрибьюторы с большим опытом;
существует отдельная команда Core Security — единственная из 23 команд, в которую невозможно «зайти с улицы»;
в рамках проекта существует система ответственного раскрытия информации;
есть программа Bug bounty с двойным вознаграждением за сообщение об уязвимости до релиза;
отлажен многоступенчатый релизный процесс
— Alfa → Beta 1, 2, 3 → RC 1, 2, 3, 4, 5 → Dry run → Release;обновления безопасности выпускаются для мажорных версий за последние 9-10 лет с долей активных установок в 99% (с июля 2025 года для версий от 4.6);
ведётся постоянная работа по покрытию кода и баг-фиксов Unit-тестами.
Но зачастую проблемы с безопасностью возникают не в ядре. Проблемы следующие:
оставил дистрибутив на хостинге готовым для установки кем угодно;
взлом другого сайта на том же хостинге;
простые пароли пользователей или случайно опубликованные креды;
проблемы с безопасностью в вендорной теме или плагине (включая взлом их аккаунта в репозитории);
бэкдоры, оставленные разработчиками сознательно;
игнорирование разработчиками встроенных средств защиты.
Ни один разработчик не даст 100%. Однако используя имеющиеся инструменты можно нивелировать риски и оперативно реагировать.
Пользователю или разработчику доступно:
автообновление (можно отключить полностью или только мажорные);
соли для создания хешей (нужно задать если конфиг-файл создается вручную);
отключение редактирования файлов из админки (включено по-умолчанию);
автоматическое создание надёжных паролей (можно запретить слабые);
ограничение доступа в соответствии с имеющимися ролями или создание новых;
доступна 2FA (со сторонними плагинами);
nonce (встроенные средства создания/валидации);
верификация core-файлов средствами WP-CLI:
wp core verify-checksums
;плагины для сканирования на уязвимости как Jetpack Protect;
Plugin Check- и Theme Check-плагины;
Встроенные функции
// Проверка прав пользователя
if ( current_user_can( 'edit_others_posts' ) ) { ... }
$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post->ID, 'title', $title );
wp_strip_all_tags( '<script>something</script>' ); // empty ''
// Для сравнения PHP-функция:
strip_tags( '<script>something</script>' ); // something
validate_file( $file, $allowed_files );
В WordPress существует широкий набор функций для санитизации данных и возможность гибкой работы с правами пользователя.
Защита от SQL-инъекций
global $wpdb;
$sql = $wpdb->prepare( "INSERT INTO $wpdb->postmeta
( post_id, meta_key, meta_value ) VALUES ( %d, %s, %s )",
10, $metakey, $metavalue );
$wpdb->query( $sql );
// Часть методов являются оберткой для prepare -> query:
class wpdb {
...
public function update( $table, $data, $where,
$format = null, $where_format = null ) {
...
return $this->query( $this->prepare( $sql, $values ) );
}
}
WordPress предоставляет широкий инструментарий для обеспечения безопасности, но никак не ограничивает разработчиков и не навязывает их использование, поэтому безопасность проекта в значительной степени зависит от качества его элементов — темы и плагинов, а также непосредственно разработчиков проекта.
Заключение
WordPress позволяет ускорить разработку, минимизировать затраты и обеспечить высокое качество и возможность лёгкого развития проекта. Его недостатки происходят из достоинств — создать медленного неповоротливого монстра, который хорошо кушает, но плохо растёт столь же легко, как и быстрый, удобный и легко развивающийся сайт, портал, интернет-магазин, образовательную платформу или ERP-систему.
Общее понимание особенностей, возможностей и логики работы WP «под капотом» поможет использовать его эффективно, а детальное изучение его компонентов — заимствовать опыт для собственных проектов.
На HighLoad++ 2025 в Москве этой осенью будет ещё много интересных тем. Пока набор докладчиков ещё не закрыт и вы можете предложить свой доклад. Подробности на официальном сайте конференции.
Комментарии (4)
lipatovroman
25.06.2025 13:49Согласен. Но на сегодня, все это есть и в Joomla. Кнопка обновить + предупреждение о возможных несовместимых расширениях. А так да, раньше сделать обновление - это слезы. Но переезд на WP еще слезоточивее был. По поводу расширений, их тоже огромное количество. Я не знаю, чего там нет такого, что есть в WP. А блочный редактор - это зло, по моему мнению. Хотя, судя по всему, именно он + обновление и сделали его популярным. Ну то есть не нужно лезть в код, следить за количеством элементов в DOM и т.д. Красиво и ладно. А то, что одна кнопка из блочного редактора завернута в кучу div, то такое. Да я в целом только "за" конкуренцию. Иначе, все стояло бы на месте.
lipatovroman
Всегда работал с Joomla. А последние выпуски 4 и 5 - вообще песня. И да, если у вас доступы к БД под рукой, то займет установка не 5, а 2 минуты. Но то, такое. Срач разводить не собираюсь.
В общем, как-то думаю, дай попробую этот WP. Все таки самый популярный в мире движок. И как-то очень быстро, практически в первый же день, я понял, что мне с ним не по пути.
Причина? Ну она на поверхности. Когда вы вставляете в статью Joomla изображение, это прямая scr ссылка на конкретное изображение, в какой-то папке. Все.
В базе данных, в таблице _content строк ровно столько, сколько у вас статей.
И каково же было мое удивление, когда я увидел, что WP для каждого изображения в статье создает в БД новую строку с типом attachment. То есть, если у вас есть статья, а в ней 20 изображений, в таблице _posts вы получите 21 одну строку. В итоге, WP делает 21 запрос к БД, а потом еще 20 к диску для физического получения файлов. В то время как в Joomla к БД идет один запрос, остальные напрямую к диску.
Я так и не понял, для чего это сделано именно так, но это явно не по феншую.
Artem_Omny
В Joomla ранее тоже многое было сделано неправильно и команда Joomla решила всё это переписывать, даже ценой обратной совместимости и потери пользователей. Теперь у Joomla отличный код, но аудитория хотела просто нажимать кнопку Обновить. Команда WP же решила делать бесшовные обновления и сильно вложилась в маркетинг, впрочем не забывая и о новых технологиях, но поверх старого легаси. Встроенный блочный редактор, кастомные поля, огромное количество решений в магазине. Проблемы производительности решились доступностью быстрого железа и на этом все успокоились.
JerryI
Да это понятно зачем. Смотрите, у вас фотография в статье и на сайте вы не один автор. Если это коммерция, то сразу вопрос: какая лицензия и тп. Те в WP изначально задумывал что все пикчи это сущности отдельные со своей страницей (если нужно). Всякие вики движки точно также делают…
Насчет числа запросов: я думаю там все кэшируется…