
Часть 2. Создание страницы настроек пользовательских сайдбаров
Продолжение статьи как добавить произвольное количество пользовательских сайдбаров (колонок) в WordPress-тему.
В этой части я покажу как можно создать страницу настроек в консоли WordPress и обрабатывать операции над пользовательскими колонками.
Первая честь: Часть 1. Пользовательский сайдбар для WordPress темы
Регистрация страницы настроек
Страницу настроек пользовательских колонок поместим в подменю “Внешний вид” отдельным пунктом. Такое расположение будет наиболее оправдано и предсказуемо, т.к. в этом же подменю редактируется содержимое сайдбаров (колонок).
Для этого нужно воспользоваться функцией add_theme_page()
. Функцию нужно использовать во время хука admin_menu
, который срабатывает перед загрузкой меню администрирования в админке.
Функция add_theme_page()
принимает пять параметров, это:
$page_title
– текст тега<title>
для страницы меню и заголовка страницы$menu_title
– текст пункта меню$capability
– права пользователей, которые могут попасть на эту страницу$menu_slug
– идентификатор этой страницы, должен быть уникальным$function
– функция (или имя функции), которая выведет HTML код этой страницы

function resume_register_custom_columns_submenu() {
add_theme_page(
__( 'Пользовательские колонки', TEXTDOMAIN ),
__( 'Пользовательские колонки', TEXTDOMAIN ),
'manage_options',
RESUME_SLUG . '_custom_columns',
'resume_render_custom_columns_submenu'
);
}
add_action( 'admin_menu', 'resume_register_custom_columns_submenu' );
Формирование содержимого страницы пользовательских сайдбров
Страница “управления” пользовательскими сайдбарами состоит из нескольких вкладок (экранов). Каждая вкладка отвечает за одно или несколько операций, которые можно провести над списком сайдбаров (колонок). Это:
- просмотр списка
- добавление сайдбара (колонки)
- редактирование сайдбара (колонки)
- удаление сайдбара (колонки)
Показ списка (таблицы) и удаление происходит на одной вкладке, эта же вкладка показыватся по умолчанию, когда пользователь только перешел на страницу.
На остальных вкладках находятся формы добавления и редактирования. Если нет добавленных сайдаров, то отображается форма добавления нового сайдбара (колонки).
<?php
function render_custom_columns_submenu() {
// определяем текущую вкладку
$current_tab = ( isset( $_REQUEST[ 'tab' ] ) && in_array( $_REQUEST[ 'tab' ], [ 'table', 'add', 'edit' ] ) ) ? $_REQUEST[ 'tab' ] : 'table';
// получаем список колонок
$register_columns = get_theme_mod( 'register_columns', [] );
// создаём уникальный защитный ключ для обеспечения безопасности
$nonce = wp_create_nonce( plugin_basename( __FILE__ ) );
// получаем url текущей страницы
$page_url = 'themes.php?page=' . THEME_SLUG . '_custom_columns';
// если есть "предупреждения", то віводим их
$page_content = ( isset( $_REQUEST[ 'notice' ] ) ) ? $_REQUEST[ 'notice' ] : '';
// получаем заголовок страницы
$page_title = get_admin_page_title();
// поскульку содержимое страницы нужно передать в переменной - включаем буфер
ob_start();
// выводим контент вкладки добавления нового сайдбара
if ( 'add' == $current_tab || empty( $register_columns ) ) : ?>
<h3><?php _e( 'Добавление', TEXTDOMAIN ); ?></h3>
<form method="post">
<!-- скрытое поле с одноразовым кодом для безопасности -->
<input type="hidden" name="nonce" required="required" value="<?php echo $nonce; ?>">
<!-- идентификатор выполняемого действия (добавление) -->
<input type="hidden" name="action" required="required" value="add">
<p>
<!-- название новой колонки -->
<input type="text" name="new_column[name]" required="required" placeholder="<?php esc_attr_e( 'Название', TEXTDOMAIN ); ?>">
<!-- краткое описание колонки -->
<input type="text" name="new_column[description]" placeholder="<?php esc_attr_e( 'Описание', TEXTDOMAIN ); ?>">
<!-- класс контейнера колонки -->
<input type="text" name="new_column[class]" placeholder="<?php esc_attr_e( 'CSS-класс', RESUME_TEXTDOMAIN ); ?>">
<!-- кнопка отправки формы (добавления колонки) -->
<button class="button button-primary" type="submit"><?php _e( 'Добавить', RESUME_TEXTDOMAIN ); ?></button>
<!-- если ни одного сайдбара ещё не добавлено, то отменять действие добавление нет смысла -->
<?php if ( ! empty( $register_columns ) ) : ?>
<a class="button" href="<?php echo $page_url; ?>"><?php _e( 'Отмена', RESUME_TEXTDOMAIN ); ?></a>
<?php endif; ?>
</p>
</form>
<!-- вкладка редактирование сайдбара -->
<?php elseif ( 'edit' == $current_tab && isset( $_REQUEST[ 'id' ] ) ) : ?>
<h3><?php _e( 'Редактирование', TEXTDOMAIN ); ?></h3>
<?php
// получаем предыдущие значения свойств сайдбара для заполнения полей формы редактирования
// для этого сначала создадим ассоциативный массив с пустыми знаениями
$old_value = [ 'name' => '', 'id' => '', 'description' => '', 'class' => '' ];
// затем найдём в цикле в массиве колонок ту, значения которой будем редактировать
for ( $i = 0; $i < count( $register_columns ); $i++ ) {
if ( $_REQUEST[ 'id' ] == $register_columns[ $i ][ 'id' ] ) {
// после того как колонка найдена - сохраним её свойства и прервём работу цыкла
$old_value = $register_columns[ $i ];
break;
}
}
?>
<form method="post">
<p>
<!-- скрытое поле с кодом безопасности -->
<input type="hidden" name="nonce" required="required" value="<?php echo $nonce; ?>">
<!-- поле с идентификатором выполняемого действия -->
<input type="hidden" name="action" required="required" value="edit">
<!-- идентификатор сайдбара -->
<input type="hidden" name="new_value[id]" required="required" value="<?php echo esc_attr( $old_value[ 'id' ] ); ?>">
<!-- новое название сайдбара -->
<input type="text" name="new_value[name]" required="required" value="<?php echo esc_attr( $old_value[ 'name' ] ); ?>" placeholder="<?php esc_attr_e( 'Название', TEXTDOMAIN ); ?>">
<!-- новое описание сайдбара -->
<input type="text" name="new_value[description]" value="<?php echo esc_attr( $old_value[ 'description' ] ); ?>" placeholder="<?php esc_attr_e( 'Описание', TEXTDOMAIN ); ?>">
<!-- новое значение аттрибута класса -->
<input type="text" name="new_value[class]" value="<?php echo esc_attr( $old_value[ 'class' ] ); ?>" placeholder="<?php esc_attr_e( 'CSS-класс', TEXTDOMAIN ); ?>">
</p>
<p>
<a class="button" href="<?php echo $page_url; ?>"><?php _e( 'Отмена', RESUME_TEXTDOMAIN ); ?></a>
<button class="button button-primary" type="submit"><?php _e( 'Сохранить', RESUME_TEXTDOMAIN ); ?></button>
</p>
</form>
<!--
Вкладка со списокм добавленных сайдбаров. На этой вкладке выведем весь
список (тиблицу) добавленных сайдаров и дополнительные свойства.
-->
<?php else : ?>
<?php
// подключим скрип и стили для модальных окон, они пригодятся для отображения дополнительных свойств
add_thickbox();
// инициализируем переменную для счетчика строк списка-таблицы
$count = 0;
// для удобства добавим кнопку на форму регистрации нового сайдара рядом с заголовком страницы
$page_title = $page_title . ' <a class="button button-primary" href="' . $page_url . '&tab=add">' . __( 'Добавить', TEXTDOMAIN ) . '</a>';
// получаем список всех сайдбаров и виджетов в них
$registered_sidebars = wp_get_sidebars_widgets();
?>
<!-- Выводи шапку таблицы -->
<table class="custom-column-table">
<thead>
<th><?php _e( '№', TEXTDOMAIN ); ?></th>
<th><?php _e( 'Название', TEXTDOMAIN ); ?></th>
<th><?php _e( 'Описание', TEXTDOMAIN ); ?></th>
<th><?php _e( 'CSS-класс', TEXTDOMAIN ); ?></th>
<th><?php _e( 'Страницы', TEXTDOMAIN ); ?></th>
<th><?php _e( 'Виджеты', TEXTDOMAIN ); ?></th>
</thead>
<tbody>
<!-- в цикле выводи строки таблицы со свойствами колонок -->
<?php foreach ( $register_columns as $register_column ) : ?>
<?php
// номер строки увеличиваем на 1
$count = $count + 1;
// получаем список страниц на которых используется текущая колонка
$pages = get_pages( [
'meta_key' => '_custom_columns',
'meta_value' => $register_column[ 'id' ],
'sort_order' => 'ASC',
'sort_column' => 'post_title'
] );
// получам список категорий на страницах которых используется текущая колонка
$categories = get_categories( [
'taxonomy' => 'category',
'orderby' => 'name',
'order' => 'ASC',
'hide_empty' => false,
'fields' => 'all',
'meta_key' => '_custom_columns',
'meta_value' => $register_column[ 'id' ],
] );
// инициализируем переменную в которой будем хранить количество категорий и страниц
// где используется текущая колонка
$usage = 0;
// получим количество виджетов в колонке
$widgets_count = ( array_key_exists( $register_column[ 'id' ], $registered_sidebars ) ) ? count( $registered_sidebars[ $register_column[ 'id' ] ] ) : 0;
// создадим часть url с кодом безопасности и идентификатором колонки эта строка нужна для использования в ссылках для выполнения действий удаления и редактирования колонки
$action_url = $page_url . '&nonce=' . $nonce . '&id=' . $register_column[ 'id' ];
?>
<!-- выводим строку -->
<tr>
<td class="count"><?php echo $count; ?></td>
<td class="name"><?php echo $register_column[ 'name' ]; ?></td>
<td class="description"><?php echo $register_column[ 'description' ]; ?></td>
<td class="class"><?php echo $register_column[ 'class' ]; ?></td>
<!-- В этой колонку будет выводится статистика по использованию колонки. Чтобы не перегружать таблицу лишней информацией часть параметров будет показываться в модальном окне. -->
<td class="posts">
<!-- начало модального окна -->
<div id="usage-<?php echo $register_column[ 'id' ]; ?>" style="display:none;">
<!-- если есть страницы с колонкой - выведем их -->
<?php if ( is_array( $pages ) && ! empty( $pages ) ) : $usage = $usage + count( $pages ); ?>
<h4><?php _e( 'Страницы', RESUME_TEXTDOMAIN ); ?></h4>
<ul class="list-disc">
<?php foreach ( $pages as $page ) : ?>
<li><a target="_blank" href="<?php echo get_permalink( $page ); ?>"><?php echo apply_filters( 'the_title', $page->post_title, $page->ID ); ?></a></li>
<?php endforeach; ?>
</ul>
<?php else : ?>
<p><?php _e( 'Колонка не используется на постоянных страницах.', RESUME_TEXTDOMAIN ); ?></p>
<?php endif; ?>
<!-- если есть категории с текущей колонкой - выведем их -->
<?php if ( is_array( $categories ) && ! empty( $categories ) ) : $usage = $usage + count( $categories ); ?>
<h4><?php _e( 'Категории', RESUME_TEXTDOMAIN ); ?></h4>
<ul class="list-disc">
<?php foreach ( $categories as $category ) : ?>
<li><a target="_blank" href="<?php echo get_category_link( $category ); ?>"><?php echo apply_filters( 'single_cat_title', $category->name ); ?></a></li>
<?php endforeach; ?>
</ul>
<?php else : ?>
<p><?php _e( 'Колонка не используется на страницах категорий.', RESUME_TEXTDOMAIN ); ?></p>
<?php endif; ?>
</div>
<!-- конец модального окна -->
<!-- теперь выведем кнопку для открытия модального окна со списком виджетов и колонок -->
<a class="thickbox" href="/?TB_inline&inlineId=usage-<?php echo $register_column[ 'id' ]; ?>&width=300&height=200"><?php echo $usage; ?></a>
</td>
<!-- количество виджетов, которые добавили в эту колонку -->
<td class="widgets"><?php echo $widgets_count; ?></td>
<!-- кнопки действий (удалени/редактирования) для работы этих кнопок и была нужна строка-url в переменной $action_url -->
<td class="text-right">
<!-- для кнопки удаления добавим простой скрип для подтверждения действия -->
<a class="action-button delete-button" onclick="return confirm( '<?php esc_attr_e( 'Вы уверены?', RESUME_TEXTDOMAIN ); ?>' );" href="<?php echo $action_url . '&action=delete'; ?>"><?php _e( 'Удалить', TEXTDOMAIN ); ?></a>
<!-- ссылка редактировать ведёт на текущую страницу, но только другую "вкладку" и содержит дополнительные параметры -->
<a class="action-button edit-button" href="<?php echo $action_url . '&tab=edit'; ?>"><?php _e( 'Редактировать', RESUME_TEXTDOMAIN ); ?></a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif;
// сохраним данные из буфера в переменную
$page_content = $page_content . ob_get_contents();
// завершим работу буфера и очистим его
ob_end_clean();
// подключим шаблон админ страница
include get_theme_file_path( 'views/admin/menu-page.php' );
}
Операции с пользовательской колонкой
Над пользовательской колонкой на странице настроек можно осуществлять три операции, это:
- добавление
- редактирование
- удаление
Формы добавления и редактирования в качестве обработчика (атрибут action
) указывают текущую страницу настроек. Во время удаления используется URL с GET-параметрами, который так же ссылается на страницу настроек. Для реализации операций с колонками предусмотрена функция обработчик, которая работает перед загрузкой страницы настроек.
Для этого удобно использовать хук current_screen
, который выполнятеся, когда уже установлены необходимые элементы для идентификации текущего экрана. В качестве параметра передает объект WP_Screen
.
Во время хука current_screen
можно осуществить какое либо действие только для конкретной страницы админки, поєтому он идеально подходит для реализации операций над колонками.
/**
* Выполнение пользовательских действий над списком сайдбаров
* @param WP_Screen $current_screen [description]
*/
function action_for_custom_columns( $current_screen ) {
// определяем на нужно ли мы странице находимся, т.к. по задумке действия будут совершаться только на одной
if ( 'appearance_page_' . RESUME_SLUG . '_custom_columns' == $current_screen->id ) {
// проверяем права пользователя, соответствие кода безопасности и наличие необходимых для
if (
current_user_can( 'manage_options' )
&& isset( $_REQUEST[ 'action' ] )
&& in_array( $_REQUEST[ 'action' ], [ 'add', 'edit', 'delete' ] )
&& isset( $_REQUEST[ 'nonce' ] )
&& wp_verify_nonce( $_REQUEST[ 'nonce' ], plugin_basename( __FILE__ ) )
) {
// получаем массив всех зарегистрированных пользовательских колонок
$register_columns = get_theme_mod( 'register_columns', [] );
// определяем какую операцию нужно соверщить
switch ( $_REQUEST[ 'action' ] ) {
// добавление/регистрация новой колонки
case 'add':
// код ниже
break;
// редактирование пользовательской колонки
case 'edit':
// код ниже
break;
// удаление пользовательской колонки
case 'delete':
// код ниже
break;
}
// сохраняем изменившийся список пользовательских колонок в базе данных
set_theme_mod( 'register_columns', $register_columns );
// редректим назад на страницу настроек
wp_safe_redirect( get_admin_url( null, 'themes.php?page=' . RESUME_SLUG . '_custom_columns', null ), 302 );
exit();
}
}
}
add_action( 'current_screen', 'action_for_custom_columns', 10, 1 );
Добавление колонки
if ( isset( $_REQUEST[ 'new_column' ] ) ) {
// очищаем полученных данные и сохраняем во временную переменную
$new_column = parse_only_allowed_args(
[ 'name' => '', 'description' => '', 'class' => '' ],
$_REQUEST[ 'new_column' ],
[ 'sanitize_text_field', 'sanitize_text_field', 'sanitize_text_field' ],
[ 'name' ]
);
// проверяем успешность очистки данных
if ( null !== $new_column ) {
// если всё нормально, то создаём идентификатор для новой колонки
$new_column[ 'id' ] = 'column_' . md5( $new_column[ 'name' ] );
// проверяем существует ли такой же идентификатор в базе
if ( empty( count( wp_list_filter( $register_columns, [ 'id' => $new_column[ 'id' ] ], 'AND' ) ) ) ) {
// если нет, то добавляем новую колонку в массив
$register_columns[] = $new_column;
}
}
}
Полученные данные перед сохранением необходимо проверить и очистить. Для этого потребуется дополнительная функция.
Функция принимает неочищенные параметры в виде массива, сверяет с белым списком и очищает или присваивает значения по умолчанию. “Белый список” передаётся в виде ассоциативного массива, где ключ это идентификатор (имя) параметра, а значение – значение по умолчанию. Отдельно передаётся массив с идентификаторами (именами) обязательных параметров и функций очистки.
Эта функция пригодится для обработки данных форм.
/**
* Функция для очистки массива параметров
* @param array $default разрешённые параметры и стандартные значения
* @param array $args неочищенные параметры
* @param array $sanitize_callback одномерный массив с именами функция, с помощью которых нужно очистить параметры
* @param array $required обязательные параметры
* @return array возвращает очищенный массив разрешённых параметров
*/
function parse_only_allowed_args( $default, $args, $sanitize_callback = [], $required = [] ) {
// неочищенные параметры обязательно должны быть в виде массива
$args = ( array ) $args;
// инициализируем возвращаемые данные, так же в виде массива
$result = [];
// счетчик-указатель на текущий параметр
$count = 0;
// проходим по массиву с белым списком параметров
// получаем текущую пару ключ => значение в переменную
while ( ( $value = current( $default ) ) !== false ) {
// получаем имя текущего разрешённого параметра
$key = key( $default );
// проверяем есть ли в неочищенном массиве есть такое элемент
if ( array_key_exists( $key, $args ) ) {
// если есть, то добавляем это значение к результату
$result[ $key ] = $args[ $key ];
// проверяем есть ли функция для очистки значения
if ( isset( $sanitize_callback[ $count ] ) && ! empty( $sanitize_callback[ $count ] ) ) {
// если есть, то очищаем значение
$result[ $key ] = $sanitize_callback[ $count ]( $result[ $key ] );
}
// если в неочищенном массиве НЕ передали нужное нужное значение, то проверяем обязательные ли это параметр и если обязателен, то в результат записываем значение по умолчанию
} elseif ( in_array( $key, $required ) ) {
return null;
} else {
$result[ $key ] = $value;
}
// передвигаем указатели на следующий элемент
$count = $count + 1;
next( $default );
}
// возвращаем результат
return $result;
}
Редактирование колонки
Процедура редактирования колонки похожа на процедуру добавления новой.
// проверяем есть ли данные для обновления
if ( isset( $_REQUEST[ 'new_value' ] ) ) {
// если есть, то очищаем их
$new_value = parse_only_allowed_args(
[ 'name' => '', 'id' => '', 'description' => '', 'class' => '' ],
$_REQUEST[ 'new_value' ],
[ 'sanitize_text_field', 'sanitize_text_field', 'sanitize_text_field', 'sanitize_text_field' ],
[ 'name', 'id', 'description', 'class' ]
);
// проверяем результат после очистки
if ( null !== $new_value ) {
// находим колонку, которую нужно редактировать
foreach ( $register_columns as &$register_column ) {
// колонка найдена - записываем новые данные и прерываем работу цикла
if ( $new_value[ 'id' ] == $register_column[ 'id' ] ) {
$register_column = $new_value;
break;
}
}
}
}
Удаление колонки
Идентификатор колонки – это обязательный параметр для её удаления.
Так же во время операции удаления колонки нужно очистить и метаданные постов и категорий на которых она используется.
// проверяем передан ли идентификатор колонки
if ( isset( $_REQUEST[ 'id' ] ) ) {
// очищаем данные перед использованием
$id = sanitize_text_field( $_REQUEST[ 'id' ] );
// проверяем поле после очистки
if ( ! empty( $id ) ) {
// ищем в цикле нужную колонку
for ( $i = 0; $i < count( $register_columns ); $i++ ) {
// если колонка с идентификатором найдена - начинаем её удаление
if ( $id == $register_columns[ $i ][ 'id' ] ) {
// получим список страниц на которых используется колонка
$pages = get_pages( [
'number' => -1,
'meta_key' => '_custom_columns',
'meta_value' => $id,
] );
// если страницы найдены - удаляем метаданные в цикле
if ( is_array( $pages ) && ! empty( $pages ) ) {
foreach ( $pages as $page ) {
delete_post_meta( $page->ID, '_custom_columns' );
}
}
// получим список категорий на которых используется колонка
$categories = get_categories( [
'taxonomy' => 'category',
'hide_empty' => false,
'meta_key' => '_custom_columns',
'meta_value' => $id,
] );
// если категории найдены - удаляем метаданные в цикле
if ( is_array( $categories ) && ! empty( $categories ) ) {
foreach ( $categories as $category ) {
delete_term_meta( $category->ID, '_custom_columns' );
}
}
// удаляем данные колонки со списка
array_splice( $register_columns, $i, 1 );
break;
}
}
}
}
Итоги
Количество кода получилось довольно большое. В реальном проекте его целесообразней разделить на несколько файлов.
Описанный выше способ подойдёт не только для работы с пользовательскими колонками, но и для создания страниц настроек консоли в целом.
Продолжение
- Часть 1. Пользовательский сайдбар для WordPress темы
- Часть 3. Добавление дополнительных полей при редактировании постоянных страниц и категорий.