PHP
#wordpress#php#meta-box#post-meta#security#oop#nonce

WordPress: метабокс с безопасным сохранением (OOP, nonce, права)

Класс метабокса для CPT с проверкой nonce, автосохранения и прав пользователя. get_post_meta, update_post_meta, add_meta_box по документации.

Как использовать

  1. Подключите класс в теме или плагине и вызовите ProjectRepoMetaBox::init() на add_action('init', ..., 20) (после регистрации CPT).
  2. Ключ мета с подчёркиванием (_repo_url) скрыт из блока «Произвольные поля» в редакторе.

Метабокс для поля «URL репозитория» у CPT project с проверкой nonce, пропуском автосохранения и проверкой прав. Соответствует документации add_meta_box и примерам на wp-kama.ru.

<?php

declare(strict_types=1);

/**
 * Метабокс «Репозиторий» для post type project.
 * Nonce + проверка прав при сохранении. PHP 8.1+.
 */
final class ProjectRepoMetaBox
{
    private const POST_TYPE = 'project';
    private const META_KEY = '_repo_url';
    private const NONCE_ACTION = 'project_repo_save';
    private const NONCE_NAME = 'project_repo_nonce';
    private const INPUT_NAME = 'project_repo_url';

    public static function init(): void
    {
        $self = new self();
        add_action('add_meta_boxes', [$self, 'addMetaBox']);
        add_action('save_post_' . self::POST_TYPE, [$self, 'save'], 10, 1);
    }

    public function addMetaBox(string $postType): void
    {
        if ($postType !== self::POST_TYPE) {
            return;
        }
        add_meta_box(
            'project_repo',
            'Репозиторий',
            [$this, 'render'],
            self::POST_TYPE,
            'normal',
            'default'
        );
    }

    /** @param \WP_Post $post */
    public function render($post): void
    {
        wp_nonce_field(self::NONCE_ACTION, self::NONCE_NAME);
        $value = get_post_meta((int) $post->ID, self::META_KEY, true);
        $value = is_string($value) ? $value : '';
        ?>
        <p>
            <label for="<?php echo esc_attr(self::INPUT_NAME); ?>">URL репозитория</label><br>
            <input type="url"
                   id="<?php echo esc_attr(self::INPUT_NAME); ?>"
                   name="<?php echo esc_attr(self::INPUT_NAME); ?>"
                   value="<?php echo esc_attr($value); ?>"
                   style="width:100%; max-width:32em;"
            />
        </p>
        <?php
    }

    public function save(int $postId): void
    {
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return;
        }
        $nonce = $_POST[self::NONCE_NAME] ?? '';
        if (!is_string($nonce) || !wp_verify_nonce($nonce, self::NONCE_ACTION)) {
            return;
        }
        if (!current_user_can('edit_post', $postId)) {
            return;
        }
        $raw = $_POST[self::INPUT_NAME] ?? '';
        if (!is_string($raw)) {
            return;
        }
        $url = esc_url_raw(trim($raw));
        update_post_meta($postId, self::META_KEY, $url);
    }
}

// После регистрации CPT (например в плагине):
ProjectRepoMetaBox::init();

Usage:

Вызовите ProjectRepoMetaBox::init() после того, как зарегистрирован тип project (например, в том же плагине на init с приоритетом 20). В шаблоне значение читайте через сниппет «Post meta helper (OOP)» или get_post_meta($postId, '_repo_url', true).

Notes:

  • Ключ _repo_url с подчёркиванием не показывается в блоке «Произвольные поля» (get_post_meta).
  • Сохранение привязано к save_post_{post_type} — срабатывает только для записей типа project.