Решил перевести на русский официальное руководство WordPress по разработке плагинов. Штука полезная, но на русском не нашел. Так что, добро пожаловать в справочник разработчика плагинов WordPress! Вы готовы окунуться в мир плагинов WordPress?

Если вы хотите помочь перевести оригинальное руководство или дополнить/поправить текущее – напишите мне через форму обратной связи на сайте.

Руководство разработчика плагинов – это ресурс по плагинам WordPress. Независимо от того, являетесь ли вы новичком в разработке плагинов для WordPress или опытным разработчиком плагинов, вы сможете найти здесь ответы на многие вопросы, связанные с плагинами.

  1. Если вы новичок в разработке плагинов, начните с прочтения введения, а затем перейдите к основам.
  2. Раздел 3 познакомит вас с безопасностью плагина.
  3. С помощью хуков ваш плагин взаимодействует с WordPress. Узнайте все о них в разделе 4.
  4. Чтобы узнать больше о встроенных функциях WordPress, которые вы можете использовать в своем плагине, ознакомьтесь с разделами 5-11: меню администрирования, шорткоды, настройки, метаданные, пользовательские типы записей, таксономии и пользователи.
  5. Узнайте о получении данных с использованием HTTP API в разделе 12.
  6. Если вы используете JavaScript, jQuery или Ajax в своем плагине, вы найдете необходимую информацию в разделе 13.
  7. Чтобы узнать о задачах WordPress, основанных на времени, используя Cron, ознакомьтесь с разделом 14.
  8. Разделы 15-17 познакомят вас с интернационализацией вашего плагина, подготовкой его к выпуску на WordPress.org и некоторыми инструментами разработчика, которые могут оказаться полезными.

Руководство разработчика плагинов WordPress создано сообществом WordPress для сообщества WordPress. Мы всегда ищем новых участников; если вам интересно, зайдите в блог команды разработчиков документации, чтобы узнать больше об участии.

Ниже те разделы, что я успел перевести.

Содержание скрыть

1. Введение в разработку плагинов

Добро пожаловать в Справочник разработчика плагинов. Независимо от того, пишете ли вы свой первый плагин или свой пятидесятый, мы надеемся, что этот ресурс поможет вам написать лучший плагин из возможных.

Руководство разработчика плагинов охватывает множество тем – от того, что должно быть в заголовке плагина, до рекомендаций по безопасности и инструментов, которые вы можете использовать для создания вашего плагина.

Еще руководство про то, как работать в процессе – если вы найдете что что-то пропустили или является неполным, пожалуйста, вы сможете отредактировать это и сделать это лучше.

В WordPress есть три основных компонента:

  • ядро
  • темы
  • плагины

Это руководство о плагинах и их взаимодействии с WordPress. Это поможет вам понять, как они работают и как создать свой собственный.

Почему мы делаем плагины

Есть одно кардинальное правило в разработке WordPress – не трогайте ядро WordPress. Это означает, что вы не редактируете основные файлы WordPress для добавления функциональности на ваш сайт.

Это происходит потому, что при обновлении WordPress до новой версии, он перезаписывает все основные файлы. Любая функциональность, которую вы хотите добавить, должна быть добавлена через плагины с использованием утвержденных API WordPress.

Плагины WordPress могут быть простыми или сложными, в зависимости от того, что вы хотите сделать.

Самый простой плагин представляет собой отдельный PHP-файл. Плагин Hello Dolly является примером такого плагина. Для PHP-файла плагина просто нужны заголовок плагина, пара PHP-функций и некоторые хуки для присоединения ваших функций.

Плагины позволяют значительно расширить функциональность WordPress, не касаясь самого ядра WordPress.

1.1 Что такое плагин?

Плагины – это пакеты кода, которые расширяют основные функциональные возможности WordPress. Плагины WordPress состоят из кода PHP и других ресурсов, таких как изображения, CSS и JavaScript.

Создавая собственный плагин, вы расширяете WordPress, т.е. создаете дополнительные функциональные возможности поверх того, что WordPress уже предлагает. Например, вы можете написать плагин, который отображает ссылки на десять последних статей на вашем сайте.

Или, используя настраиваемые типы записей WordPress, вы можете написать плагин, который создает полнофункциональную систему поддержки билетов с уведомлениями по электронной почте, пользовательскими статусами заявок и порталом для клиентов. Возможности безграничны!

Большинство плагинов WordPress состоят из множества файлов, но плагину действительно нужен только один основной файл со специально отформатированным DocBlock в заголовке.

Hello Dolly, один из первых плагинов в котором всего 82 строки. “Привет Долли” показывает текст из знаменитой песни в админке WordPress. Некоторые CSS стили используются в файле PHP для управления стилями лирики.

Как автор плагина WordPress.org, у вас есть удивительная возможность создать плагин, который будет установлен и любим миллионами пользователей WordPress. Все, что вам нужно сделать, это превратить вашу прекрасную идею в код. Это руководство по разработке плагинов поможет вам в этом.

2. Основы плагинов

В простейшем случае плагин WordPress представляет собой файл PHP с комментарием в заголовке плагина. Настоятельно рекомендуется создать каталог для размещения вашего плагина, чтобы все файлы вашего плагина были аккуратно организованы в одном месте.

Чтобы начать создавать новый плагин, выполните следующие действия.

  1. Перейдите в каталог wp-content установки WordPress.
  2. Откройте каталог плагинов – plugins.
  3. Создайте новый каталог и дайте ему соответствующее название, например, plugin-name.
  4. Откройте каталог (директорию) нового плагина.
  5. Создайте новый файл PHP. Его полезно назвать также, например, plugin-name.php.

Вот как выглядит процесс в командной строке Unix:

wordpress$ cd wp-content
wp-content$ cd plugins
plugins$ mkdir plugin-name
plugins$ cd plugin-name
plugin-name$ vi plugin-name.php

В приведенном выше примере «vi» – это имя текстового редактора. Используйте любой удобный для вас редактор.

Теперь, мы можем редактировать файл PHP своего нового плагина, и нам нужно добавить комментарий в заголовке плагина. Это специально отформатированный блочный комментарий PHP, содержащий метаданные о плагине, такие как его имя, автор, версия, лицензия и т.д. Комментарий в заголовке плагина должен соответствовать требованиям к заголовку и, как минимум, содержать имя плагина.

Только один файл в папке плагина должен иметь комментарий заголовка – если плагин имеет несколько файлов PHP, только один из этих файлов должен иметь комментарий заголовка.

<?php
/**
 * Plugin Name: YOUR PLUGIN NAME
 */

После сохранения файла вы сможете увидеть свой плагин в списке плагинов на вашем сайте WordPress. Войдите в админку своего сайта WordPress и нажмите «Плагины» (Plugins) на левой панели навигации администратора WordPress. На этой странице отображается список всех плагинов, которые есть на вашем сайте. Ваш новый плагин теперь должен быть в этом списке!

Хуки (hooks): действия (actions) и фильтры

Хуки WordPress (hooks) позволяют вам подключаться к WordPress в определенных точках, чтобы изменить поведение WordPress без редактирования каких-либо основных файлов.

В WordPress есть два типа хуков: действия (actions) и фильтры (filters). Действия позволяют добавлять или изменять функциональные возможности WordPress, а фильтры позволяют изменять содержимое по мере его загрузки и отображения пользователю веб-сайта.

Хуки служат не только для разработчиков плагинов; Хуки широко используются для обеспечения функциональности по умолчанию самим ядром WordPress. Другие хуки – это неиспользуемые заполнители, к которым вы можете просто подключиться, когда вам нужно изменить работу WordPress. Это то, что делает WordPress таким гибким.

Основные хуки

3 основных хука, которые вам понадобятся при создании плагина, это:

  • register_activation_hook(),
  • register_deactivation_hook(),
  • register_uninstall_hook().

Хук активации запускается при активации вашего плагина. Вы можете использовать его, чтобы предоставить функцию для настройки вашего плагина – например, создание некоторых настроек по умолчанию в таблице параметров.

Хук деактивации запускается, когда вы деактивируете свой плагин. Вы можете использовать его для предоставления функции, которая очищает любые временные данные, которые создал ваш плагин.

Методы деинсталяции (удаления) используются для очистки после удаления (delete) вашего плагина через админ панель WordPress. Вы можете использовать его для удаления всех данных, созданных вашим плагином, таких как любые опции, которые были добавлены в таблицу опций (options).

Добавление хуков

Вы можете добавить свои собственные пользовательские хуки с помощью do_action(), которая позволит разработчикам расширять ваш плагин, передавая функции через ваши хуки.

Удаление хука

Вы также можете использовать invoke remove_action(), чтобы удалить функцию, которая была определена ранее. Например, если ваш плагин является надстройкой к другому плагину, вы можете использовать remove_action() с той же функцией обратного вызова, которая была добавлена предыдущим плагином с помощью add_action(). Приоритет действий важен в этих ситуациях, так как remove_action() должен был бы выполняться после начальной add_action().

Вы должны быть осторожны при удалении действия из хука, а также при изменении приоритетов, потому что может быть трудно понять, как эти изменения повлияют на другие взаимодействия с тем же хуком. Мы настоятельно рекомендуем проводить частые тесты.

Вы можете узнать больше о создании хуков и взаимодействии с ними в разделе хуков этого руководства.

WordPress API

Знаете ли вы, что WordPress предоставляет несколько интерфейсов прикладного программирования (Application Programming Interfaces или API)? Эти API-интерфейсы могут значительно упростить ваш код, который вы пишете в своих плагинах. Вы не захотите изобретать велосипед, особенно когда очень много людей проделали до вас большую работу и проводили много тестов.

Наиболее распространенным из них является API параметров (Options API), который позволяет легко хранить данные в базе данных для вашего плагина. Если вы думаете об использовании cURL в своем плагине, то вам пригодится HTTP API.

Поскольку мы говорим о плагинах, вам нужно изучить API плагинов (Plugin API). Он имеет множество функций, которые помогут вам в разработке плагинов.

Как WordPress загружает плагины

Когда WordPress загружает список установленных плагинов на странице плагинов администратора WordPress, он просматривает папку плагинов (и ее подпапки), чтобы найти файлы PHP с комментариями в заголовках плагинов WordPress.

Если весь ваш плагин состоит из одного PHP-файла, например, как Hello Dolly, этот файл может находиться непосредственно в корне папки плагинов. Но чаще всего файлы плагинов будут находиться в собственной папке, названной в честь плагина.

Распространение плагина

Иногда созданный плагин предназначен только для вашего сайта. Но многим нравится делиться своими плагинами с остальным сообществом WordPress.

Прежде чем делиться своим плагином, вам нужно выбрать лицензию. Это позволяет пользователю вашего плагина знать, как ему разрешено использовать ваш код. Для обеспечения совместимости с ядром WordPress рекомендуется выбрать лицензию, которая работает с GNU General Public License (GPLv2 +).

2.1 Требования к заголовку (header)

Как описано в разделе «Основы плагинов», основной файл PHP должен содержать комментарий заголовка, который сообщает WordPress, что файл является плагином, и предоставляет информацию о плагине.

Минимальные поля

Как минимум, заголовок комментария должен содержать имя плагина (Plugin Name):

<?php
/**
 * Plugin Name: YOUR PLUGIN NAME
 */

Поля заголовка

Доступные поля заголовка:

  • Plugin Name (обязательно): Название вашего плагина, который будет отображаться в списке плагинов в админке WordPress.
  • Plugin URI: Домашняя страница плагина, которая должна быть уникальным URL, желательно на вашем собственном сайте. Должно быть уникальным для вашего плагина. Вы здесь не можете использовать URL-адрес WordPress.org.
  • Description: Краткое описание плагина, которое показано в разделе плагинов в админке WordPress. Описание должно быть до 140 символов.
  • Version: Текущий номер версии плагина, например 1.0 или 1.0.3.
  • Requires at least: Самая низкая версия WordPress, над которой будет работать плагин.
  • Requires PHP: Минимальная требуемая версия PHP.
  • Author: Имя автора плагина. Несколько авторов могут быть перечислены с помощью запятых.
  • Author URI: Сайт автора или профиль на другом сайте, например WordPress.org.
  • License: Краткое название (slug, слаг) лицензии плагина (например, GPLv2). Более подробную информацию о лицензировании можно найти в руководствах WordPress.org.
  • License URI: Ссылка на полный текст лицензии (например, https://www.gnu.org/licenses/gpl-2.0.html).
  • Text Domain: Текстовый домен gettext плагина. Дополнительную информацию можно найти в разделе «Текстовый домен» (Text Domain) на странице «Как интернационализировать свой плагин» (How to Internationalize your Plugin).
  • Domain Path: Путь к домену позволяет WordPress знать, где найти переводы. Дополнительную информацию можно найти в разделе «Путь к домену» (Domain Path) на странице «Как интернационализировать свой плагин» (How to Internationalize your Plugin).
  • Network: Может ли плагин быть активирован только для всей сети. Может быть установлено только в true и должно быть пропущено, когда нет необходимости.

Правильный файл PHP с комментарием заголовка может выглядеть так:

<?php
/**
 * Plugin Name:       My Basics Plugin
 * Plugin URI:        https://example.com/plugins/the-basics/
 * Description:       Handle the basics with this plugin.
 * Version:           1.10.3
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            John Smith
 * Author URI:        https://author.example.com/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       my-basics-plugin
 * Domain Path:       /languages
 */

Вот еще один пример, который допускает файловый уровень PHPDoc DocBlock, а также заголовки файлов плагинов WordPress:

<?php
/**
 * Plugin Name
 *
 * @package           PluginPackage
 * @author            Your Name
 * @copyright         2019 Your Name or Company Name
 * @license           GPL-2.0-or-later
 *
 * @wordpress-plugin
 * Plugin Name:       Plugin Name
 * Plugin URI:        https://example.com/plugin-name
 * Description:       Description of the plugin.
 * Version:           1.0.0
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            Your Name
 * Author URI:        https://example.com
 * Text Domain:       plugin-slug
 * License:           GPL v2 or later
 * License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
 */

Замечание

При назначении номера версии вашему проекту, помните, что WordPress использует функцию PHP version_compare() для сравнения номеров версий плагина. Поэтому, прежде чем выпускать новую версию своего плагина, вы должны убедиться, что эта функция PHP считает, что новая версия «больше», чем старая. Например, 1.02 на самом деле больше, чем 1.1.

2.2 Включение лицензии на программное обеспечение

Большинство плагинов WordPress выпускаются под лицензией GPL, которая является той же лицензией, которую использует сам WordPress. Однако есть и другие варианты. Всегда лучше четко указать лицензию, которую использует ваш плагин.

В разделе «Требования к заголовку» мы кратко упомянули, как вы можете указать лицензию вашего плагина в комментарии к заголовку плагина. Другая распространенная и рекомендуемая практика – размещать комментарий к блоку лицензии в верхней части основного файла плагина (того же, в котором есть комментарий к заголовку плагина).

Этот комментарий к блоку лицензии обычно выглядит примерно так:

/*
{Plugin Name} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
any later version.
 
{Plugin Name} is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with {Plugin Name}. If not, see {URI to Plugin License}.
*/

В сочетании с комментарием к заголовку плагина:

<?php
/*
Plugin Name: WordPress.org Plugin
Plugin URI:  https://developer.wordpress.org/plugins/the-basics/
Description: Basic WordPress Plugin Header Comment
Version:     20160911
Author:      WordPress.org
Author URI:  https://developer.wordpress.org/
Text Domain: wporg
Domain Path: /languages
License:     GPL2
 
{Plugin Name} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
any later version.
 
{Plugin Name} is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with {Plugin Name}. If not, see {License URI}.
*/

2.3 Хуки активации / деактивации

Хуки активации и деактивации предоставляют способы выполнения действий, когда плагины активируются или деактивируются.

При активации плагины могут запускать подпрограмму (функцию) для добавления правил перезаписи, добавления пользовательских таблиц базы данных или установки значений параметров по умолчанию.

При деактивации плагины могут запускать подпрограмму для удаления временных данных, таких как кеш, временные файлы и каталоги.

Хук деактивации иногда путают с хуком удаления. Хук удаления лучше всего подходит для постоянного удаления всех данных, таких как удаление параметров плагина, пользовательских таблиц и т.д.

Активация

Чтобы настроить хук активации, используйте функцию register_activation_hook():

register_activation_hook( __FILE__, 'pluginprefix_function_to_run' );

Деактивация

Чтобы установить хук деактивации, используйте функцию register_deactivation_hook():

register_deactivation_hook( __FILE__, 'pluginprefix_function_to_run' );

Первый параметр в каждой из этих функций относится к вашему основному файлу плагина, который является файлом, в который вы поместили комментарий заголовка плагина.

Обычно эти две функции запускаются из основного файла плагина; однако, если функции помещены в любой другой файл, вы должны обновить первый параметр, чтобы он правильно указывал на основной файл плагина.

Пример

Одним из наиболее распространенных способов использования хука активации является обновление постоянных ссылок WordPress, когда плагин регистрирует пользовательский тип записи. Это избавляет от неприятных 404 ошибок.

Давайте рассмотрим пример того, как это сделать:

/**
 * Register the "book" custom post type
 */
function pluginprefix_setup_post_type() {
    register_post_type( 'book', ['public' => true ] ); 
} 
add_action( 'init', 'pluginprefix_setup_post_type' );
 
 
/**
 * Activate the plugin.
 */
function pluginprefix_activate() { 
    // Trigger our function that registers the custom post type plugin.
    pluginprefix_setup_post_type(); 
    // Clear the permalinks after the post type has been registered.
    flush_rewrite_rules(); 
}
register_activation_hook( __FILE__, 'pluginprefix_activate' );

Если вы не знакомы с регистрацией пользовательских типов записей, не беспокойтесь – об этом будет рассказано позже. Этот пример используется просто потому, что он очень распространен.

Используя приведенный выше пример, ниже показано, как отменить этот процесс и деактивировать плагин:

/**
 * Deactivation hook.
 */
function pluginprefix_deactivate() {
    // Unregister the post type, so the rules are no longer in memory.
    unregister_post_type( 'book' );
    // Clear the permalinks to remove our post type's rules from the database.
    flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'pluginprefix_deactivate' );

Для получения дополнительной информации об активации и деактивации хуков, есть несколько отличных ресурсов:

2.4 Методы деинсталляции

Вашему плагину может потребоваться некоторая очистка, когда он удаляется с сайта.

Плагин считается деинсталлированным, если пользователь деактивировал плагин, а затем кликает ссылку удаления в админке WordPress.

Когда ваш плагин будет удален, вы захотите очистить все его параметры и/или настройки, относящиеся к плагину, и/или другие объекты базы данных, такие как таблицы.

Менее опытные разработчики иногда допускают ошибку, используя для этого хук деактивации.

Эта таблица иллюстрирует различия между деактивацией и удалением.

СценарийХук деактивацииХук деинсталляции
Очистка кеш / временных файловДаНет
Очистка постоянных ссылокДаНет
Удаление параметров из {$wpdb-> prefix}_optionsНетДа
Удаление таблицы из wpdbНетДа

Метод 1: register_uninstall_hook

Чтобы настроить хук удаления, используйте функцию register_uninstall_hook():

register_uninstall_hook(__FILE__, 'pluginprefix_function_to_run');

Метод 2: uninstall.php

Чтобы использовать этот метод, вам нужно создать файл uninstall.php в корневой папке вашего плагина. Этот волшебный файл запускается автоматически, когда пользователи удаляют плагин.

Например: /plugin-name/uninstall.php

При использовании uninstall.php перед выполнением плагин всегда должен проверять константу WP_UNINSTALL_PLUGIN, чтобы предотвратить прямой доступ.

Константа будет определена WordPress во время вызова uninstall.php.

Константа НЕ определяется, когда деинсталляция выполняется через register_uninstall_hook().

Вот пример удаления записей опций и удаления таблицы базы данных:

// если uninstall.php не вызван WordPress, die
if (!defined('WP_UNINSTALL_PLUGIN')) {
    die;
}
 
$option_name = 'wporg_option';
 
delete_option($option_name);
 
// для опций сайтов в Мультисайте
delete_site_option($option_name);
 
// удалить пользовательскую таблицу базы данных
global $wpdb;
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}mytable");
В мультисайтах циклический просмотр всех блогов для удаления параметров может быть очень ресурсоемким.

2.5 Лучшие практики

Вот несколько рекомендаций (лучших практик), которые помогут организовать ваш код так, чтобы он хорошо работал вместе с ядром WordPress и другими плагинами WordPress.

Избегайте конфликтов имен

Конфликт имен происходит, когда ваш плагин использует то же имя для переменной, функции или класса, что и другой плагин.

К счастью, вы можете избежать именования коллизий, используя методы ниже.

Процедурные

По умолчанию все переменные, функции и классы определены в глобальном пространстве имен, что означает, что ваш плагин может переопределять переменные, функции и классы, установленные другим плагином, и наоборот.

На переменные, которые определены внутри функций или классов, это не влияет.

Префикс для всего

Все переменные, функции и классы должны иметь префикс с уникальным идентификатором. Префиксы не позволяют другим плагинам перезаписывать ваши переменные и случайно вызывать ваши функции и классы. Это также помешает вам сделать то же самое.

Проверка существующих реализаций

PHP предоставляет ряд функций для проверки существования переменных, функций, классов и констант. Все они вернут истину (true), если сущность существует.

Пример

// создаем функцию "wporg_init" если она еще не существует
if ( !function_exists( 'wporg_init' ) ) {
    function wporg_init() {
        register_setting( 'wporg_settings', 'wporg_option_foo' );
    }
}
 
// создаем функцию "wporg_get_foo" если она еще несуществует
if ( !function_exists( 'wporg_get_foo' ) ) {
    function wporg_get_foo() {
        return get_option( 'wporg_option_foo' );
    }
}

ООП

Более простой способ решения проблемы коллизии имен – использовать класс для кода вашего плагина.

Вам по-прежнему нужно будет позаботиться о том, чтобы убедиться, что имя нужного вам класса уже занято, но об остальном позаботится PHP.

Пример

if ( !class_exists( 'WPOrg_Plugin' ) ) {
    class WPOrg_Plugin
    {
        public static function init() {
            register_setting( 'wporg_settings', 'wporg_option_foo' );
        }
 
        public static function get_foo() {
            return get_option( 'wporg_option_foo' );
        }
    }
 
    WPOrg_Plugin::init();
    WPOrg_Plugin::get_foo();
}

Организация файлов

Корневой уровень вашей директории плагинов должен содержать ваш файл plugin-name.php и, необязательно, файл uninstall.php. Все остальные файлы должны быть организованы в подпапки, когда это возможно.

Структура папок

Четкая структура папок помогает вам и другим людям, работающим над вашим плагином, хранить похожие файлы вместе.

Вот пример структуры папок для справки:

/plugin-name
     plugin-name.php
     uninstall.php
     /languages
     /includes
     /admin
          /js
          /css
          /images
     /public
          /js
          /css
          /images

Архитектура плагина

Архитектура или организация кода, которую вы выбираете для своего плагина, вероятно, будет зависеть от размера вашего плагина.

Для небольших плагинов специального назначения, которые имеют ограниченное взаимодействие с ядром WordPress, темами или другими плагинами, мало смысла в разработке сложных классов; только если вы не планируете значительно расширять плагин позже.

Для больших плагинов с большим количеством кода начните с классов. Отдельные файлы стилей и скриптов, и даже файлы, связанные со сборкой. Это поможет организации кода и долгосрочном обслуживании плагина.

Условная загрузка

Полезно отделить ваш код администратора от общедоступного кода. Используйте условный is_admin().

Например:

if ( is_admin() ) {
    // мы в режиме админа
    require_once __DIR__ . '/admin/plugin-name-admin.php';
}

Архитектурные паттерны

Хотя существует несколько возможных архитектурных шаблонов, их можно сгруппировать в три варианта:

Объясненные паттернов архитектуры

Конкретные реализации более сложной из вышеперечисленных организаций кода уже были написаны в виде учебных пособий и слайдов:

Стартовые точки Boilerplate (шаблона)

Вместо того, чтобы начинать с нуля для каждого нового плагина, который вы пишете, вы можете начать с шаблона (boilerplate). Одним из преимуществ использования шаблона является согласованность между вашими плагинами. Boilerplates также облегчают другим людям добавлять свой код, если вы используете шаблон, с которым они уже знакомы.

Это также служит дополнительными примерами различных, но сопоставимых архитектур.

  • WordPress Plugin Boilerplate: Основа для разработки плагинов WordPress, целью которой является предоставление четкого и последовательного руководства по созданию ваших плагинов.
  • WordPress Plugin Bootstrap: Базовый загрузчик для разработки плагинов WordPress с использованием Grunt, Compass, GIT и SVN.
  • WP Skeleton Plugin: Skeleton плагин, который фокусируется на модульных тестах и использовании композера для разработки.
  • WP CLI Scaffold: Команда Scaffold WP CLI создает каркасный плагин с такими параметрами, как файлы конфигурации CI.

Конечно, вы можете использовать различные аспекты из примеров выше и другие, чтобы создать свой собственный шаблон.

2.6 Определение плагинов и директорий контента

При разработке плагинов WordPress вам часто нужно ссылаться на различные файлы и папки в процессе установки WordPress и в вашем плагине или теме.

WordPress предоставляет несколько функций для простого определения того, где находится данный файл или каталог. Всегда используйте эти функции в своих плагинах вместо того, чтобы жестко кодировать ссылки на каталог wp-content или использовать внутренние константы WordPress.

WordPress позволяет пользователям размещать каталог wp-content в любом месте и переименовывать его по своему усмотрению. Никогда не думайте, что плагины будут в wp-content/plugins, загрузки будут в wp-content/uploads, или что темы будут в wp-content/themes.

Волшебная константа PHP __FILE__ автоматически разрешает символические ссылки, поэтому, если символические ссылки содержат wp-content или wp-content/plugins или даже отдельный каталог плагинов, жестко закодированные пути не будут правильно работать.

Основное использование

Если ваш плагин содержит файлы JavaScript, CSS-файлы или другие внешние файлы, вероятно, вам понадобится URL-адрес этих файлов, чтобы вы могли загрузить их на страницу. Для этого вы должны использовать функцию plugins_url() следующим образом:

plugins_url( 'myscript.js', _FILE_ );

Это вернет полный URL-адрес файла myscript.js, например:

example.com/wp-content/plugins/myplugin/myscript.js

Чтобы загрузить JavaScript или CSS ваших плагинов на страницу, вы должны использовать wp_enqueue_script() или wp_enqueue_style() соответственно, передавая результат plugins_url() в качестве URL файла.

Доступные функции

WordPress включает в себя множество других функций для определения путей и URL-адресов файлов или каталогов внутри плагинов, тем и самого WordPress. См. отдельные страницы Кодекса для каждой функции для получения полной информации об их использовании.

Плагины

plugins_url()
plugin_dir_url()
plugin_dir_path()
plugin_basename()

Темы

get_template_directory_uri()
get_stylesheet_directory_uri()
get_stylesheet_uri()
get_theme_root_uri()
get_theme_root()
get_theme_roots()
get_stylesheet_directory()
get_template_directory()

Главная страница

home_url()
get_home_path()

WordPress

admin_url()
site_url()
content_url()
includes_url()
wp_upload_dir()

Мультисайт

get_admin_url()
get_home_url()
get_site_url()
network_admin_url()
network_site_url()
network_home_url()

Константы

WordPress использует следующие константы при определении пути к каталогам содержимого и плагинов. Они не должны использоваться непосредственно плагинами или темами, но перечислены здесь для полноты информации.

WP_CONTENT_DIR  // без косой черты, только полные пути
WP_CONTENT_URL  // полный url 
WP_PLUGIN_DIR  // полный путь, без косой черты
WP_PLUGIN_URL  // полный URL, без косой черты
// Доступно по умолчанию в Мультисайт, не устанавливается при установке на одном сайте
// Может использоваться в одиночной установке сайта (как обычно: на свой страх и риск)
UPLOADS // (Если установлено, загружает папку относительно ABSPATH) (например: /wp-content/uploads)

По теме

Каталоги WordPress:

Каталоги WordPress:
home_url() Домашний URL http://www.example.com
site_url() URL каталога сайта http://www.example.com или http://www.example.com/wordpress
admin_url() URL каталога администратора http://www.example.com/wp-admin
includes_url() URL-адрес каталога includes http://www.example.com/wp-includes
content_url() URL каталога содержимого (content) http://www.example.com/wp-content
plugins_url() URL каталога плагинов http://www.example.com/wp-content/plugins
wp_upload_dir() URL каталог uploads (возвращает массив) http://www.example.com/wp-content/uploads

3. Безопасность плагинов

Поздравляем, ваш код работает! Но безопасно ли это? Как плагин защитит ваших пользователей, если их сайт будет взломан? Лучшие плагины в каталоге WordPress.org сохраняют информацию своих пользователей в безопасности.

Помните, что ваш код может работать на сотнях, возможно, даже миллионах веб-сайтов, поэтому безопасность имеет первостепенное значение.

В этой главе мы рассмотрим, как проверять возможности пользователей, проверять и очищать входные данные, очищать выходные данные, создавать и проверять одноразовые числа.

Внешние Ресурсы

3.1 Проверка возможностей пользователя

Если ваш плагин позволяет пользователям отправлять данные – будь то на Админ или на Публичной стороне – он должен проверить на User Capabilities (Возможности пользователя).

Роли пользователей и возможности

Самым важным шагом в создании эффективного уровня безопасности является наличие системы пользовательских разрешений. WordPress обеспечивает это в виде ролей пользователей и возможностей.

Каждому пользователю, авторизованному в WordPress, автоматически присваиваются конкретные возможности пользователя в зависимости от его роли.

Роли пользователей – это просто изящный способ сказать, к какой группе принадлежит пользователь. Каждая группа имеет определенный набор предопределенных возможностей.

Например, основной пользователь вашего веб-сайта будет иметь роль User (Пользователь) администратора, в то время как у других пользователей могут быть роли типа Editor (Редактор) или Author (Автор). Вы можете назначить более одного пользователя на одну роль, т.е. на сайте может быть два администратора.

Пользовательские возможности – это конкретные разрешения, которые вы назначаете каждому пользователю или его роли.

Например, администраторы имеют возможность “управлять_опциями” (manage_options), которая позволяет им просматривать, редактировать и сохранять опции сайта. Редакторы, с другой стороны, не имеют этой возможности, что препятствует их взаимодействию с опциями.

Эти возможности затем проверяются в различных точках Админа. В зависимости от возможностей, назначенных для той или иной роли, могут быть добавлены или удалены меню, функциональность и другие аспекты работы с WordPress.

При создании плагина убедитесь, что код запускается только тогда, когда текущий пользователь обладает необходимыми возможностями.

Иерархия

Чем выше роль пользователя, тем больше у него возможностей. Каждая пользовательская роль наследует предыдущие роли в иерархии.

Например, “Администратор” (Administrator), которая является самой высокой пользовательской ролью на одном автономном сайте, наследует следующие роли и их возможности: “Подписчик” (Subscriber), “Участник” (Contributor), “Автор” (Author) и “Редактор” (Editor).

Примеры

Нет ограничений

В примере, приведенном ниже, создается ссылка на фронтенде, которая дает возможность удалять статьи в корзину. Поскольку этот код не проверяет возможности пользователя, он позволяет любому посетителю удалять статьи!

<?php
/**
* создать ссылку Delete (удалить) на основе URL домашней страницы
*/
function wporg_generate_delete_link($content)
{
// запускать только для одного поста (single)
if (is_single() && in_the_loop() && is_main_query()) {
// добавить аргументы запроса: action, post
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post'   => get_the_ID(),
],
home_url()
);
return $content . ' <a href="' . esc_url($url) . '">' . esc_html__('Delete Post', 'wporg') . '</a>';
}
return null;
}
 
/**
* обработчик запросов
*/
function wporg_delete_post()
{
if (isset($_GET['action']) && $_GET['action'] === 'wporg_frontend_delete') {
 
// проверка на id поста
$post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
 
// проверка наличия поста с таким номером
$post = get_post((int)$post_id);
if (empty($post)) {
return;
}
 
// удалить пост
wp_trash_post($post_id);
 
// перенаправление на страницу админа
$redirect = admin_url('edit.php');
wp_safe_redirect($redirect);
 
// готово
die;
}
}
 
/**
* добавить ссылку на удаление в конце статьи (content)
*/
add_filter('the_content', 'wporg_generate_delete_link');
 
/**
* зарегистрировать обработчик запроса с помощью хука init
*/
add_action('init', 'wporg_delete_post');

Ограничения с конкретными возможностями

Пример выше позволяет любому посетителю сайта нажать на ссылку “Удалить” (Delete) и удалить пост. Однако, мы хотим, чтобы только Редакторы и выше могли нажать на ссылку “Удалить”.

Для этого мы проверим, что у текущего пользователя есть возможность редактирования постов edit_others_posts, которая есть только у редакторов и выше:

<?php
/**
* создать ссылку Delete на основе URL домашней страницы
*/
function wporg_generate_delete_link($content)
{
// запускать только для одного поста
if (is_single() && in_the_loop() && is_main_query()) {
// добавлять аргументы запроса: action, post
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post'   => get_the_ID(),
],
home_url()
);
return $content . ' <a href="' . esc_url($url) . '">' . esc_html__('Delete Post', 'wporg') . '</a>';
}
return null;
}
 
/**
* обработчик запросов
*/
function wporg_delete_post()
{
if (isset($_GET['action']) && $_GET['action'] === 'wporg_frontend_delete') {
 
// проверьте, есть ли у нас id поста
$post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
 
// проверить наличие поста с таким номером
$post = get_post((int)$post_id);
if (empty($post)) {
return;
}
 
// удалить пост
wp_trash_post($post_id);
 
// перенаправление на страницу Админа
$redirect = admin_url('edit.php');
wp_safe_redirect($redirect);
 
// готово
die;
}
}
 
if (current_user_can('edit_others_posts')) {
/**
* добавить ссылку на удаление в конце публикации
*/
add_filter('the_content', 'wporg_generate_delete_link');
 
/**
* зарегистрировать обработчик запроса с помощью хука init
*/
add_action('init', 'wporg_delete_post');
}

3.2 Валидация данных

Валидация данных – это процесс анализа данных по предопределенному шаблону (или шаблонам) с окончательным результатом: действительным или недействительным.

Обычно это относится к данным, поступающим из внешних источников, таких как пользовательский ввод и запросы к веб-сервисам через API.

Простые примеры валидации данных:

  • Убедитесь, что необходимые поля не оставлены пустыми.
  • Убедитесь, что введенный номер телефона содержит только цифры и знаки препинания.
  • Убедитесь, что введенный почтовый индекс является действительным почтовым индексом.
  • Убедитесь, что количественное поле больше 0

Валидацию данных следует проводить как можно раньше. Это означает, что перед выполнением каких-либо действий необходимо проверить данные.

Валидация может быть выполнена с помощью JavaScript на фронтенде и с помощью PHP на бекэнде.

Проверка данных

Существует как минимум три способа: встроенные функции PHP, основные функции WordPress и пользовательские функции, которые вы пишете.

Встроенные функции PHP

Базовая валидация возможна с использованием многих встроенных функций PHP, в том числе и этих:

  • isset() и empty() для проверки, существует ли переменная и не пуста ли она
  • mb_strlen() или strlen() для проверки, что строка имеет ожидаемое количество символов
  • preg_match(), strpos() для проверки на наличие вхождения одних строк в другие
  • count() для проверки количества элементов в массиве
  • in_array() для проверки, существует ли что-то в массиве

Функции ядра WordPress

WordPress предоставляет множество полезных функций, которые помогают проверять различные типы данных. Вот несколько примеров:

  • is_email() будет проверять действительность адреса электронной почты
  • term_exists() проверяет, существует ли тег, категория или другой таксономический термин
  • функция username_exists() проверяет, существует ли имя пользователя
  • validate_file() подтвердит, что введенный путь к файлу является реальным путем (но не существует ли файл!)

Проверьте руководство программиста WordPress, чтобы узнать больше о таких функциях.

Поиск функций с подобными названиями: *exists(), *_validate() и is*(). Не все из них являются функциями проверки, но многие из них полезны.

Пользовательские функции PHP и JavaScript

Вы можете написать свои собственные PHP и JavaScript функции и включить их в свой плагин. При написании функции валидации вы захотите назвать ее как вопрос (примеры: is_phone, is_available, is_us_zipcode).

Функция должна возвращать булевое значение, true или false, в зависимости от того, являются ли данные действительными или нет. Это позволит использовать функцию в качестве условия.

Пример 1

Допустим, у вас есть поле ввода почтового индекса США, которое отправляет пользователь.

<input id="wporg_zip_code" type="text" maxlength="10" name="wporg_zip_code">

Текстовое поле позволяет вводить до 10 символов без ограничений по типу используемых символов. Пользователь может ввести что-то действительное, например 1234567890 или что-то недействительное (и злое), например eval().

Атрибут максимальной длины (maxlength) в нашем поле ввода вводится только браузером, поэтому вам все равно придется проверять длину вводимого значения на сервере. Если вы этого не сделаете, атакующий может изменить значение maxlength.

Используя валидацию, мы можем быть уверены, что принимаем только действительные почтовые коды.

Сначала вам нужно написать функцию для валидации американских почтовых индексов:

<?php
function is_us_zip_code($zip_code)
{
    // сценарий 1: empty (пусто)
    if (empty($zip_code)) {
        return false;
    }
 
    // сценарий 2: более 10 символов
    if (strlen(trim($zip_code)) > 10) {
        return false;
    }
 
    // сценарий 3: неверный формат
    if (!preg_match('/^\d{5}(\-?\d{4})?$/', $zip_code)) {
        return false;
    }
 
    // успешно пройден
    return true;
}

При обработке формы ваш код должен проверить поле wporg_zip_code и выполнить действие на основании результата:

if (isset($_POST['wporg_zip_code']) && is_us_zip_code($_POST['wporg_zip_code'])) {
    // выше действие
}

Пример 2

Скажем, вы собираетесь сделать запрос к базе данных для некоторых публикаций, и вы хотите дать пользователю возможность сортировать результаты запроса.

Данный пример кода проверяет входящий ключ сортировки (хранящийся во входном параметре “orderby“) на корректность, сравнивая его с массивом разрешенных ключей сортировки с помощью встроенной PHP-функции in_array. Это предотвращает передачу пользователем вредоносных данных и потенциальную угрозу для сайта.

Перед проверкой входящего ключа сортировки по массиву, ключ передается во встроенную функцию WordPress sanitize_key. Эта функция, помимо прочего, гарантирует, что ключ находится в строчном регистре (in_array выполняет поиск с учетом регистра).

Передача “true” в третий параметр in_array позволяет выполнить строгую проверку типа, которая говорит функции не только сравнивать значения, но и типы значений. Это позволяет коду быть уверенным, что входящий ключ сортировки – это строка, а не какой-то другой тип данных.

<?php
$allowed_keys = ['author', 'post_author', 'date', 'post_date'];
 
$orderby = sanitize_key($_POST['orderby']);
 
if (in_array($orderby, $allowed_keys, true)) {
    // изменить запрос, чтобы отсортировать его по порядковым ключам
}

3.3 Безопасность Ввода

Защита ввода данных – это процесс очищения (фильтрации) входных данных.

Вы используете очистку, когда не знаете, чего ожидать, или не хотите быть строгим с валидацией данных.

Всякий раз, когда вы принимаете потенциально опасные данные, важно их проверять или очищать.

Очистка данных

Самый простой способ очистки данных – это встроенные функции WordPress.

Ряд вспомогательных функций sanitize_*() очень полезен, так как эти функции гарантируют, что вы получите безопасные данные, и это требует минимальных усилий с вашей стороны:

Пример

Допустим, у нас есть поле ввода с именем title.

<input id="title" type="text" name="title">

Очистить входные данные можно с помощью функции sanitize_text_field():

$title = sanitize_text_field($_POST['title']);
update_post_meta($post->ID, 'title', $title);

Функция sanitize_text_field() делает следующее:

  • Проверки на недействительный UTF-8
  • Преобразовывает одиночные символы (<) в сущность
  • Удаляет теги
  • Удаляет разрывы строк, табуляции и дополнительное белое пространство
  • Удаляет октеты

3.4 Безопасность вывода

Обеспечение безопасности вывода – это процесс экранирования выходных данных.

Эскейпинг означает удаление нежелательных данных, таких как неверно сформированный HTML или теги скрипта.

Всякий раз, когда вы визуализируете данные, убедитесь, что они правильно экранируются. Экранирование выходных данных предотвращает XSS атаки (межсайтовый скриптинг).

Межсайтовый скриптинг (XSS) – это тип уязвимости компьютерной безопасности, обычно встречающейся в веб-приложениях. XSS позволяет злоумышленникам внедрять клиентские скрипты в веб-страницы, просматриваемые другими пользователями. Уязвимость межсайтового скриптинга может использоваться злоумышленниками для обхода контроля доступа, например, с помощью правила ограничения домена (same-origin policy).

Escaping (экранирование)

Escaping или экранирование помогает защитить ваши данные перед их отображением для конечного пользователя. WordPress имеет несколько вспомогательных функций, которые можно использовать для большинства распространенных сценариев.

  • esc_html() – Используйте эту функцию в любое время, когда HTML-элемент заключает в себе участок отображаемых данных.
  • esc_url() – Используйте эту функцию для всех URL, включая те, которые находятся в атрибутах src и href HTML-элемента.
  • esc_js() – Используйте эту функцию для встроенного Javascript.
  • esc_attr() – Используйте эту функцию для всего остального, что выводится в атрибут HTML-элемента.

Большинство функций WordPress правильно подготавливают данные для вывода, так что вам не нужно будет снова экранировать данные. Например, вы можете безопасно вызвать функцию the_title() без экранирования.

Эскейпинг с локализацией

Вместо того, чтобы использовать echo для вывода данных, обычно используются функции локализации WordPress, такие как _e() или __().

Эти функции просто обертывают функцию локализации внутри экранирующей функции:

esc_html_e( 'Hello World', 'text_domain' );
// точно так же, как
echo esc_html( __( 'Hello World', 'text_domain' ) );

Эти вспомогательные функции сочетают в себе локализацию и экранирование:

Собственный Escaping

В случае, если вам необходимо экранировать ваш вывод определенным образом, вам пригодится функция wp_kses() (произносится как “поцелуй” – kisses).

Эта функция гарантирует, что в вашем выводе будут присутствовать только указанные HTML элементы, атрибуты и значения атрибутов, и нормализует HTML сущности.

$allowed_html = [
'a'      => [
'href'  => [],
'title' => [],
],
'br'     => [],
'em'     => [],
'strong' => [],
];
echo wp_kses( $custom_content, $allowed_html );

wp_kses_post() – функция обёртывания для wp_kses, где $allowed_html – набор правил, используемых для содержимого записи.

echo wp_kses_post( $post_content );

3.5 Одноразовые числа (nonces)

Nonces – это генерируемые числа, используемые для проверки происхождения и интента (намерение) запросов в целях безопасности. Каждое число может быть использована только один раз.

Nonce так и расшифровывается – «число, используемое один раз» (англ. – number used once). Некоторые пользователи называют их токенами.

Если ваш плагин позволяет пользователям предоставлять данные; будь то на стороне администратора или на публичной стороне; вы должны убедиться, что пользователь тот, за кого он себя выдает, и что у него есть необходимые возможности для выполнения этого действия. Выполнение обоих действий в тандеме означает, что данные изменяются только тогда, когда пользователь ожидает их изменения.

Использование nonces

Следуя нашему примеру проверки возможностей пользователей, следующим шагом в обеспечении безопасности предоставления пользовательских данных является использование nonces.

Проверка возможности гарантирует, что только пользователи, имеющие разрешение на удаление сообщения, могут его удалять. Но что, если кто-то обманом заставит вас нажать на эту ссылку? У вас есть необходимая возможность, поэтому вы можете непреднамеренно удалить сообщение.

Одноразовые числа могут быть использованы для проверки того, что текущий пользователь на самом деле намерен выполнить это действие.

Когда вы генерируете ссылку delete (удалить), вы захотите использовать функцию wp_create_nonce() для добавления одноразового числа в ссылку, аргумент, переданный в функцию, гарантирует, что создаваемое число уникально для данного конкретного действия.

Затем, когда вы обрабатываете запрос на удаление ссылки, вы можете проверить, что nonce тот, который вы ожидаете.

Для более подробной информации, рекомендую перевод хорошей статьи Марка Жакита про одноразовые числа.

Подробный пример

Полный пример с использованием проверок возможностей, проверки данных, безопасного ввода, безопасного вывода и одноразовых чисел:

<?php
/**
 * generate a Delete link based on the homepage url
 */
function wporg_generate_delete_link($content)
{
    // run only for single post page
    if (is_single() && in_the_loop() && is_main_query()) {
        // add query arguments: action, post, nonce
        $url = add_query_arg(
            [
                'action' => 'wporg_frontend_delete',
                'post'   => get_the_ID(),
                'nonce'  => wp_create_nonce('wporg_frontend_delete'),
            ],
            home_url()
        );
        return $content . ' <a href="' . esc_url($url) . '">' . esc_html__('Delete Post', 'wporg') . '</a>';
    }
    return null;
}
 
/**
 * request handler
 */
function wporg_delete_post()
{
    if (
        isset($_GET['action']) &&
        isset($_GET['nonce']) &&
        $_GET['action'] === 'wporg_frontend_delete' &&
        wp_verify_nonce($_GET['nonce'], 'wporg_frontend_delete')
    ) {
 
        // verify we have a post id
        $post_id = (isset($_GET['post'])) ? ($_GET['post']) : (null);
 
        // verify there is a post with such a number
        $post = get_post((int)$post_id);
        if (empty($post)) {
            return;
        }
 
        // delete the post
        wp_trash_post($post_id);
 
        // redirect to admin page
        $redirect = admin_url('edit.php');
        wp_safe_redirect($redirect);
 
        // we are done
        die;
    }
}
 
if (current_user_can('edit_others_posts')) {
    /**
     * add the delete link to the end of the post content
     */
    add_filter('the_content', 'wporg_generate_delete_link');
 
    /**
     * register our request handler with the init hook
     */
    add_action('init', 'wporg_delete_post');
}

4. Хуки (Hooks)

Хуки – это способ для одного куска кода взаимодействовать/модифицировать другой кусок кода в специфических, предопределенных точках. Они составляют основу того, как плагины и темы взаимодействуют с ядром WordPress (Core), но они также широко используются самим ядром.

Есть два типа хуков: Действия (Actions) и Фильтры (Filters). Чтобы использовать любой из них, вам нужно написать пользовательскую функцию, известную как Callback, а затем зарегистрировать ее с помощью хука WordPress для конкретного действия или фильтра.

Callback-функции – это Функции обратного вызова.

Действия (Actions) позволяют добавлять данные или изменять работу WordPress. Функции обратного вызова для Actions будут выполняться в определенных точках при выполнении WordPress, и могут выполнять какие-то задачи, например, выводить данные пользователю или вставлять что-то в базу данных. Экшены не возвращают ничего обратно в вызывающий хук.

Фильтры (Filters) дают возможность изменять данные во время выполнения WordPress. Функции обратного вызова для Фильтров будут принимать переменную, изменять ее и возвращать. Они предназначены для изолированной работы и никогда не должны иметь побочных эффектов, таких как воздействие на глобальные переменные и вывод. Фильтры ожидают, что к ним что-то вернется.

Экшены против Фильтров

Основное различие между действием и фильтром можно резюмировать следующим образом:

  • действие принимает информацию, которую получает, что-то делает с ней, и ничего не возвращает. Другими словами: он действует на что-то, а затем выходит, ничего не возвращая обратно на вызывающий хук.
  • фильтр принимает полученную информацию, как-то модифицирует ее и возвращает. Другими словами: он фильтрует что-то и передает это обратно на хук для дальнейшего использования.

Другими словами:

  • действие прерывает поток кода, чтобы что-то сделать, а затем возвращается в нормальный поток, ничего не изменяя;
  • фильтр используется для модификации чего-либо определенным образом, так что модификация затем используется кодом.

“Что-то” – это список параметров, посылаемый через определение хука. Подробнее об этом в следующих разделах.

Дополнительно

4.1 Действия (Actions или экшены)

Действия – один из двух видов Хуков (Hooks). Они обеспечивают способ выполнения функции в определенных точках выполнения в ядре WordPress Core, плагинах и темах. Они являются аналогом Фильтров (Filters).

Добавление действия

Процесс добавления действия включает в себя два этапа:

Создание функции обратного вызова (callback)

Сначала создайте функцию обратного вызова (callback function). Эта функция будет запущена, когда будет выполнено действие, к которому она привязана (или зацеплена хуком).

Функция обратного вызова похожа на обычную функцию: она должна быть префиксной и должна быть в файле functions.php или где-нибудь в вызываемом месте. Параметры, которые она должна принять, будут определяться действием (action), к которому вы подключаетесь (цепляетесь хуком); большинство хуков хорошо определены, поэтому просмотрите документацию хуков, чтобы увидеть, какие параметры выбранное вами действие будет передавать в вашу функцию.

Присваивание (хук) вашей функции обратного вызова

Во-вторых, добавьте callback-функцию к действию (action). Это называется “перехватом” или хукингом и сообщает экшену, что нужно запустить вашу функцию обратного вызова, когда действие (action) запущено.

Когда функция обратного вызова готова, используйте add_action(), чтобы привязать ее к выбранному вами действию. Как минимум, add_action() требует два параметра:

  1. строка (string) $tag – название действия, к которому вы цепляетесь (hook)
  2. вызываемая $function_to_add имя вашей функции обратного вызова

В примере ниже будет запущена функция wporg_callback() при выполнении инициализации хука:

function wporg_callback() {
    // сделать что-то
}
add_action( 'init', 'wporg_callback' );

Список доступных хуков можно найти в главе “Хуки”.

Как только у вас появится больше опыта, просмотр исходного кода ядра WordPress позволит вам находить наиболее подходящие хуки.

Дополнительные параметры

add_action() может принимать два дополнительных параметра, int $priority для приоритета, отдаваемого функции обратного вызова, и int $accepted_args для количества аргументов, которые будут переданы функции обратного вызова.

Приоритет

Многие функции обратного вызова могут быть подключены (зацеплены) к одному действию. Например, хук init очень часто полезен. Могут быть случаи, когда вам нужно убедиться, что ваша функция обратного вызова работает до или после других функций обратного вызова, даже когда эти другие функции, возможно, еще не были подключены.

WordPress определяет порядок выполнения функций обратного вызова на основе двух вещей и первый способ – это ручная установка приоритета. Это делается с помощью третьего аргумента add_action().

Вот некоторые важные факты о приоритетах:

  • приоритеты – положительные целые числа, обычно от 1 до 20
  • приоритет по умолчанию (т.е. приоритет, назначенный, когда значение приоритета не задается вручную) равен 10
  • теоретически нет верхнего предела для значения приоритета, но реалистичный верхний предел составляет 100

Функция с приоритетом 11 будет выполняться после функции с приоритетом 10; а функция с приоритетом 9 будет выполняться перед функцией с приоритетом 10.

Второй способ определения порядка следования функций обратного вызова – это просто порядок, в котором она была зарегистрирована в пределах одного и того же значения приоритета. Таким образом, если две функции обратного вызова зарегистрированы для одного и того же хука с одинаковым приоритетом, они будут запущены в том порядке, в котором они были зарегистрированы.

Например, все следующие функции обратного вызова зарегистрированы для хука init, но с разными приоритетами:

add_action('init', 'wporg_callback_run_me_late', 11);
add_action('init', 'wporg_callback_run_me_normal');
add_action('init', 'wporg_callback_run_me_early', 9);
add_action('init', 'wporg_callback_run_me_later', 11);

В примере выше:

  • Первой выполняемой функцией будет функция wporg_call_backrun_me_early(), так как она имеет ручной приоритет 9
  • Далее, wporg_callback_run_me_normal(), потому что она не имеет установленного приоритета, а значит, ее приоритет 10
  • Далее выполняется функция wporg_callback_run_me_late(), поскольку ее ручной приоритет 11
  • Наконец, запущена функция wporg_callback_run_me_later(): она также имеет приоритет 11, но была подключена после wporg_callback_run_me_late().

Количество аргументов

Иногда, желательно для функции обратного вызова (callback-функции) иметь возможность принимать какие-то дополнительные данные, связанные с действием (action), которое подключается (цепляется хуком).

Например, когда WordPress сохраняет запись и запускает хук save_post, он передаёт в функцию обратного вызова два параметра: идентификатор сохраняемой записи и сам объект записи:

do_action( 'save_post', $post->ID, $post );

Когда функция обратного вызова зарегистрирована для хука save_post, то она может указать, что хочет получить эти два параметра. Она делает это, сказав add_action ожидать их, поместив 2 в качестве четвертого аргумента:

add_action('save_post', 'wporg_custom', 10, 2);

Чтобы на самом деле получить эти параметры в вашей функции обратного вызова, измените параметры, которые будет принимать ваша функция обратного вызова:

function wporg_custom( $post_id, $post ) {
    // сделать что-то
}
Хорошая практика – давать параметрам функции обратного вызова то же имя, что и переданным параметрам, или как можно более близкое.

4.2 Фильтры (Filters)

Фильтры – один из двух типов Хуков (hooks).

Они дают возможность функциям изменять данные других функций. Они являются аналогом действий (Action’ов).

В отличие от Actions, фильтры предназначены для изолированной работы и никогда не должны иметь побочных эффектов, таких как воздействие на глобальные переменные и вывод.

Добавить фильтр

Процесс добавления фильтра включает в себя два этапа.

Во-первых, вам нужно создать функцию обратного вызова, которая будет вызываться при запуске фильтра. Во-вторых, вам нужно добавить функцию Callback (Обратный вызов) на хук, который будет выполнять вызов этой функции.

Вы будете использовать функцию add_filter(), передавая как минимум два параметра, string $tag, вызываемую $function_to_add.

Пример ниже будет запущен при выполнении the_title.

<?php
function wporg_filter_title($title)
{
    return 'The ' . $title . ' was filtered';
}
add_filter('the_title', 'wporg_filter_title');

Допустим, у нас есть заголовок публикации “Learning WordPress”, в приведенном выше примере он будет изменен на “The Learning WordPress was filtered”.

Список доступных хуков можно найти в главе “Хуки”.

По мере того, как вы приобретаете больше опыта, просматривая исходный код ядра WordPress, вы сможете найти наиболее подходящий хук.

Дополнительные параметры

add_filter() может принимать два дополнительных параметра, int $priority для приоритета, отдаваемого функции обратного вызова, и int $accepted_args для количества аргументов, которые будут переданы функции обратного вызова.

Подробное объяснение этих параметров можно найти в главе о действиях.

Пример

Добавление класса CSS к тегу при выполнении определенного условия:

<?php
function wporg_css_body_class($classes)
{
    if (!is_admin()) {
        $classes[] = 'wporg-is-awesome';
    }
    return $classes;
}
add_filter('body_class', 'wporg_css_body_class');

4.3 Пользовательские хуки

Важной, но часто игнорируемой практикой является использование пользовательских хуков в вашем плагине, чтобы другие разработчики могли расширять и изменять его.

Пользовательские хуки создаются и вызываются так же, как и хуки ядра WordPress.

Создание хука

Для создания пользовательского хука используйте do_action() для экшенов и apply_filters() для фильтров.

Мы рекомендуем использовать apply_filters() для любого текста, который выводится в браузер. Особенно на фронтенде. Это облегчает модификацию плагинов в соответствии с потребностями пользователя.

Добавление Callback на Хук

Чтобы добавить функцию обратного вызова (callback-функцию) на пользовательский хук, используйте add_action() для действий и add_filter() для фильтров.

Конфликты с названиями

Так как любой плагин может создать пользовательский хук, важно, установить префикс в имени хука, чтобы избежать конфликтов с другими плагинами.

Например, фильтр с именем email_body будет менее целесообразен, потому что, скорее всего, другой разработчик выберет то же самое имя. Если пользователь установит оба плагина, это может привести к ошибкам, которые трудно отследить.

Название функции wporg_email_body (где wporg_ является уникальным префиксом для вашего плагина) позволит избежать любых конфликтов.

Примеры

Расширяемый экшен: Форма настроек

Если ваш плагин добавляет форму настроек в Административные панели, вы можете использовать экшен, чтобы позволить другим плагинам добавлять свои собственные настройки к нему.

Foo: 
Bar: 
<?php
    do_action( 'wporg_after_settings_page_html' );
}

Теперь другой плагин может зарегистрировать функцию обратного вызова (callback) для хука wporg_after_settings_page_html и вставить новые настройки:

New 1: 
<?php
}
add_action( 'wporg_after_settings_page_html', 'myprefix_add_settings' );

Расширяемый фильтр: Пользовательский тип записи

В данном примере, при регистрации нового типа записи, параметры, определяющие ее, передаются через фильтр, поэтому другой плагин может изменить их до того, как будет создан тип записи.

<?php
function wporg_create_post_type()
{
    $post_type_params = [/* ... */];
 
    register_post_type(
        'post_type_slug',
        apply_filters( 'wporg_post_type_params', $post_type_params )
    );
}

Теперь другой плагин может зарегистрировать функцию обратного вызова для хука wporg_post_type_params и изменить параметры типа записи:

<?php 
function myprefix_change_post_type_params( $post_type_params ) {             
    $post_type_params['hierarchical'] = true;
    return $post_type_params;
} 
add_filter( 'wporg_post_type_params', 'myprefix_change_post_type_params' );

Дополнительные ресурсы

4.4 Продвинутые возможности

Удаление действий и фильтров

Иногда вы хотите удалить функцию обратного вызова из хука, который зарегистрирован другим плагином, темой или даже ядром WordPress.

Чтобы удалить функцию обратного вызова с хука, необходимо вызвать функцию remove_action() или remove_filter(), в зависимости от того, была ли функция обратного вызова добавлена как Action или как Filter.

Параметры, передаваемые для функции remove_action() / remove_filter(), должны быть идентичны параметрам, передаваемым для функции add_action() / add_filter(), которая ее зарегистрировала.

Для успешного удаления функции обратного вызова необходимо выполнить ее удаление после регистрации функции обратного вызова. Важен порядок выполнения.

Пример

Допустим, мы хотим улучшить производительность большой темы, удалив ненужную функциональность.

Проанализируем код темы, заглянув в function.php.

<?php
function my_theme_setup_slider()
{
    // ...
}
add_action('template_redirect', 'my_theme_setup_slider', 9);

Функция my_theme_setup_slider добавляет слайдер, который нам не нужен, который, вероятно, загружает огромный CSS-файл с последующим инициализационным файлом JavaScript, использующим пользовательскую написанную библиотеку размером 1MB. Мы можем от этого избавиться.

Так как мы хотим зацепиться (хук) в WordPress после регистрации функции обратного вызова my_theme_setup_slider (выполняется functions.php), наш лучший вариант – хук after_setup_theme.

<?php
function wporg_disable_slider()
{
    // make sure all parameters match the add_action() call exactly
    remove_action('template_redirect', 'my_theme_setup_slider', 9);
}
// make sure we call remove_action() after add_action() has been called
add_action('after_setup_theme', 'wporg_disable_slider');

Удаление всех Callback-функций

Вы также можете удалить все функции обратного вызова, связанные с хуком, с помощью функций remove_all_actions() / remove_all_filters().

Определение текущего хука

Иногда вы хотите запустить Экшен или Фильтр на нескольких хуках, но при этом поведение должно отличается в зависимости от того, какой из них вызывается в данный момент.

Вы можете использовать current_action() / current_filter() для определения текущего экшена / фильтра.

<?php
function wporg_modify_content($content)
{
    switch (current_filter()) {
        case 'the_content':
            // do something
            break;
        case 'the_excerpt':
            // do something
            break;
    }
    return $content;
}
add_filter('the_content', 'wporg_modify_content');
add_filter('the_excerpt', 'wporg_modify_content');

Проверка, сколько раз был запущен хук

Некоторые хуки вызываются несколько раз в процессе выполнения, но вы можете захотеть, чтобы ваша функция обратного вызова запускалась только один раз.

В этой ситуации вы можете проверить, сколько раз хук выполнялся с помощью функции did_action().

<?php
function wporg_custom()
{
    if (did_action('save_post') !== 1) {
        return;
    }
    // ...
}
add_action('save_post', 'wporg_custom');

Отладка хуком “all”

Если вы хотите, чтобы функция обратного вызова срабатывала на каждом хуке, вы можете зарегистрировать ее на всех хуках. Иногда это полезно при отладке, при определении, когда конкретное событие происходит или когда страница ломается.

<?php
function wporg_debug()
{
    echo '<p>' . current_action() . '</p>';
}
add_action('all', 'wporg_debug');

5. Конфиденциальность

Вы пишете плагин, который обрабатывает личные данные – такие вещи, как имена, адреса и другие вещи, которые могут быть использованы для идентификации личности? Вы захотите позаботиться об этих данных и защитить конфиденциальность ваших пользователей и посетителей.

Что такое Конфиденциальность?

WordPress.org сделал несколько усовершенствований, опередив европейское Общее Положение о Защите Данных (GDPR, General Data Protection Regulation).

После начала этой работы мы сделали конфиденциальность постоянным фокусом в развитии основных направлений, что позволит нам продолжать вносить усовершенствования в области конфиденциальности и защиты данных вне рамок конкретного законодательства.

Но какие вопросы могут подпадать под определение “конфиденциальности”, и как мы это определяем? Несмотря на то, что требования к конфиденциальности сильно различаются в разных странах, культурах и правовых системах, существует несколько общих принципов, применимых в любой ситуации:

  • Согласие и выбор: предоставление пользователям (и посетителям сайта) выбора и вариантов использования их данных, а также требование ясного, конкретного и информированного согласия;
  • Законность и спецификация цели: собирать и использовать персональные данные только в целях, для которых они предназначены, и для которых пользователь был заранее проинформирован;
  • Ограничение по сбору: собирайте только те данные пользователя, которые необходимы; не делайте дополнительных копий данных и не комбинируйте свои данные с данными из других плагинов, если вы можете этого избежать.
  • Минимизация данных: ограничить обработку данных, а также количество людей, имеющих к ним доступ, до минимума и людей, которые в ней нуждаются;
  • Ограничение использования, хранения и раскрытия: удаляйте данные, которые больше не нужны, как при активном использовании, так и в архивах, как получателем, так и любыми третьими лицами;
  • Точность и качество: убедитесь, что собранные и используемые данные являются правильными, релевантными и актуальными, особенно если неточные или недостоверные данные могут отрицательно повлиять на пользователя;
  • Открытость, прозрачность и уведомление: информируйте пользователей о том, как их данные собираются, используются и передаются, а также о любых правах, которые они имеют в отношении такого использования;
  • Индивидуальное участие и доступ: предоставить пользователям возможность доступа или скачивания своих данных;
  • Подотчетность: документирование использования данных, защита их при передаче и использовании третьими лицами, а также предотвращение неправомерного использования и нарушений в максимально возможной степени;
  • Информационная безопасность: защита данных путем принятия соответствующих технических мер и мер безопасности;
  • Соблюдение конфиденциальности: обеспечение соответствия работы правилам конфиденциальности того места, где она будет использоваться для сбора и обработки данных людей.

(Источник: ISO 29100/Privacy Framework standard)

Хотя не все из этих принципов будут применимы во всех ситуациях и областях применения, их использование в процессе разработки может помочь обеспечить доверие пользователей.

Конфиденциальность в дизайне

Многие из этих принципов соблюдаются в рамках концепции “Конфиденциальность в дизайне”, которая гласит следующее:

  • Конфиденциальность должна быть проактивной, а не реактивной, и должна предвосхищать вопросы конфиденциальности до того, как они дойдут до пользователя. Конфиденциальность также должна быть превентивной, а не корректирующей.
  • Конфиденциальность должна быть настройкой по умолчанию. Пользователь не должен предпринимать никаких действий для обеспечения своей конфиденциальности, и согласие на обмен данными не должно приниматься во внимание.
  • Конфиденциальность должна быть встроена в дизайн как основная функция, а не как дополнение.
  • Конфиденциальность должна быть положительной составляющей: не должно быть никаких компромиссов между конфиденциальностью и безопасностью, конфиденциальностью и надежностью, или конфиденциальностью и предоставлением услуг.
  • Конфиденциальность должна обеспечивать защиту в течение всего жизненного цикла за счет минимизации данных, минимального сохранения данных и регулярного удаления данных, в которых больше нет необходимости.
  • Стандарты конфиденциальности, используемые в Вашем плагине (и сервисе, если применимо) должны быть видимыми, прозрачными, открытыми, документированными и независимо проверяемыми.
  • Конфиденциальность должна быть ориентирована на пользователя. Людям должны быть предоставлены такие опции, как детальный выбор конфиденциальности, максимальная степень конфиденциальности по умолчанию, подробные уведомления о конфиденциальности, удобные для пользователя опции и четкое уведомление об изменениях.

Размышления для вашего плагина

Чтобы помочь вашему плагину быть готовым, мы рекомендуем просмотреть следующий список вопросов для каждого плагина, который вы делаете:

1. Как Ваш плагин обрабатывает персональные данные? Используйте wp_add_privacy_policy_content(ссылка), чтобы раскрыть вашим пользователям следующее:

  • Предоставляет ли плагин личные данные третьим лицам (например, внешним API / серверам). Если да, какие данные он передает третьим сторонам и имеет ли они опубликованную политику конфиденциальности, на которую вы можете указать ссылку?
  • Собирает ли плагин личные данные? Если да, то какие данные и где они хранятся? Подумайте о таких местах, как пользовательские данные/мета, опции, почтовые мета-данные, пользовательские таблицы, файлы и т.д.
  • Использует ли плагин личные данные, собранные другими? Если да, то какие данные? Передает ли плагин персональные данные SDK? Что SDK делает с данными?
  • Плагин собирает данные телеметрии, прямо или косвенно? Например, загрузка изображения из стороннего источника при каждой установке может косвенно регистрировать и отслеживать данные об использовании всех установок вашего плагина.
  • Вызывает ли плагин Javascript, отслеживающие пиксели или встраиваемые iframe от третьей стороны (сторонние JS, отслеживающие пиксели и iframe могут собирать данные/действия посетителей, оставлять куки и т.д.)?
  • Хранит ли плагин что-то в браузере? Если да, то где и что? Подумайте о таких объектах, как файлы cookie, локальное хранилище и т.д.

2. Если Ваш плагин собирает личные данные…

  • Обеспечивает ли он экспорт персональных данных?
  • Предоставляет ли он обратный вызов для удаления личных данных?
  • По каким причинам (если таковые имеются) плагин отказывается удалять личные данные? (например, еще не выполненный заказ и т.д.) – эти данные также должны быть раскрыты.

3. Использует ли плагин протоколирование ошибок? Это позволяет избежать регистрации личных данных, если это возможно? Можете ли вы использовать, например, wp_privacy_anonymize_data, чтобы минимизировать зарегистрированные личные данные? Как долго хранятся записи в журнале? Кто имеет к ним доступ?

4. В wp-admin, какая роль/возможности необходимы для доступа/просмотра персональных данных? Достаточны ли они?

5. Какие персональные данные отображаются на фронтенде сайта с помощью плагина? Появляются ли они для пользователей, вошедших и вышедших из системы? Должны ли?

6. Какие персональные данные раскрываются плагином в конечных точках REST API? Появляются ли они для зарегистрированных и вышедших из системы пользователей? Какие роли/возможности необходимы, чтобы их увидеть? Являются ли они подходящими?

7. Правильно ли плагин удаляет/очищает данные, включая, в частности, личные данные:

  • Во время деинсталляции плагина?
  • Когда связанный элемент удаляется (например, из мета-данных записи или любых реферальных строк в другой таблице)?
  • Когда пользователь удаляется (например, из любой пользовательской строки в таблице)?

8. Предоставляет ли плагин элементы управления для уменьшения количества необходимых личных данных?

9. Разделяет ли плагин личные данные с SDK или API только тогда, когда SDK или API требует этого, или плагин также обменивается личными данными, которые являются необязательными?

10. Меняется ли количество личных данных, собираемых или передаваемых этим плагином, при установке некоторых других плагинов?

Внешние ресурсы

5.1 Содержание Политики Конфиденциальности

Предлагаемый текст для политики конфиденциальности сайта

Каждый модуль, который собирает, использует или хранит пользовательские данные, или передает их внешнему источнику или третьей стороне, должен добавлять раздел предлагаемого текста в почтовый ящик политики конфиденциальности.

Это лучше всего делать с помощью wp_add_privacy_policy_content( $plugin_name, $policy_text ). Это позволит администраторам сайта включить эту информацию в политику конфиденциальности своего сайта.

Чтобы сделать это проще для пользователей, текст должен отвечать на вопросы, предусмотренные политикой конфиденциальности по умолчанию:

  • Какие личные данные мы собираем и почему мы их собираем
    – Самостоятельно вводить информацию вручную
    – WP: Контактные формы
    – WP: Комментарии
    – WP: Куки-файлы
    – WP: Встраивания третьей стороны
    – Аналитика
  • С кем мы делимся вашими данными
  • Как долго мы храним ваши данные
  • Какие у вас есть права на ваши данные
  • Куда мы отправляем ваши данные
  • Ваши контактные данные
  • Как мы защищаем ваши данные
  • Какие процедуры по нарушению данных у нас есть
  • От каких третьих лиц мы получаем данные
  • Какие автоматические решения и/или профилирование мы делаем с данными пользователя
  • Любые требования по раскрытию информации, регулирующие отрасль

Хотя не все из этих вопросов применимы ко всем плагинам, мы рекомендуем обратить внимание на разделы об обмене данными.

Пример кода

Рекомендуется вызывать wp_add_privacy_policy_content во время действия (экшена) admin_init. Вызов его вне хука действия может привести к проблемам, подробности см. в тикете #44142.
Дополнительная информация может быть предоставлена с помощью специального CSS-класса .privacy-policy-tutorial. Любое содержимое, содержащееся в элементах HTML, к которым применен этот класс CSS, будет исключено из буфера обмена при копировании содержимого раздела.
function my_example_plugin_add_privacy_policy_content() {
    if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
        return;
    }
    $content = '<p class="privacy-policy-tutorial">' . __( 'Some introductory content for the suggested text.', 'my_plugin_textdomain' ) . '</p>'
             . '<strong class="privacy-policy-tutorial">' . __( 'Suggested Text:', 'my_plugin_textdomain' ) . '</strong> '
             . sprintf(
                    __( 'When you leave a comment on this site, we send your name, email address, IP address and comment text to example.com. Example.com does not retain your personal data. The example.com privacy policy is <a href="%s" target="_blank">here</a>.', 'my_plugin_textdomain' ),
                  'https://example.com/privacy-policy'
                );
    wp_add_privacy_policy_content( 'Example Plugin', wp_kses_post( wpautop( $content, false ) ) );
}
 
add_action( 'admin_init', 'my_example_plugin_add_privacy_policy_content' );

5.2 Добавление Экспортера Личных Данных в ваш плагин

В WordPress 4.9.6 были добавлены новые инструменты, облегчающие соблюдение таких законов, как Общее Положение Европейского Союза о Защите Данных, или сокращенно GDPR.

Среди инструментов, добавленных в WordPress 4.9.6, появился инструмент Экспорт Персональных Данных (Personal Data Export), который поддерживает экспорт всех персональных данных для данного пользователя в ZIP-файл.

В дополнение к персональным данным, хранящимся в таких объектах, как комментарии WordPress, плагины могут также подключаться к функции экспортера, чтобы экспортировать персональные данные, которые они собирают, будь то в нечто вроде postmeta или даже в совершенно новый Пользовательский Тип Данных (Custom Post Type, CPT).

“Ключом” для всех экспортируемых данных является адрес электронной почты пользователя – он был выбран потому, что поддерживает экспорт персональных данных как для полноправных зарегистрированных пользователей, так и для незарегистрированных (например, как комментатор, вышедший из системы).

Однако, поскольку сборка экспорта персональных данных может быть интенсивным процессом и, вероятно, будет содержать конфиденциальные данные, мы не хотим просто генерировать их и отправлять по электронной почте запрашивающей стороне без подтверждения запроса, поэтому пользовательский интерфейс, с которым сталкивается администратор, запускает все запросы.

Администратор вводит имя пользователя или адрес электронной почты, который делает запрос, а затем отправляет ссылку для подтверждения своего запроса.

После подтверждения запроса администратор может сгенерировать и скачать или отправить непосредственно по электронной почте ZIP-файл экспорта личных данных для пользователя, или сделать экспорт в любом случае, если возникает необходимость. Внутри ZIP-файла, который получает пользователь, он найдет “мини-сайт” с индексной HTML-страницей, содержащей его персональные данные, организованные в группы (например, группа для комментариев и т.д.).

Независимо от того, загружает ли администратор ZIP-файл экспорта персональных данных или отправляет его непосредственно запрашивающему лицу, способ сборки экспорта персональных данных идентичен – и полагается на перехват “экспортера” обратных вызовов для выполнения грязной работы по сбору всех данных для экспорта.

Когда администратор нажимает на ссылку загрузки или электронной почты, начинается цикл AJAX, который повторяется над всеми экспортерами, зарегистрированными в системе, по одному. В дополнение к экспортерам, встроенным в ядро, плагины могут регистрировать свои собственные обратные вызовы экспортеров.

Интерфейс обратного вызова экспортера разработан таким образом, чтобы быть как можно более простым. Обратный вызов экспортера получает адрес электронной почты, с которым мы работаем, а также параметр страницы. Параметр страницы (который начинается с 1) используется для того, чтобы избежать подключаемых модулей, которые могут вызвать таймауты при попытке экспортировать все личные данные, которые они собрали за один раз. Хорошо себя зарекомендовавший плагин ограничит количество данных, которые он пытается стереть на одной странице (например, 100 сообщений, 200 комментариев и т.д.).

Обратный вызов экспортера отвечает всеми данными, которые у него есть для этого адреса электронной почты и страницы, и независимо от того, сделано это или нет. Если обратный вызов экспортера сообщит о том, что это не было сделано, то он будет вызван снова (в отдельном запросе) с параметром страницы, увеличенным на 1. Ожидается, что обратный вызов экспортера вернет массив элементов для экспорта. Каждый элемент содержит идентификатор группы, для группы которой элемент является частью (например, комментарии, записи, заказы и т.д.), необязательной групповой меткой (translated), идентификатором элемента (например, comment-133), а затем массивом имен, пар значений, содержащим данные, которые должны быть экспортированы для этого элемента.

Примечательно, что значением может быть путь к медиафайлу, в этом случае ссылка на медиафайл будет добавлена в индексную HTML-страницу при экспорте.

Когда все экспортеры были вызваны для завершения, WordPress сначала собирает HTML-документ “index”, который служит сердцем экспортного отчета. Если плагин сообщает дополнительные данные для элемента, который WordPress или другой плагин уже добавил, то все данные для этого элемента будут представлены вместе.

Экспорт кэшируется на сервере в течение 3 дней, а затем удаляется.

Плагин может зарегистрировать одного или нескольких экспортеров, но большинству плагинов понадобится только один. Поработаем над гипотетическим плагином, который добавляет данные о местоположении комментатора к комментариям.

Во-первых, предположим, что плагин использовал add_comment_meta для добавления данных о местоположении, используя meta_key из latitude (широты) и longitude (долготы).

Первое, что нужно сделать плагину, это создать функцию экспортера, которая будет принимать адрес электронной почты и страницу, например:

function my_plugin_exporter( $email_address, $page = 1 ) {
  $number = 500; // Limit us to avoid timing out
  $page = (int) $page;
 
  $export_items = array();
 
  $comments = get_comments(
    array(
      'author_email' => $email_address,
      'number' => $number,
      'paged' => $page,
      'order_by' => 'comment_ID',
      'order' => 'ASC',
    )
  );
 
  foreach ( (array) $comments as $comment ) {
    $latitude = get_comment_meta( $comment->comment_ID, 'latitude', true );
    $longitude = get_comment_meta( $comment->comment_ID, 'longitude', true );
 
    // Only add location data to the export if it is not empty
    if ( ! empty( $latitude ) ) {
      // Most item IDs should look like postType-postID
      // If you don't have a post, comment or other ID to work with,
      // use a unique value to avoid having this item's export
      // combined in the final report with other items of the same id
      $item_id = "comment-{$comment->comment_ID}";
 
      // Core group IDs include 'comments', 'posts', etc.
      // But you can add your own group IDs as needed
      $group_id = 'comments';
 
      // Optional group label. Core provides these for core groups.
      // If you define your own group, the first exporter to
      // include a label will be used as the group label in the
      // final exported report
      $group_label = __( 'Comments' );
 
      // Plugins can add as many items in the item data array as they want
      $data = array(
        array(
          'name' => __( 'Commenter Latitude' ),
          'value' => $latitude
        ),
        array(
          'name' => __( 'Commenter Longitude' ),
          'value' => $longitude
        )
      );
 
      $export_items[] = array(
        'group_id' => $group_id,
        'group_label' => $group_label,
        'item_id' => $item_id,
        'data' => $data,
      );
    }
  }
 
  // Tell core if we have more comments to work on still
  $done = count( $comments ) < $number;
  return array(
    'data' => $export_items,
    'done' => $done,
  );
}

Следующее, что должен сделать плагин, это зарегистрировать обратный вызов, фильтруя массив экспортеров с помощью фильтра wp_privacy_personal_data_exporters.

При регистрации вы указываете дружественное имя для экспорта (для помощи в отладке – это дружественное имя в данный момент никому не показывается) и обратного вызова, например.

function register_my_plugin_exporter( $exporters ) {
  $exporters['my-plugin-slug'] = array(
    'exporter_friendly_name' => __( 'Comment Location Plugin' ),
    'callback' => 'my_plugin_exporter',
  );
  return $exporters;
}
 
add_filter(
  'wp_privacy_personal_data_exporters',
  'register_my_plugin_exporter',
  10
);

И это все! Теперь ваш плагин будет предоставлять данные для экспорта!

5.3 Добавление Стирателя личных данных к вашему плагину

В WordPress 4.9.6 были добавлены новые инструменты, облегчающие соблюдение таких законов, как Общее Положение Европейского Союза о Защите Данных, или сокращенно GDPR.

Среди инструментов, добавленных в WordPress 4.9.6, появился инструмент удаления личных данных (Personal Data Removal или Personal Data Eraser), который поддерживает удаление/анонимизацию личных данных для конкретного пользователя.

Он НЕ удаляет зарегистрированные учетные записи пользователей – это все еще отдельный шаг, который администратор может выбрать, делать это или нет.

В дополнение к личным данным, хранящимся в таких объектах, как комментарии WordPress, плагины могут также подключаться к стирателю, чтобы стереть личные данные, которые они собирают, будь то postmeta или даже в совершенно новых Пользовательских типах записей (Custom Post Type, CPT).

Как и в случае с экспортерами, “ключом” для всех ластиков является адрес электронной почты пользователя – он был выбран потому, что поддерживает стирание личных данных как для полноценных зарегистрированных пользователей, так и для незарегистрированных (например, как для комментатора, вышедшего из системы).

Однако, поскольку выполнение стирания личных данных является разрушительным процессом, мы не хотим просто сделать это без подтверждения запроса, поэтому пользовательский интерфейс, обращенный к администратору, начинает все запросы с того, что администратор вводит имя пользователя или адрес электронной почты, делая запрос, а затем посылает ссылку, нажав на которую, пользователь может подтвердить свой запрос. Как только запрос был подтвержден, администратор может начать стирание личных данных для пользователя, или заставить его, если необходимость возникает.

Способ стирания экспортируемых персональных данных аналогичен способу экспортеров персональных данных – и полагается на перехват “стирающих” обратных вызовов для выполнения грязной работы по стиранию данных. Когда администратор нажимает на ссылку удаления персональных данных, начинается цикл AJAX, который итературизирует все стиратели, зарегистрированные в системе, по очереди. В дополнение к стирателям, встроенным в ядро, плагины могут регистрировать свои собственные обратные вызовы стирателей.

Интерфейс обратного вызова стирателя разработан таким образом, чтобы быть как можно более простым. Обратный вызов стирателя получает адрес электронной почты, с которым мы работаем, а также параметр страницы. Параметр страницы (который начинается с 1) используется для того, чтобы избежать использования плагинов, которые могут вызывать таймауты, пытаясь стереть все личные данные, которые они собрали за один раз. Хорошо себя зарекомендовавший плагин ограничит количество данных, которые он пытается стереть на одной странице (например, 100 сообщений, 200 комментариев и т.д.).

Обратный вызов стирателя отвечает, были ли удалены элементы, содержащие личные данные, были ли сохранены какие-либо элементы, содержащие личные данные, массив сообщений для представления администратору (объясняя, почему элементы, которые были сохранены, были сохранены), и было ли это сделано или нет. Если обратный вызов стирателя сообщает, что это не было сделано, он будет вызван снова (в отдельном запросе) с параметром страницы, увеличенным на 1.

Когда все экспортеры были вызваны для завершения, пользовательский интерфейс администратора обновляется, чтобы показать, были ли стерты все найденные персональные данные, а также любые сообщения, объясняющие, почему персональные данные были сохранены.

Поработаем над гипотетическим плагином, который добавляет к комментариям данные о местоположении комментатора. Предположим, что плагин использовал add_comment_meta для добавления данных о местоположении, используя meta_keys из latitude (широта) и longitude (долгота).

Первое, что нужно сделать плагину, это создать функцию стирателя, которая будет принимать адрес электронной почты и страницу, например:

function my_plugin_eraser( $email_address, $page = 1 ) {
  $number = 500; // Limit us to avoid timing out
  $page = (int) $page;
 
  $comments = get_comments(
    array(
      'author_email' => $email_address,
      'number'       => $number,
      'paged'        => $page,
      'order_by'     => 'comment_ID',
      'order'        => 'ASC',
      )
  );
 
  $items_removed = false;
 
  foreach ( (array) $comments as $comment ) {
    $latitude  = get_comment_meta( $comment->comment_ID, 'latitude', true );
    $longitude = get_comment_meta( $comment->comment_ID, 'longitude', true );
 
    if ( ! empty( $latitude ) ) {
      delete_comment_meta( $comment->comment_ID, 'latitude' );
      $items_removed = true;
    }
 
    if ( ! empty( $longitude ) ) {
      delete_comment_meta( $comment->comment_ID, 'longitude' );
            $items_removed = true;
    }
  }
 
  // Tell core if we have more comments to work on still
  $done = count( $comments ) < $number; return array( 'items_removed' => $items_removed,
    'items_retained' => false, // always false in this example
    'messages' => array(), // no messages in this example
    'done' => $done,
  );
}

Следующее, что должен сделать плагин, это зарегистрировать обратный вызов, отфильтровав массив стирателей с помощью фильтра wp_privacy_personal_data_erasers.

При регистрации вы предоставляете дружественное имя для стирателя (для помощи в отладке – это дружественное имя в данный момент никому не показывается) и обратного вызова, например:

function register_my_plugin_eraser( $erasers ) {
  $erasers['my-plugin-slug'] = array(
    'eraser_friendly_name' => __( 'Comment Location Plugin' ),
    'callback'             => 'my_plugin_eraser',
    );
  return $erasers;
}
 
add_filter(
  'wp_privacy_personal_data_erasers',
  'register_my_plugin_eraser',
  10
);

И это все! Ваш плагин теперь очистит свои личные данные!

5.4 Опции, связанные с конфиденциальностью, Хуки и Возможности

Инструменты для обеспечения конфиденциальности были первоначально представлены в WordPress 4.9.6.

Эти инструменты предназначены для того, чтобы позволить (и поощрить) разработчикам использовать их в рамках программ Экспорта Персональных Данных (Privacy Exporter), Стирания Персональных Данных (Privacy Eraser) и Руководства по политике конфиденциальности.

С тех пор было введено несколько новых хуков для расширения имеющихся возможностей. Эти хуки позволяют разработчикам включать дополнительные персональные данные в запросы на экспорт и стирание, а также знакомить с предлагаемым контентом для руководства по политике конфиденциальности.

Наряду с возможностью управления этими инструментами, появилось несколько новых фильтров для использования с запросами и подтверждениями электронной почты, что позволяет осуществлять более детальный контроль над этими уведомлениями.

Опции

wp_page_for_privacy_policy – содержит ID страницы конфиденциальности сайта

Действия (Экшены)

user_request_action_confirmed – срабатывает, когда пользователь подтверждает запрос конфиденциальности

wp_privacy_delete_old_export_files – запланированное действие, используемое для удаления старого экспорта из папки экспорта личных данных

wp_privacy_personal_data_erased – срабатывает после завершения последней страницы последнего стирателя

wp_privacy_personal_data_export_file – используется для создания файла экспорта личных данных в рамках процесса экспорта

wp_privacy_personal_data_export_file_created – срабатывает после создания файла экспорта персональных данных

Фильтры

privacy_policy_url – фильтрует URL страницы политики конфиденциальности

the_privacy_policy_link – фильтрует ссылку на страницу политики конфиденциальности HTML

wp_get_default_privacy_policy_content – фильтрует контент по умолчанию, предлагаемый для включения в руководстве по политике конфиденциальности

user_request_action_confirmed_message – позволяет изменять отображаемое пользователю сообщение с подтверждением действия

user_request_action_description – фильтрует описание действий пользователя

user_request_action_email_content – фильтрует текст сообщения электронной почты, отправленного при попытке действия учетной записи

user_request_action_email_headers – фильтрует заголовки письма, отправляемого при попытке действия учетной записи

user_request_action_email_subject – фильтрует тему письма, отправляемого при попытке действия учетной записи

user_request_confirmed_email_content – фильтрует тело письма с запросом подтверждения пользователя

user_request_confirmed_email_headers – фильтрует заголовки письма подтверждения запроса пользователя

user_request_confirmed_email_subject – фильтрует тему письма с запросом подтверждения пользователя

user_request_confirmed_email_to – фильтрует получателя уведомления о подтверждении запроса данных

user_request_key_expiration – фильтрует время истечения ключей подтверждения для пользовательских запросов

wp_privacy_additional_user_profile_data – фильтр для расширения данных профиля пользователя для экспортера конфиденциальности

wp_privacy_export_expiration – управляет разрешением получения старых файлов экспорта, по умолчанию – 3 дня

wp_privacy_personal_data_email_content – позволяет изменить сообщение электронной почты, отправляемое пользователям с помощью ссылки на файл экспорта их личных данных

wp_privacy_personal_data_email_headers – фильтрует заголовки письма, отправленного с файлом экспорта личных данных

wp_privacy_personal_data_email_subject – фильтрует тему письма, отправленного после завершения запроса на экспорт

wp_privacy_personal_data_email_to – фильтрует получателя уведомления по электронной почте экспорта личных данных

wp_privacy_personal_data_email_to следует использовать с большой осторожностью, чтобы не отправлять ссылку на экспорт данных на неправильный адрес (а) электронной почты получателя.

wp_privacy_personal_data_erasers – поддерживает регистрацию стирателей персональных данных ядра и плагина

wp_privacy_personal_data_erasure_page – фильтрует страницу данных стирателя личных данных. Позволяет использовать ответ на стирание адресатами в дополнение к Ajax.

wp_privacy_personal_data_exporters – поддерживает регистрацию экспортеров персональных данных ядра и плагинов

wp_privacy_personal_data_export_page – фильтрует страницу данных экспортера персональных данных. Используется для создания отчета об экспорте. Позволяет использовать экспортный ответ для пунктов назначения в дополнение к Ajax.

wp_privacy_anonymize_data – фильтрует анонимные данные для каждого типа

wp_privacy_exports_dir – фильтрует каталог, в котором хранятся файлы экспорта личных данных

wp_privacy_exports_url – фильтрует URL-адрес каталога, в котором хранятся файлы экспорта личных данных

user_confirmed_action_email_content – фильтрует текст письма с подтверждением запроса пользователя. Электронное письмо отправляется администратору после подтверждения запроса пользователя.

user_erasure_fulfillment_email_to – фильтрует получателя уведомления о выполнении стирания данных

user_erasure_complete_email_subject – фильтрует тему сообщения электронной почты, отправленного после завершения запроса на удаление

user_confirmed_action_email_content – фильтрует тело уведомления о выполнении стирания данных. Электронное письмо отправляется пользователю, когда администратор выполняет запрос на удаление данных.

user_erasure_complete_email_headers – фильтрует заголовки уведомления о выполнении стирания данных

Возможности

Доступ к инструментам обеспечения конфиденциальности контролируется несколькими новыми возможностями.

По умолчанию эти возможности есть у администраторов (на немультисайтовых установках).

Этими возможностями являются:

erase_others_personal_data – определяет, доступно ли подменю «Удалить личные данные» в разделе «Инструменты»

export_others_personal_data – определяет, доступно ли подменю «Экспорт личных данных» в разделе «Инструменты»

manage_privacy_options – определяет, доступно ли подменю Конфиденциальность в Настройках

6. Меню администрирования

Меню администрирования – это интерфейсы, отображаемые в панели администрирования WordPress. Они позволяют вам добавлять страницы опций для вашего плагина.

Информацию об управлении навигационными меню см. в главе “Навигационные меню” Руководства для разработчиков тем.

Меню верхнего уровня и подменю

Меню верхнего уровня отображается в левой части панели администрирования WordPress. Каждое меню может содержать набор подменю.

При выборе между меню верхнего уровня и подменю тщательно продумайте потребности вашего плагина, а также потребности ваших конечных пользователей.

Мы рекомендуем разработчикам с одной опционной страницей добавить ее в качестве Подменю в одно из существующих меню верхнего уровня; например, “Настройки” (Settings) или “Инструменты” (Tools).

6.1 Меню верхнего уровня

Добавление меню верхнего уровня

Для добавления нового меню верхнего уровня в WordPress Administration используйте функцию add_menu_page().

<?php
add_menu_page(
    string $page_title,
    string $menu_title,
    string $capability,
    string $menu_slug,
    callable $function = '',
    string $icon_url = '',
    int $position = null
);

Пример

Допустим, мы хотим добавить новое меню верхнего уровня под названием “WPOrg”.

Первым шагом будет создание функции, которая будет выводить HTML. В этой функции мы выполним необходимые проверки безопасности и отобразим зарегистрированные нами опции с помощью Settings API (Настройки API).

Мы рекомендуем обернуть ваш HTML с помощью <div> с классом wrap.
<?php
function wporg_options_page_html() {
    ?>
    <div class="wrap">
      <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
      <form action="options.php" method="post">
        <?php
        // output security fields for the registered setting "wporg_options"
        settings_fields( 'wporg_options' );
        // output setting sections and their fields
        // (sections are registered for "wporg", each field is registered to a specific section)
        do_settings_sections( 'wporg' );
        // output save settings button
        submit_button( __( 'Save Settings', 'textdomain' ) );
        ?>
      </form>
    </div>
    <?php
}
?>

Вторым шагом будет регистрация нашего меню WPOrg. Регистрация должна произойти во время перехвата ([erf) действия admin_menu.

<?php
add_action( 'admin_menu', 'wporg_options_page' );
function wporg_options_page() {
    add_menu_page(
        'WPOrg',
        'WPOrg Options',
        'manage_options',
        'wporg',
        'wporg_options_page_html',
        plugin_dir_url(__FILE__) . 'images/icon_wporg.png',
        20
    );
}
?>

Список параметров и то, что каждый из них делает, смотрите в справке add_menu_page().

Использование PHP-файла для HTML

Лучшей практикой для переносимого кода было бы создание функции обратного вызова (Callback), которая требует/включает в себя ваш PHP-файл.

Для полноты и облегчения понимания унаследованного кода мы покажем другой способ: передача пути к PHP-файлу в виде параметра $menu_slug с параметром null $function.

<?php
add_action( 'admin_menu', 'wporg_options_page' );
function wporg_options_page() {
    add_menu_page(
        'WPOrg',
        'WPOrg Options',
        'manage_options',
        plugin_dir_path(__FILE__) . 'admin/view.php',
        null,
        plugin_dir_url(__FILE__) . 'images/icon_wporg.png',
        20
    );
}
?>

Удаление меню верхнего уровня

Чтобы удалить зарегистрированное меню из администрирования WordPress, используйте функцию remove_menu_page().

<?php
remove_menu_page(
    string $menu_slug
);
?>
Удаление меню не помешает пользователям получить к ним прямой доступ. Это никогда не должно использоваться как способ ограничить возможности пользователей.

Пример

Допустим, мы хотим убрать меню “Инструменты” (Tools).

<?php
add_action( 'admin_menu', 'wporg_remove_options_page', 99 );
function wporg_remove_options_page() {
    remove_menu_page( 'tools.php' );
}
?>

Убедитесь, что меню было зарегистрировано с помощью хука admin_menu, прежде чем пытаться удалить, укажите номер с более высоким приоритетом для функции add_action().

Отправка форм

Чтобы обработать отправку форм на страницах параметров, вам понадобятся две вещи:

  1. Используйте URL-адрес страницы в качестве атрибута действия формы.
  2. Добавьте хук со слагом (slug), возвращаемый add_menu_page.
Вам нужно следовать этим шагам только в том случае, если вы вручную создаете формы в бэкэнде. Рекомендуется использовать API Settings.

Атрибут действия формы

Используйте параметр $menu_slug страницы параметров в качестве первого параметра menu_page_url(). Функция автоматически экранирует URL и выводит его по умолчанию, поэтому вы можете напрямую использовать его в теге <form>:

<form action="<?php menu_page_url( 'wporg' ) ?>" method="post">

Обработка формы

$function, указанная вами при добавлении страницы, будет вызываться только тогда, когда пришло время отобразить страницу, что делает ее неуместной, если вам нужно отправить заголовки (например, перенаправления) обратно в браузер.

add_menu_page возвращает $hookname, а WordPress запускает действие “load-$hookname” перед любым выводом HTML. Вы можете использовать это, чтобы назначить функцию, которая может обрабатывать форму.

load-$hookname” будет выполняться каждый раз перед отображением страницы параметров, даже если форма не отправляется.

Учитывая возвращаемый параметр и действие, приведенный выше пример будет выглядеть так:

add_action( 'admin_menu', 'wporg_options_page' );
function wporg_options_page() {
    $hookname = add_menu_page(
        'WPOrg',
        'WPOrg Options',
        'manage_options',
        'wporg',
        'wporg_options_page_html',
        plugin_dir_url(__FILE__) . 'images/icon_wporg.png',
        20
    );
 
    add_action( 'load-' . $hookname, 'wporg_options_page_submit' );
}

Вы можете запрограммировать wporg_options_page_submit в соответствии с вашими потребностями, но имейте в виду, что вы должны вручную выполнить все необходимые проверки, включая:

  1. Отправляется ли форма ('POST' === $_SERVER['REQUEST_METHOD']).
  2. Проверка CSRF
  3. Валидация (Validation)
  4. Очистка (Sanitization)

6.2 Подменю

Добавление подменю

Чтобы добавить новое Подменю в панели администрирования WordPress, используйте функцию add_submenu_page().

add_submenu_page(
    string $parent_slug,
    string $page_title,
    string $menu_title,
    string $capability,
    string $menu_slug,
    callable $function = ''
);

Пример

Допустим, мы хотим добавить подменю «Параметры WPOrg» (WPOrg Options) в меню верхнего уровня «Инструменты» (Tools).

Первым шагом будет создание функции, которая будет выводить HTML. В этой функции мы выполним необходимые проверки безопасности и отобразим зарегистрированные нами опции с помощью Settings API.

Мы рекомендуем обернуть ваш HTML с помощью <div> с классом wrap.
function wporg_options_page_html() {
    // check user capabilities
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form action="options.php" method="post">
            <?php
            // output security fields for the registered setting "wporg_options"
            settings_fields( 'wporg_options' );
            // output setting sections and their fields
            // (sections are registered for "wporg", each field is registered to a specific section)
            do_settings_sections( 'wporg' );
            // output save settings button
            submit_button( __( 'Save Settings', 'textdomain' ) );
            ?>
        </form>
    </div>
    <?php
}

Вторым шагом будет регистрация нашего подменю “WPOrg Options”. Регистрация должна произойти во время перехвата действия admin_menu.

function wporg_options_page()
{
    add_submenu_page(
        'tools.php',
        'WPOrg Options',
        'WPOrg Options',
        'manage_options',
        'wporg',
        'wporg_options_page_html'
    );
}
add_action('admin_menu', 'wporg_options_page');

Список параметров и то, что каждый из них делает, смотрите в справке add_submenu_page().

Предопределённые подменю

Было бы здорово, если бы у нас были вспомогательные функции, определяющие $parent_slug для встроенного меню верхнего уровня WordPress и избавляющие нас от ручного поиска по исходному коду?

Ниже приведен список родительских слагов и их вспомогательных функций:

Удаление подменю

Процесс удаления подменю точно такой же, как и удаление меню верхнего уровня.

Отправка форм

Процесс обработки отправки форм в подменю точно такой же, как и отправка форм в меню верхнего уровня.

add_submenu_page() вместе со всеми функциями для предопределенных подменю (add_dashboard_page, add_posts_page и т.д.) вернет $hookname, которое вы можете использовать в качестве первого параметра add_action для обработки отправки форм на пользовательских страницах:

function wporg_options_page() {
    $hookname = add_submenu_page(
        'tools.php',
        'WPOrg Options',
        'WPOrg Options',
        'manage_options',
        'wporg',
        'wporg_options_page_html'
    );
 
    add_action( 'load-' . $hookname, 'wporg_options_page_html_submit' );
}
 
add_action('admin_menu', 'wporg_options_page');

Как всегда, не забудьте проверить, отправляется ли форма, выполнить проверку CSRF, валидацию и очистку.