Створення сторінки налаштувань для довільних сайдбарів - Блог chomovva

Це продовження статті про те як додати будь-яку кількість довільних сайдбарів (колонок) в 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;
      }
    }
  }
}

Підсумки

Вийшла досить велика кількість коду. У реальному проекті його доцільніше розділити на кілька файлів.

Описаний вище спосіб підійде не тільки для роботи з одними колонками, а й для створення сторінок налаштувань консолі в цілому.

Далі

51голос
Рейтинг статті
Підписатися
Сповістити про
guest
0 Коментарі
Вбудовані Відгуки
Переглянути всі коментарі
0
Ми любимо ваші думки, будь ласка, прокоментуйте.x
()
x