Множественное кастомное поле в WordPress

Дмитрий Корнев
16 февраля 2015

Про полезность и сам принцип создания кастомных полей для постов в WordPress написано уже немало статей. Я бы хотел уделить внимание именно множественным полям. Можно ли записать массив данных в кастомное поле?

Оказывается можно! Причем об этом разработчики WordPress подумали и даже облегчили задачу.

Итак, задача. Требуется для постов записывать данные вида:

Как такое сделать? Естественно, что уж точно не следует создавать n-ое количество кастомных полей с соответствующими названиями (ключами). Это уж совсем изврат. Проще всего записывать такие данные массивом в одно поле.

Сразу скажу, что WordPress умеет работать со списками кастомных полей. Я имею в виду, что в кастомное поле с одним и тем же названием (ключом) можно записать несколько значений, в том числе несколько массивов. В последствии выводиться это будет в виде списка: моё_поле[0], моё_поле[1], моё_поле[2], и т.д. Соответственно это придётся учитывать потом в коде, проще говоря, возникают определённые заморочки.

На мой взгляд, работать с кастомными полями-списками следует только в случае, когда предполагается записывать ну очень большие объемы данных. Каждое кастомное поле в WordPress — это запись в базе данных в таблицу postmeta. При этом ячейка этой таблицы meta_value, куда будет непосредственно записываться значение кастомного поля, имеет тип «longtext». Если верить справочным данным, то для базы MySQL это означает возможность записать до 4294967300 байт или строки длиной до 4294967296 знаков. Согласитесь, что это очень дофига! Соответственно, даже если не записывать нужный нам массив в виде кастомного поля-списка, а записывать его именно в одно кастомное поле, то ограничения по объему мы достигнем очень-очень не скоро.

Множественное кастомное поле создаём с использованием файла functions.php из каталога темы сайта. Код следующий:

// Добавляем блок кастомного поля на страницу редактирования поста.
add_action('add_meta_boxes', 'cd_meta_box_add');
function cd_meta_box_add() {
    add_meta_box('post-datas', __('Post datas', 'd1mon'), 'cd_meta_box_post_datas', 'post', 'normal', 'high');
}

// Задаём максимальное число параметров массива.
$post_datas_max = 10;

// Формируем форму редактирования в блоке кастомного поля.
function cd_meta_box_post_datas($post) {
    global $post_datas_max;
    $post_datas_i = 0;
    wp_nonce_field('my_meta_box_nonce', 'meta_box_nonce');

    if ($post_datas = get_post_meta($post->ID, 'post_datas', true)) {
        foreach ($post_datas as $data_arr) {
            // Если есть значения, то заносим их в форму.
            if (mb_strlen($data_arr['name']) || mb_strlen($data_arr['value'])) {
                $post_datas_i++;
                echo 'Name:<input name="data_name_'.$post_datas_i.'" id="data_name_'.$post_datas_i.'" value="'.esc_attr($data_arr['name']).'" /></p>';
                echo '<p>Value:<input name="data_value_'.$post_datas_i.'" id="data_value_'.$post_datas_i.'" value="'.esc_attr($data_arr['value']).'" /> ';
            }
        }
    }

    // Доформировываем форму пустыми полями.
    while ($post_datas_i < $post_datas_max) {
        $post_datas_i++;
        echo 'Name:<input name="data_name_'.$post_datas_i.'" id="data_name_'.$post_datas_i.'" value="" /></p>';
        echo '<p>Value:<input name="data_value_'.$post_datas_i.'" id="data_value_'.$post_datas_i.'" value="" /> ';
    }
}

// Сохраняем кастомное поле при сохранении поста.
add_action( 'save_post', 'cd_meta_box_save' );
function cd_meta_box_save( $post_id ) {
    global $post_datas_max;
    $post_datas_i = 0;
    $post_datas_rec_i = 0;

    // Bail if we're doing an auto save
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;

    // if our nonce isn't there, or we can't verify it, bail
    if (!isset($_POST['meta_box_nonce']) || !wp_verify_nonce($_POST['meta_box_nonce'], 'my_meta_box_nonce')) return;

    // if our current user can't edit this post, bail
    if (!current_user_can('edit_post')) return;

    // Массив для фильтрации записываемых данных. В данном примере допустима запись тегов ссылок.
    $allowed = array('a' => array('href' => array()));

    // Собираем наш массив.
    while ($post_datas_i <= $post_datas_max) {
        $post_datas_i++;
        if ((isset($_POST['data_name_'.$post_datas_i]) && mb_strlen($_POST['data_name_'.$post_datas_i]))
        || (isset($_POST['data_value_'.$post_datas_i]) && mb_strlen($_POST['data_value_'.$post_datas_i]))) {
            $post_datas_rec[$post_datas_rec_i]['name'] = isset($_POST['data_name_'.$post_datas_i]) && mb_strlen($_POST['data_name_'.$post_datas_i]) ? wp_kses($_POST['data_name_'.$post_datas_i], $allowed) : '';
            $post_datas_rec[$post_datas_rec_i]['value'] = isset($_POST['data_value_'.$post_datas_i]) && mb_strlen($_POST['data_value_'.$post_datas_i]) ? wp_kses($_POST['data_value_'.$post_datas_i], $allowed) : '';
            $post_datas_rec_i++;
        }
    }

    // Если массив собрался, то записываем.
    if (isset($post_datas_rec)) {
        update_post_meta($post_id, 'post_datas', $post_datas_rec);
    }
}

Этот код делает основную работу. Он создает форму для редактирования массива на странице редактирования поста, который в последствии будет записываться в кастомное поле с именем post_datas.

Размер этого массива задаётся переменной $post_datas_max. В примере полей не много = 10шт. Они выводятся сразу в форму. Если полей будет больше, то имеет смысл уже заморочиться с AJAX, чтобы по умолчанию выводить только, допустим, 10 полей (ну или сколько уже содержат данные), а дополнительные добавлять в форму по клику «Добавить ещё».

Также этот код сохраняет данные формы при сохранении поста. Ну и производит другие действия, вроде фильтрации данных и защиты от записи формы лицами, которые не имеют на это прав.

Стоит отметить, что в коде нет каких-либо дополнительных преобразований, т.е. мы сформировали массив и сразу записали его в кастомное поле. На самом же деле, конечно, массив записывается в поле в преобразованном виде. Просто эту работу берет на себя функционал WordPress. Конкретно, функция update_post_meta, когда видит, что ей предлагают записать в кастомное поле не строку, а массив, то автоматически преобразует его с использованием PHP-функции serialize().

Обратное преобразование с использованием unserialize() тоже может производится автоматически. Для этого следует использовать функцию WordPress под названием get_post_meta(). Далее пример вывода нашего массива на странице. Код следует размещать в файле single.php.

if ($post_datas = get_post_meta($post->ID, 'post_datas', true)) {
    $post_datas_html = '';
    //echo "<pre>",htmlspecialchars(print_r($post_datas,true)),"</pre>";

    foreach ($post_datas as $post_datas_arr) {
        // Если есть инфа.
        if (!empty($post_datas_arr['name']) || !empty($post_datas_arr['value'])) {
            if (!empty($post_datas_arr['name'])) {
                $post_datas_html .= '<p>Name: '.$post_datas_arr['name'].'</p>';
            }
            if (!empty($post_datas_arr['value'])) {
                $post_datas_html .= '<p>Value: '.$post_datas_arr['value'].'</p>';
            }
        }
    }

    if (mb_strlen($post_datas_html)) {
        ?><div class="post-data"><?php echo $post_datas_html; ?></div><?php
    }
}

Ну, вот и всё. Я использовал данный способ для сохранения в массив ссылок, прикреплённых к посту, т.е. сохранял URL и TITLE для каждой ссылки. Но вообще, применений можно найти массу.

Множественное кастомное поле в WordPress

7 комментариев

al
Здравствуйте. Я не очень разбираюсь в коде, но вот такие вот поля очень мне бы пригодились. Подскажите какой нибудь плагин, что бы ничего лишнего. Только то что у Вас описано. Спасибо.
К сожалению, не подскажу. Уже не помню, но наверняка искал сам такой плагин и не нашел, раз в итоге пришлось делать своими силами.
El
Здравствуйте! Дмитрий, а скажите пожалуйста, как на таком же примере добавить дополнительно поле для вставки миниатюры ?
Нужно, чтобы для поста задавалась одна дополнительная миниатюра? Наверное можно сделать такое кастомное поле. Как бы, в статье речь не совсем о том, либо я не понял, что вы хотите.
Ма
Здравствуйте! Подскажите, как сделать, чтобы поля добавлялись не указанием массива в численном значении, а через кнопку добавить?
Такое через ajax делается. В статье написано. Готового кода у меня нет, не делал.
Юр
Идеальная функция! Спасибо, автор) Я ещё добавил поле "описание": Получилось: ссылка/название - описание