Создание страницы настроек пользовательских сайдбаров - Блог 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 в переменной $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;
      }
    }
  }
}

Итоги

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

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

Продолжение

51голос
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x
()
x