
Частина 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, яка стрвора вище -->
<td class="text-right">
<!-- до кнопки видалення додано простой js скрипт для підтвердження виконання операції -->
<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 поточний екран
*/
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. Додавання додаткових полів при редагуванні постійних сторінок і категорій