Это продолжение статьи «WP REST API(дефолтное) — взаимодействие с WP сайтом,» в которой я повествовал о работе с дефолтным апи вордпрес. Однако, той статье я ничего не рассказываю о пагинации или получении связанных обьектов в одном запросе, ну и тд… в общем, эта статья ксть дополнением к той(по этому прочтите ее).
Параметры пагинации
Предположим, у нас появилась потребность получить обьекты постов запросом к WP REST API. В случаи если ЧПУ на сайте включены, запрос будет выглядеть так
https://guten.xyz/wp-json/wp/v2/posts
Cейчас мы получим JSON-строку, содержащую массив обьектов всех постов(или 100, если их больше). Но если нам не нужны они все? Здесь все просто — нужно указать параметры пагинации(аналогичные им же в обьекте WP_Query). Этот запрос ограничит нашу выборку 5-ю постами ч 2-ой страницы
Как видно, были добавлены 2 GET-параметра — per_page и page. На деле их 4(5)(если порядок сортировки результатов относить к пагинации).
Третий параметр(offset) позволяет настроить сдвиг, т.е. количество постов, которое нужно пропустить(еще не придумал ситуации где он нужен, может, для разбития самой страницы…). И четвертый(order и orderby, да, их два — обманул) — определяют поле для сортировки(второй) и порядок сортировки(первый).
В итоге, запрос, который будет выводить последние 3 поста с второй страницы, содержащей по 5 постов на страницу и в порядке убывание id, выглядит так
Сортируемые поля у каждого типа обьекта разные… и, к полной картине, заголовки ответа(в полях X-WP-Total и X-WP-TotalPages) содержится
Заголовки HTTP-ответа
информация о количестве записей и количестве страниц, при данном разбиении. Немного уточнений по offset — он отбрасывает не n первых постов на каждой странице, а n первых постов согласно параметров сортировки.
Ссылание и встраивание
Каждый полученный обьект содержит поле _links, представляющее собой массив ссылок на апи-пути связанных обьектов. Также, эти массивы имеют поле embeddable, несущее информацию о том, можно ли этот связанный обьект встроить в текущий.
Для примера, воспользуемся обращением к постам; ответ содержит только id изображения миниатюры, но не саму ссылку на него(которую можно вставить в тег img, к примеру). В поле _links есть следующий обьект
Ссылка на связанный обьект
В этом участке информации меня интересует не так ссылка на обьект, как то, что поле embeddable имеет значение true, т.е. обьект может быть встроен в текущий. Чтобы его встроить, нужно к текущему запросу дописатьь еще один гет-параметр _embed с значением нужного обьекта; в моем случаи
при этом в каждом родительском объекте появится поле _embedded, содержащий требуемый обьект(равный тоому, который будет ответом по ссылке).
Встроенный объект
Фильтр полей
В WP REST API есть get-атрибут _fields, позволяющий заказать поля, которые нужно получить. Это нужно, как вы понимаете, для оптимизации запросов к серверу, что б он не «лег» в пиковые моменты… но не только! Также, этим параметром можно заказывать различные зарегистрированные метаполя. Рассмотрим пример получения id, title и метаполя counter; для начала зарегистрируем его в rest
https://guten.xyz/wp-json/wp/v2/posts?_fields=id,title,meta.counter
# или
https://guten.xyz/wp-json/wp/v2/posts?_fields=id,title,counter
Теперь немного об опыте использования этого на практике. Этим параметром можно получить только поля обьекта записи(или других табличных сущностей), т.е. при этом никаких вложенных обьектов не будет.
Все чаще и чаще на в сайты интегрируют элементы SPA, в частности «бесконечную страницу»… здравствуй, читатель! По ходу этой статьи будет описан принцип работы такой страницы, реализация и посадка на вордпресс.
SPA и бесконечная страница
В принципе, и SPA(Single Page Application — одно-страничное приложение) и бесконечная страница исповедуют один и тот же принцип(извиняюсь за тавтологию) — динамическая загрузка контента. Все очень просто — когда поступает событие(клик либо скролл, либо еще что-то), javascript’ом загружается новая порция контента и вставляется в страницу.
Реализация
Использовать буду jQuery(тут часть любителей хайповых React, Vue и прочего, вышли…). Обьясню свой выбор — зачем перегружать страницу яваскриптом? да, jQuery тоже библиотека без которой можно обойтись, но если он используется для многих других элементов — почему бы и нет?
Немного о ней: поскольку я черпать контент буду в ворпрессе — то и использую пагинацию для разбивки на загружаемые блоки. Первую страницу загрузим обычным способом(каждая страница — это div class="page-%n"). В теге template будет содержаться разметка нашего поста — после загрузки пачки постов я буду ее клонировать.
Далее, нужно прибегнуть к javascript. Как я говорил ранее, на странице будет jQuery, поэтому сделаем AJAX-запрос к WP REST API со списком постов и их данными. Но прежде — определим условия для начала загрузки постов. Раз уж бесконечное полотно — значит условие это достижение нижней границы текущей страницы
var prevScroll = 0,
page = 1,
windowH = $(window).height(),
positionY = $('.page-1').offset().top,
blockH = $('.page-1').height(),
template = $('#post').html(),
currentScroll, tmpl
$(window).scroll(function (event) {
currentScroll = $(this).scrollTop()
if (currentScroll > prevScroll) {
prevScroll = currentScroll
if (Math.round(blockH + positionY) === (windowH + currentScroll)) {
// Получаем посты, рисуем их и высчитываем новую границу для загрузки
}
}
})
Немного пояснений для этого куска кода. Эта строка(12) позволяет отсеять скролл вверх; можно было б отслеживать прокрутку колеса на мишке — но тогда скрипт был бы мертв на мобильниках. По поводу Math.round() свойства — высота(как и ширина) может быть дробной, в то время как значение скролла всегда целое число. Еще замечу — этот скрипт рабочий только в случаи если под блоком(не по разметке, а по позиции) с постами нет больше блоков(отсутствует футер). Дело в том, что при быстрой прокрутке scrollTop() имеет разрывы и тогда может произойти перескок нужного значения — потому, заменим его(===) простым сравниванием(>). Также, поскольку загрузка постов требует времени, используем коэффициент(что пользователю не пришлось ждать и он не покинул из-за этого сайт). В итоге, строка 15 трансформируется в
строка 1 «убивает» прослушивание события прокрутки. Это нужно потому, что js асинхронен и способен отослать несколько одинаковых запросов к серверу — что, в свою очередь, приведет к повторению постов(помимо ненужной нагрузки на сервер);
строки 4 — 33 сам запрос к серверу: первый аргумент это адрес эндпоинта WP REST API, который возвращает список постов, второй — обьект с параметрами постов, третий — функция, которая будет исполнена при получении ответа;
строки 23 — 26 — почему измерения высоты блока заперты в load’е? — у меня нет контейнера с размерами для картинки, а поскольку они грузятся отдельно от аякса — они изменяют размер блока постов через некоторое время;
строка 29 возвращаем прослушку прокрутки.
По ходу написания статьи я немного изменил скрипт, а именно, анонимную функцию слушателя присвоил переменной onScroll. Немного о том, почему в строке 14 я постоянно клонирую шаблончик — каждый клон одноразовый, т.е. один раз вставив клон его переменная стает пустой.
В общем, полный код бесконечной страницы(уже посаженой на шаблон страницы вордпрес) можно увидеть и взять здесь.
Elementor — популярный wp плагин для построения страниц; данный тип плагинов называют pagebuilder. Это плагин(как и все в его категории) оперирует специальными виджетами — именно его я и создам, по ходу статьи…
Здесья расскажу о том, что толкнуло меня на создание виджета(помимо получения опыта для написания этой писульки). Дело было так: была страница на которую выводилось видео с YouTube. К нему был список тайм-кодов, клик по которым включал воспроизведение видео с определенного момента. Временная метка(таймкод) состоит из самого времени, картинки и текста-описания. При переезде страницы на Элементор — всё взаимодействие сламалось. Задача: создать аддон к Элементор, добавляющий эти возможности в виджете.
Немного воды…
Всем, кто знаком с веб-программированием, с ходу стало понятно в чем причина — javascript; теперь для тех, кто незнаком: за прослушивание всех событий в браузере отвечает javascript. И правда, билдер изменил структуру DOM-дерева страницы и, за часик(долго разбирался в новой структуре), изменив селекторы в скрипте все заработало…
И что ж?.. на этом все? Было бы всё, если бы владелец не сказал, что неудобно так: строить ряд, в нем колонки с текстовым полем в(которых снова ряды с колонками) — сделай-ка единым блоком, чтоб в элементы управления просто вбить значения.
Приступим…
Начал я, разумеется, с оболочки плагина. На сайте плагина, в доках, есть шаблон кода основного файла для аддонов Элементор. Немного опишу главный метод, который в нем присутствует
add_action('elementor/init', function () {
add_action('elementor/widgets/widgets_registered', function () {
require_once(__DIR__ . '/class-youtube-widget.php');
ElementorPlugin::instance()->widgets_manager->register_widget_type(new Elementor_YouTube_Widget());
});
});
чтоб написать любое расширение к элементору нужно расширить его класс; чтобы получить доступ к ядру плагина(а именно там лежат они) нужно вызывать аддон после загрузки плагина. В движке есть хук(plugins_loaded), срабатывающий после загрузки всех плагинов — однако, в виду того, как устроен сам Элементор — в этот момент его ядро уже недоступно.
Плагин Элементор имеет свои хуки, позволяющие получить доступ к разным классам его ядра:
elementor/init — ко всем;
ementor/widgets/widgets_registered — к классу ElementorWidget_Base(и прочим, необходимым для создания нового виджета);
elementor/controls/control_registered — к классу ElementorControl_Base(и прочим, необходимым для создания нового типа контролера для плагина);
Это все хуки, которые нужно знать, для того чтоб построить свой виджет. На деле, из-за довольно широкой палитры встроенных контролей, 3ий хук я никогда не использовал — не было нужды создавать свои контроллеры.
Теперь можно переходить к созданию самого виджета… хотя, нет — пару строк о скрипте. Здесь, как и в вордпресс, нет метода, который подключал бы скрипты только когда виджет есть на странице(по крайней мере я таких не увидел — если ты о них знаешь — напиши в комментах, буду благодарен!) и потому я подключаю их как обычные скрипты.
Теперь немного о самом скрипте — в нем я буду использовать YouTube Iframe Player API. Начнем с html-разметки, которую будем строить на странице
Думал использовать Iframe API, но глянув, что он делает — послал его лесом. Дело в том, что беглый анализ кода показал, что апи содержит методы постройки тега iframe с его атрибутами; учитывая, что страница индексируемая(должна быть оптимизирована под скорость загрузки) я решил не захламлять ее дополнительной библиотекой(jquery и так хорошо выполняет эту задачу). В общем, вот скрипт для работы с метками с вышеприведенной разметкой
(function($) {
'use strict';
$(document).on('click', '.youtube-preview', function() {
var id = $(this).closest('.elementor-row').data('id');
YTFrame.build(id, 0, $(this).parent());
});
$(document).on('click', 'figure.stamp', function() {
console.log(this);
var id = $(this).closest('.elementor-row').data('id'),
time = $(this).find('.timestamp').text().split(':'),
seconds = 60 * parseInt(time[0]) + parseInt(time[1]);
console.log($(this).closest('.elementor-row').find('.frame-container')[0]);
YTFrame.build(id, seconds, $(this).closest('.elementor-row').find('.frame-container')[0]);
});
var YTFrame = {
build: function(id, seconds, container) {
var iframe = document.createElement('iframe');
iframe.src = 'https://www.youtube-nocookie.com/embed/' + id + '?autoplay=1&start=' + seconds;
$(container).html(iframe);
}
}
})(jQuery);
Также, данный скрипт позволил обойти некоторые ограничения.
Доделываем
Имея скрипт и накидав разметку, а также оболочку для аддона — создадим класс нашего виджета. Он является дочерним для базового абстрактного виджета элементор(который доступен только в ядре плагина); также, необходимы классы для контроля виджета в редакторе
<?php
use ElementorControls_Manager;
use ElementorWidget_Base;
use ElementorUtils;
use ElementorRepeater;
Теперь создадмим сам класс со следующими методами
<?php
class Elementor_Test_Widget extends ElementorWidget_Base {
public function get_name() {}
public function get_title() {}
public function get_icon() {}
public function get_categories() {}
protected function _register_controls() {}
protected function render() {}
protected function _content_template() {}
}
Первые 4 методы просто возвращают по строке. Метод render() строит разметку в редакторе(с приметкой) и на сайте
Здесь Widget_Base->get_settings_for_display() это получение настроек виджета, которые я создам в следующем методе.
Метод _register_controls() отвечает за создание контролей виджета в сайдбаре редактора — здесь немного поподробней. Все настройки растоложены в 3х табах — контент, стиль и дополнительные(их не трожь — они всегда дефолтные!). чтоб выбрать таб нужно в этом методе написать
и уже между этими тегами можно писать свою настройку.
Вся «слоенка» будет выглядеть так
// указание на таб
// если TAB_CONTENT поменять на TAB_STYLE
// будет запись в стиль-таб
$this->start_controls_section(
'content_section',
[
'label' => __('Video'),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
// открытие новой секции
$this->add_control(
'title',
[
'label' => __('Section Title'),
'type' => Controls_Manager::TEXT,
'input_type' => 'text'
]
);
// настройка в секции, их может быть много
$this->add_control(
'video_id',
[
'label' => __('Video ID'),
'type' => Controls_Manager::TEXT,
'input_type' => 'text'
]
);
// закрытие текущей секции, но не таба!
// после этого можно открыть новую секцию
// она будет в том же табе
$this->end_controls_section();
Впринципе, этих методов достаточно для того, чтоб сделать рабочий виджет. Далее пойдет вещь, которую я не делал в этой работе, но хочу показать и рассказать.
Наверняка внимательный читатель не только статьи но и кода, заметил в рендеринг-методе эти строки
все они не имеют смысла без метода _content_template(). Поясню: виджет элементор — блок визуально редактируемый; это значит его можно редактировать в поле редактора, а не в сайдбаре. Кароч, 1ая строка это указатель на то, что эта настройка имееит возможность визуального редактирования; 2ой — получает набор классов для того чтоб javascript элементора взял под контроль этот тег; 3я — получает само значение настройки. В случаи с ‘advanced’ можно увидеть расширенную панель редактирования текста
расширенная панель редактирования текста
Однако, чтоб все это сработало — необходим вышеуказанный метод с следующими строками
Заметь(!) эти строки должны выводиться, тк они будут вставлены в backbone-шаблоны(документация по ним). 2ая строка это т, что в окне редактора будет вставлено вместо редактируемой строки.
Заключение
Было разработанное расширение для Элементор. Код сможешь глянуть здесь, конечно он не полный(я удалил из него интеграции с другими плагинами, кастомными плагинами и сократил дерево настроек). Больше примеров ты увидишь в самом Элементор, ну а я попытался немного объяснить — что и как там работает.
Редактор Gutenberg блочный редактор wordpress для создания контентной части страницы. Он пример того, как плагин стал частью движка. В этой статье я буду не обозревать редактор, а выполнять разработку блока для расширения возможностей редактора.
Задача: разработать плагин, добавляющий блок последних записей в редактор.
В блоке можно редактировать:
Количество выводимых постов;
Категорию постов(из существующих, не пустых)
Статус отображения изображения записи
Я уверен, что подобные плагины существуют и, что подобный блок можно собрать используя другие плагины, на подобии ACF. Но данная задача — это реальное тестовое задание, выполнением которого я хотел бы поделится.
Теоретические выкладки
Начнем из далека — нужно же статье придать объёма!
Редактор Gutenberg – это новый(относительно, частью wp он стал в версии движка 5, до этого был плагином) визуальный редактор WordPress для записей и страниц. Проект назван именем Иогана Гутерберга, презентовавшего Европе печатный станок и начавшего печатную революцию. Его работа сделала знание и информацию доступнее и запустила социальную революцию. Аналогично этому, разработчики хотят сделать доступным создание продвинутый макетов страниц для всех пользователей WordPress(источник).
Основная особенность редактора Gutenberg – это преставление всего контента в виде блоков и определение макета записи прямо в редакторе.
Ну все, хватит копи-паста, мой внутренний борец с плагиатом не позволит более. Да, я знаю, что все мои статейки — это, всего лишь, пере озвучка документаций и других источников… но, иногда, пишу и свои наблюдения.
В общем, каждый блок редактора — скрипт написанный на javascript с использованием библиотеки react. Все, что нужно для создания блока — это зарегистрировать скрипт и стили(коль нужны); в скрипте определить зависимости и функцию(с методами и свойствами).
Опишу свои мысли о том, что я буду делать — дабы выполнить поставленную задачу. Первым делом, учитывая, что по правилам хорошего тона react общается с приложениями по средствам REST API, и нужного пути вордпресс по умолчанию не имеет — создам этот путь в WP REST API. Второе — я знаю, что в таблицу БД движок сохраняет запись о блоке; поскольку, наш блок во фронте должен быть динамичен — то запись должна хранить элемент, который вордпресс ассоциирует с функцией. Кто ранее знакомился с этим движком, понял о чем я — о шоркоде, который мне также предстоит создать. И последнее, несколько слов о javascript — внутри функции регистрации блока нужно будет определить 4 вещи: мета инфу о блоке, его атрибуты, сохраняемую строку и вид в редакторе.
Установка рабочей среды
Это действие нужно чисто для удобства и скорости разработки. Но, в принципе, код можно писать и в блокноте — но все используют специальные редакторы. Так-что, советую эти операции проделать тоже…
Первое, при любой разработке на PHP(а язык бэка wordpress именно php) понадобится локальный сервер. Я использую OpenServer: просто скачай с официального сайта и установи его.
Второе — это компилятор JSX в JS, SCSS в CSS. В качестве такого компилятора, я использую NodeJS с пакетами; далее, подробно об установке.
С официального сайта грузим и устанавливаем NodeJS. После успешной установки, зашел в командную строку, у нас появиться менеджер пакетов npm. Первое, что нужно сделать — это сформировать package.json запуском команды
npm init
и дальнейшими ответами на поставленные вопросы. После чего, в командной строке пишем
npm install --save-dev @babel/core @babel/cli
это установи транскриптор babel в зависимости для ноды. И, для полного понимания при компиляции, нужен пресет @wordpress/babel-preset-default. Запустим
Среда для компиляции JSX готова. Немного о нахождении компилируемого файла и самого запуска процесса компиляции. Пусть компилируемый файл называется script.js и скомпилированный script-compiled.js; как я понял наблюдением, файл script.js должен лежать на одном уровне с package.json и папкой node_modules, так как ругается на указанные пути(по крайней мере, так было у меня). Сам процесс компиляции запускается так
но, по условию, мне нужно выбрать категорию постов. Для этого передадим список категорий и путь, по которому блок будет брать информацию о постах — конечный PHP скрипт в этой части
<?php
function blocks_scripts()
{
$categories = get_categories();
if (!empty($categories)) {
foreach ($categories as $key => $value) {
$cats[$value->term_id] = $value->name;
}
}
// скрываем блок если нет категорий
if (!empty($cats)) {
// подключаем блок
wp_enqueue_script(
'gutenberg-latest-posts',
plugins_url('/block/js/gutenberg-latest-posts-block.js'),
['wp-blocks', 'wp-element', 'wp-editor', 'wp-i18n'],
true
);
// создаем обьект glp, содержащий
// список категорий и ссылку на WP REST API постов
wp_localize_script(
'gutenberg-latest-posts',
'glp',
[
'categories' => $cats,
'restURL' => esc_url(get_rest_url(null, 'block/v1/latest-posts'))
]
);
}
}
add_action('enqueue_block_editor_assets', 'blocks_scripts');
немного о зависимостях — скрипт wp-blocks содержит функции, необходимые для регистрации блока и образует js объект wp.blocks; wp-element содержит функции react с ворпрессовской «прокладкой», объект wp.element; wp-i18n функции для создания translate ready, wp.18n. Зависимость wp-editor можно пропустить, но она содержит готовые функции для размещения элементов управления атрибутами блока.
Следующий шаг — создание пути ‘wp/v2/latest-posts‘ — ведь его еще не существует. Этот код его зарегистрирует в системе
Создадим js-файл по ранее указанному пути и, во избежание конфликтов с другими, сделаем замыкание. Замыкание выглядит как анонимная само исполняющаяся функция
В нижних скобках указаны те объекты, которые нужно «впустить», сверху — под какими именами их использовать в порядке соответствия. Далее, для удобства(поймете после компиляции файла), нужно «вытянуть» некоторые методы с объектов
поля title и icon интуитивно понятные(есть еще и другие, необязательные), скажу только, что category это блок, где будет отображен блок(блоки по умолчанию ). Чтобы создать свою категорию добавь следующий PHP-код
Отдельно упомяну об атрибутах — из-за строгого отношения к типам данных, чтобы булевое значение не сохранить в БД; поэтому, приходится булевое ставить в зависимость от других типов, значение которых можно сохранить. Так у меня, выбор отображения изображения поста происходит чекбоксом(атрибут checked), состояние которого возвращается в типе bool. Чтобы сохранять это состояние, я ввел доп.атрибут в типе integer(thumb), значение которого в полной зависимостти от checked.
Осталось определить 2 метода, которые должны возвращать HTML-разметку имеющую по контейнеру. Первый(edit) отвечает за разметку в редакторе блоков, второй(save) — за то, что будет сохранено в БД. Начнем с первого(полный код, написанный мною)
немного о написанном: функции setCount, setCategory и setThumb — для изменения состояния атрибутов; Item и ее вызов кодом <Item /> — чисто реактовский прикол; setPosts получает список постов по нашему маршруту, и устанавливает его в атрибут posts. BlockControls и InspectorControls — методы объекта wp.editor: первый размещает внутреннюю разметку в верхней панели(toolbar) блока, второй — в боковой панели.
И, наконец-то, edit. Он очень прост, если бы разрабатываемый блок был стационарен — он возвращал бы такую же разметку, как и основное поле блока
но поскольку он динамичен — содержит мой шорткод. Обратите внимание, поскольку результатом должна быть HTML-разметка — я обернул шорткод в тег.
Заключение
После компиляции js был получен код регистрации нового блока. Забыл сказать в основном теле статьи — особенностью react есть то, что он рендерит по фреймам, а не по клику. Из этого следует, что любое действие следует «запирать» в функцию. Весь код выполненного тестового лежит здесь.
Все страницы сайта — это PHP шаблоны, содержащие HTML разметку с PHP тегами. Здесь будет рассмотрено создание шаблона страницы для отдельной записи — начиная от вордпресс цикла и до построения элементов навигации…
Каждая запись это отдельный представитель типа поста. Из коробки, есть 2 типа постов — страницы и записи; у каждой свои приколы по отображению — у страниц шаблоны для отображения, записей — форматы
1 — шаблоны страниц; 2 — форматы записей
Для создания шаблона страницы, в начало файла достаточно добавить комментарий
<?php
// Template Name: TemplateName
с форматами постов все немного сложнее…
Чтобы активировать возможность выбора формата, в functions.php на экшн after_setup_theme функцию со следующим содержимым
Данный код содержит все допустимые форматы для постов; чтобы получить формат текущего поста используйте функцию get_post_format без аргументов, если ты внутри цикла(о нем позже), или с указанием ID поста.
При создании шаблона для своего типа записей — движок позволяет использовать только схему с форматами. По наименованию: для страниц с заголовком требований нет(если стандартный шаблон, без заголовка — page.php). Для постов и нестандартных типов записей — схема такова single-{post_type}.php, ну или single.php для всех типов кроме страниц(page).
WordPress циклы
Будь-то страница или любой другой тип записи(с условием, что он публичен — смотрите «Создаем свой тип записи. Метадата, роли пользователей, таксономии«) его вывод будет начат с цикла. В принципе, тип записей может быть и непубличным, но тогда его нужно будет принудительно доставать, что, я считаю, нехорошо. Итак, цикл(иногда его называют loop, поскольку это перевод) будет выглядеть так
<?php
if (have_posts()) {
while (have_posts()) {
the_post();
// post content
}
}
Естественно, что в начале выводится шапка(get_header) и подвал(get_footer) в конце. Касательно наполнения цикла — принято подключать кусочек шаблона(отдельный файл) при помощи get_template_part($patth), содержащий разметку страницы.
Элементы страницы
Здесь я рассмотрю основные элементы сингл-поста, которые применимы как к записям, так и страницам и прочим типам.
Изображение записи
Для начала — нужно включить их поддержку — это делает следующий код
После этого, на странице редактирования записи появиться такой метабокс
Теперь о том, как сохраняется загруженное изображение. По умолчанию, при сохранении , сохраняется оригинал и три нарезанных картинки из оригинала. Перейдя по «Settongs > Media» размеры этих изображений можно менять. Чтобы отключить нарезку достаточно выставить размеры в 0. Также, можно задать свой размер для кадрирования
Теперь мы имеем 2 варианта по получению установленного изображения; первый — это получение url на оригинал изображения; второй — получение всего img-тега
Приведенный выше код работает только внутри вордпресс-цикла, иначе, в качестве первого аргумента, следует указать ID поста. Но функции вывода работают только в цикле. Размеры кадров я упомянул в предыдущем абзаце к тому, что в данных функциях(получение во втором, вывод первым) можно указывать в виде аргумента название размеров. Стандартно full(оригинал), large(1024px*1024px), medium(300px*300px) и thumbnail(150px*150px). Также, вместо строки с названием размера, можно указать массив с шириной и высотой; при этом, при первой загрузке страницы с новым размером, будет создан и сохранен новый кадр.
Касательно получения и выведения img-тега. Последним аргументом в функцию можно передать массив атрибутов тега. Поскольку, в данной функции задается дефолтное значение этого аргумента, то задать можно только такие атрибуты — src, class и alt.
Таксономии
Будь-то стандартные, либо же кастомные таксономии, древовидные они или нет — вывод их одинаков
здесь $object_ids это ID записи, а вот на $args, пожалуй, поподробнее:
orderby -поле, по которому сортировать порядок вывода:
поиск в полях терминов — term_id, name, slug, term_group;
по полям таксономии терминов — id(поле term_taxonomy_id в таблице БД), description, parent, count(количество терминов в таксономии);
среди полей таблицы term_relationships — term_order;
по полям меты — meta_value_num(количеству полей meta_value) и по значению meta_key
Можно и вообще отключить сортировку установив значение в ‘none’. Ключ order устанавливает порядок сортировки(ASK/DESC).
Также, в этом массиве можно задать множество других параметров поиска. К примеру, наличие/отсутствие/равенство метаполей, вложенность, древовидность и т.д. Получаем ссылку на архив термина
В случаи с выведением на страницу, указывать аргументы не обязательно, но в такой записи заголовок будет обрамлен H1-тегом. Не много о том, почему не стоит вытягивать post_title с обьекта WP_Post — потому, что запись может быть личной или защищенной паролем; при правильном выводе эти статусы являются частью заголовка, при неправильном — этот функционал придется дублировать… а это плохо!
поскольку одинаковых метаключей к одной записи может быть много, то данные извлекаются в виде массива; если поле одно — можно сразу его извлечь, указав true третьим аргументом — иначе, по умолчанию, будет false.
Навигация
Под контентом поста принято делать ссылки на предыдущий и следующий посты. Делается это следующим кодом
Через аргумент функции моно кастомизировать только атрибуты class у и aria-label тега nav, а также то, что в ссылке. Для этого в массиве $args есть ключи prev_text(может содержать html-теги), next_text(тоже), aria_label и class.
Если же такой уровень кастомизации не достаточен и нужна своя разметка — делаем следующее: получаем а-теги
В вордпресс принято поключать комментарии таким образом
<?php
if ( comments_open() || get_comments_number() ) :
comments_template();
endif;
Здесь проверяется открытые ли комментарии у поста и их наличие. При выполнении хотя б одного условия — будут загружены стандартные форма комментирования и список комментов, которые можно перезаписать в файле comments.php темы.
Рекомендую сделать также, это избавит от некоторых проблем. Дальше, создаем файл comments.php и пишем в него.
Форма комментариев
Здесь ситуация похожа на ситуацию с навигацией: форму комментария строит отдельная функция.
<?php
comment_form( $args, $post_id );
внутри цикла $post_id можно пропустить. При пустом $args получим следующий html
<div id="respond" class="comment-respond">
<h3 id="reply-title" class="comment-reply-title">Leave a Reply <small><a rel="nofollow" id="cancel-comment-reply-link" href="{cancelReplyLink}" style="display:none;">Cancel reply</a></small></h3>
<form action="{actionLink}" method="post" id="commentform" class="comment-form" novalidate="">
<!-- if current user logged in -->
<p class="logged-in-as">
<a href="{profileLink}" aria-label="Logged in as admin. Edit your profile.">Logged in as admin</a>. <a href="nonce=dc4f1aa958">Log out?</a>
</p>
<!-- endid -->
<p class="comment-form-comment">
<label for="comment">Comment</label>
<textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" required="required"></textarea>
</p>
<!-- if current user not logged in -->
<p class="comment-form-author">
<label for="author">Name <span class="required">*</span></label>
<input id="author" name="author" type="text" value="" size="30" maxlength="245" required="required">
</p>
<p class="comment-form-email">
<label for="email">Email <span class="required">*</span></label>
<input id="email" name="email" type="email" value="" size="30" maxlength="100" aria-describedby="email-notes" required="required">
</p>
<p class="comment-form-url">
<label for="url">Website</label>
<input id="url" name="url" type="url" value="" size="30" maxlength="200">
</p>
<p class="comment-form-cookies-consent">
<input id="wp-comment-cookies-consent" name="wp-comment-cookies-consent" type="checkbox" value="yes"> <label for="wp-comment-cookies-consent">Save my name, email, and website in this browser for the next time I comment.</label>
</p>
<!-- endif -->
<p class="form-submit">
<input name="submit" type="submit" id="submit" class="submit" value="Post Comment">
<input type="hidden" name="comment_post_ID" value="10" id="comment_post_ID">
<input type="hidden" name="comment_parent" id="comment_parent" value="0">
</p>
<input type="hidden" id="_wp_unfiltered_html_comment_disabled" name="_wp_unfiltered_html_comment" value="f6a52e0808"><script>(function(){if(window===window.parent){document.getElementById('_wp_unfiltered_html_comment_disabled').name='_wp_unfiltered_html_comment';}})();</script>
</form>
</div>
Но, в отличие от навигации, здесь можно провести полную кастомизацию через функцию. Для этого заполним массив $args:
ключ fields — массив шаблонов(начиная с тега р), содержащий поля формы комментария за ключами author, email и url;
comment_field — содержит шаблон, непосредственно, поля ввода комментария;
must_log_in — текст для не залогиненных пользователей при обязательном логировании, чтоб оставить коммент;
logged_in_as — текст для залогиненного пользователя;
submit_field — шаблон поля отправки формы.
В принципе, для полной кастомизации этого достаточно, однако, в аргументе есть еще дополнительные поля для более тонкой настройки… и не только вида.
Немного о добавлении своих полей в форму комментирования: для добаления поля — фильтром comment_form_fields изменяй массив полей
после этого экшном wp_insert_comment нужно сохранить данное поле
<?php
add_action('wp_insert_comment', 'wp_insert_custom_comment_field', 10, 2);
function wp_insert_custom_comment_field($id, $comment)
{
if (isset($_POST['my_query'])) {
update_comment_meta($id, 'my_query', sanitize_text_field($_POST['my_query']));
}
}
Список комментариев
Следующая вещь, которая, по сути, есть продолжением формы комментариев — это список ранее оставленных комментариев. Он также выводится функцией
<?php
wp_list_comments($args, $comments);
При использовании стандартной схемы аргумент $comments указывать не нужно. Иначе, $comments = get_comments() в котором нужно формировать свой массив аргументов. При пустом $args wp_list_comments выведет следующую разметку для каждого коммента
Здесь самый широкий, даже, наверное, излишний, способ кастомизации — и все доступно через функцию:
max_depth — максимально разрешенная вложенность комментов;
style — стиль вывода дерева — ul, ol(по умолчанию) или div. Данный аргумент имеет значение для вложенных комментариев, поскольку сама функция должна быть при вызове обнесенная контейнером отдельно;
type — отображаемый тип комментариев;
per_page — в случаи необходимости пагинации — количество комментариев на странице;
page — страница пагинации коммента;
avatar_size — размер аватара комментатора
callback — функция-строитель каждого комментария, без закрывающего тега;
end-callback — строитель закрывающего тега;
walker экземпляр класса-строителя дерева комментариев.
Есть еще много ключей для «мелкой настройки» этой функции, но главное уже приведено. По поводу того, что функции открывающего и закрывающего тега разделены — тут, как и в меню, есть вложенность, и для ее реализации сделано так.
Сайдбар
Сайдбар — это место для вывода виджетов, виджет-зона. У нее, также как и у комментов, свой стандарт для выведения. В шаблоне страницы ставят такой код
<?php
get_sidebar($slug);
данный код подключит sidebar.php из темы; если $slug не пустая строка, то подключит sidebar-{$slug}.php
<?php
if (is_active_sidebar($slug)) {
dynamic_sidebar($slug);
}
$slug здесь это идентификатор виджет-зоны, который был указан при ее регистраци в функции register_sidebar().
Заключение
Рассмотрены все стандартные элементы шаблон страницы записи WordPress. О чем стоит упомянуть еще — так это о том, что в обертке каждого поста должны быть теги
это анкор для ссылки в шапке. Считаю, что рассказал вам обо всех элементах сих страниц, так как дополнительные — это виджеты, вставляемые в сайдбар. И кстати, некоторые из этих элементов встречаются и на страницах других типов.
Навигационное меню это один из 6-и типов навигации представленных в вордпресс. Поскольку 3 это часть «архитектурного ансамбля» других элементов — расскажу о них в будущих статьях; оставшиеся 2 типа — это виджеты со списками последних записей и различных архивов. О них, я считаю, и говорить не стоит…
Прежде, чем вызывать в шаблоне менюху, нужно зарегистрировать меню в системе. Да, в любом шаблоне это уже сделано, но ведь я рассматриваю случай с разработкой собственной! Итак, код регистрации(в functions.php темы)
Данным кодом я регнул локацию для вывода меню с идентификатором «primary» и именем «Primary Menu». В случаи регистрации множества локаций для меню, код будет таким
массив аргументов необязателен, но тогда будет выведено первое непустое меню.
Теперь по аргументам… theme_location идентификатор зоны регистрации меню(если равно primary из примера реги, будет выведено меню, закреплённое за этой зоной в админке — выбран чекбокс). В menu будет слаг, название или id созданного меню.
Ключи container, container_class, container_id и container_aria_label — это тег обертки меню и ее атрибуты class, id и aria-label соответственно. В items_wrap, menu_class, menu_id хранятся разметка для обертки списка и ее атрибуты. Если ты используешь свой шаблон обертки — обязательно делай плейсхолдер %3$s, поскольку именно он будет заменен на пункты списка.
Каждый пункт меню — это ссылка, тег а; before и after — это содержимое перед и после ссылки. link_before и link_after — содержимое до и после текста ссылки(между открывающим и закрывающим тегами а).
Также, есть настройки, не связанные непосредственно с постройкой дерева — fallback_cb, depth и walker. Первая — это название функции, которая будет вызвана при отсутствии меню в БД; вторая — разрешенная глубина меню, третья — класс, который будет строить само меню(нужно указывать объект класса, а не строку). О нем то мы и поговорим более подробно.
Класс Walker. Кастомизация базового шаблона меню
По умолчанию, функция wp_nav_menu использует родной класс Walker_Nav_Menu расширяющий класс Walker. По сути, класс Walker_Nav_Menu содержит методы-шаблону постройки пунктов меню и оберток для них, а родительский класс содержит всю логику посстроения скелета по шаблонам ребенка. Для кастомизации шаблонов, в нем(классе), как и во всем вордпресс, предусмотрены фильтры и экшны; предлагая их кратко рассмотреть.
<?php
class Walker_Nav_Menu extends Walker {
/**
* What the class handles.
*
* @since 3.0.0
* @var string
*
* @see Walker::$tree_type
*/
public $tree_type = array( 'post_type', 'taxonomy', 'custom' );
/**
* Database fields to use.
*
* @since 3.0.0
* @todo Decouple this.
* @var array
*
* @see Walker::$db_fields
*/
public $db_fields = array(
'parent' => 'menu_item_parent',
'id' => 'db_id',
);
/**
* Starts the list before the elements are added.
*
* @since 3.0.0
*
* @see Walker::start_lvl()
*
* @param string $output Used to append additional content (passed by reference).
* @param int $depth Depth of menu item. Used for padding.
* @param stdClass $args An object of wp_nav_menu() arguments.
*/
public function start_lvl( &$output, $depth = 0, $args = null ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "t";
$n = "n";
}
$indent = str_repeat( $t, $depth );
// Default class.
$classes = array( 'sub-menu' );
/**
* Filters the CSS class(es) applied to a menu list element.
*
* @since 4.8.0
*
* @param string[] $classes Array of the CSS classes that are applied to the menu `<ul>` element.
* @param stdClass $args An object of `wp_nav_menu()` arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$class_names = join( ' ', apply_filters( 'nav_menu_submenu_css_class', $classes, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
$output .= "{$n}{$indent}<ul$class_names>{$n}";
}
/**
* Ends the list of after the elements are added.
*
* @since 3.0.0
*
* @see Walker::end_lvl()
*
* @param string $output Used to append additional content (passed by reference).
* @param int $depth Depth of menu item. Used for padding.
* @param stdClass $args An object of wp_nav_menu() arguments.
*/
public function end_lvl( &$output, $depth = 0, $args = null ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "t";
$n = "n";
}
$indent = str_repeat( $t, $depth );
$output .= "$indent</ul>{$n}";
}
/**
* Starts the element output.
*
* @since 3.0.0
* @since 4.4.0 The {@see 'nav_menu_item_args'} filter was added.
*
* @see Walker::start_el()
*
* @param string $output Used to append additional content (passed by reference).
* @param WP_Post $item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $id Current item ID.
*/
public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t = '';
$n = '';
} else {
$t = "t";
$n = "n";
}
$indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
/**
* Filters the arguments for a single nav menu item.
*
* @since 4.4.0
*
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param WP_Post $item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
*/
$args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );
/**
* Filters the CSS classes applied to a menu item's list item element.
*
* @since 3.0.0
* @since 4.1.0 The `$depth` parameter was added.
*
* @param string[] $classes Array of the CSS classes that are applied to the menu item's `<li>` element.
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
/**
* Filters the ID applied to a menu item's list item element.
*
* @since 3.0.1
* @since 4.1.0 The `$depth` parameter was added.
*
* @param string $menu_id The ID that is applied to the menu item's `<li>` element.
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $class_names . '>';
$atts = array();
$atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : '';
$atts['target'] = ! empty( $item->target ) ? $item->target : '';
if ( '_blank' === $item->target && empty( $item->xfn ) ) {
$atts['rel'] = 'noopener noreferrer';
} else {
$atts['rel'] = $item->xfn;
}
$atts['href'] = ! empty( $item->url ) ? $item->url : '';
$atts['aria-current'] = $item->current ? 'page' : '';
/**
* Filters the HTML attributes applied to a menu item's anchor element.
*
* @since 3.6.0
* @since 4.1.0 The `$depth` parameter was added.
*
* @param array $atts {
* The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored.
*
* @type string $title Title attribute.
* @type string $target Target attribute.
* @type string $rel The rel attribute.
* @type string $href The href attribute.
* @type string $aria_current The aria-current attribute.
* }
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
$attributes = '';
foreach ( $atts as $attr => $value ) {
if ( is_scalar( $value ) && '' !== $value && false !== $value ) {
$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
$attributes .= ' ' . $attr . '="' . $value . '"';
}
}
/** This filter is documented in wp-includes/post-template.php */
$title = apply_filters( 'the_title', $item->title, $item->ID );
/**
* Filters a menu item's title.
*
* @since 4.4.0
*
* @param string $title The menu item's title.
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );
$item_output = $args->before;
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
/**
* Filters a menu item's starting output.
*
* The menu item's starting output only includes `$args->before`, the opening `<a>`,
* the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
* no filter for modifying the opening and closing `<li>` for a menu item.
*
* @since 3.0.0
*
* @param string $item_output The menu item's starting HTML output.
* @param WP_Post $item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
* @param stdClass $args An object of wp_nav_menu() arguments.
*/
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
И еще… мало не забыл… немного о методах. Для строительства скелета, есть 4 метода: start_lvl и end_lvl — строят открывающий и закрывающий теги обертки вложенных меню; start_el и end_el — открывающий и закрывающий теги пункта меню.
Глянем фильтры
Буду описывать их по порядку встречи в коде. Первым мы встречаем фильтр «nav_menu_submenu_css_class». Он расположен в методе start_lvl и отвечает за фильтрацию классов оберток дочерних оберток списков. Функции-фильтру передаются 3 параметра — массив классов, вложенность строящейся обертки и массив, переданный при вызове wp_nav_menu. В этом методе хуков больше нет, перейду к следующему.
Метод end_lvl в принципе не может содержать ничего меняющегося, соответственно — хуков он не содержит. Глянем следующий метод — start_el — он то уж содержит много хуков.
Первый хук который мы здесь видим — фильтр nav_menu_item_args. Как можно понять с его кода, предназначен он для фильтрации массива с функции строителя(wp_nav_menu). Фильтр принимает 3 аргумента: массив для фильтрации, объект данных о строящемся пункте меню и его вложенность. Следующий, тоже фильтр, nav_menu_css_class фильтрует классы у тега li, принимает уже 4 аргумента — массив классов, объект пункта, массив из строителя и вложенность. Следующим, идет фильтр атрибута id тега li. Он принимает те же аргументы, что и предыдущий фильтр с той лишь разницей, что вместо массива классов — строка с текущим id(строка, поскольку class`ов может быть много, а id один).
Следующий фильтр nav_menu_link_attributes фильтрует атрибуты тега а. Принимает он те же аргументы, что и фильтр выше, с разницей в место строки id массив атрибутов тега а. Затем идет общий фильтр the_title, которым можно изменить текст ссылки, исходя из текущего текста и id пункта меню. После общего фильтра, идет местный nav_menu_item_title фильтр анкора ссылки. Если честно — не знаю и не понимаю, зачем прилепили общий, ведь этот получает данные для фильтрации, включающие предыдущие; а именно те же, что и с классами, только в место массива классов текст ссылки.
Ну и последним фильтром класса(метод end_el как и end_lvl не содержит никаких данных) является фильтр всего пункта меню. Он, вполне ожидаемо, принимает те же аргументы, что предыдущий, только вместо строки текста передана строка с HTML разметкой пункта.
Создание своего шаблона меню
В принципе, я не знаю что нужно поместить в меню, чего б не можно было сделать фильтрами в дефолтном классе; может, чтоб не наращивать код и с точки зрения оптимизации — выиграть какую-то долю процента…
Но ладно, раз уж это сделать можно — почему об этом не рассказать?… Первым делом — идем в файл wp-includes/class-walker-nav-menu.php, копируем к нам в файл соответствующий класс и переименовываем его. Именно копируем — я то знаю, где-то в буковке ошибся и все — ошибка, ничего не работает… хотя, впрочем, можете и с нуля набрать… только прочти до конца!
Итак, допустим, я решил создать walker-класс который будет строить только такую разметку
Первое, что можно заметить — наличие картинки, возможность выбора коей с админки пока не существует.
Безусловно, можно сделать кнопку у каждого пункта меню, которая бы открывала фрейм с медиатекой(как у миниатюры поста, к примеру); с целью укорочения этой статьи, я приделаю ввод id картинки с медиатеки
Этот код выведет под каждым пунктом меню следующее
но это еще не все. Для того, чтоб эту настройку сохранить — нужен дополнительный код. Я знаю, что каждый пункт меню хранится как запись с типом поста nav_menu_item; следовательно, настройка мета данное поста и и для его сохраниния нужно функцию повесить на экшн save_post_nav_menu_item(save_post_{post_type} в общем случаи)
<?php
add_action('save_post_nav_menu_item', 'save_nav_menu_item');
function save_nav_menu_item()
{
if (isset($_POST['menu-item-thumbnail'])) {
foreach ($_POST['menu-item-thumbnail'] as $key => $value) {
add_post_meta($key, '_item_thumbnail', $value);
}
}
}
здесь я убрал проверки на возможности пользователя, типы данных и прочее, чтоб не убивать читабельность кода; но ты в своих проектах о них не забывай… Еще одна вещь — все это(настройку, но не меню) можно было бы сделать используя плагины.
Теперь время непосредственно walker-класса. Я не буду делать возможности фильтрации данных и максимально захардкоджу код класса в целях экономии места
Ты наверное заметил, что я «убил» в своем классе часть аргументов функции строителя, для публичных проектов так не делай… никогда.
Заключение
Рассмотрел способ создания кастомного меню. Также был рассмотрен базовый класс, который создает и меню; для примера, наведу код, коим можно сделать такие же изменения в коде
Из «коробки» вордпресс имеет 2 типа записей: посты и страницы. Не всегда этого достаточно… Расскажу, как добавить кастомный тип постов, таксономии к нему и метаданные. А также, создадим свои роли пользователей для операций с постами этого типа.
Следуя традиции из кодекса вордпресс, зарегистрируем тип записи book и категории для нее book_cat. Для этого достаточно следующего кода
<?php
add_action('init', 'register_post_types');
function register_post_types()
{
// регистрируем тип записи
register_post_type('book');
// регистрируем таксономию для типа записи
register_taxonomy('book_cat', 'book');
}
По-поводу регистрации первой функции — все ясно-понятно — аргумент это слаг типа постов; по второй функции — первый аргумент это слаг таксономии, второй — к какому типу записей ее прикрепить, если таких типов несколько — укажи массив всех. В документации сказано, что второй аргумент при регистрации таксономии — это экраны, где выводятся данные таксономии, но, по-моему, мое определение ближе к сути.
Итак, данным выше кодом мы зарегистрировали тип постов, но смысл?.. все, что он(код) делает — создает запись о том, что такой тип существует; единственное, что ты можешь с ним сделать — получить его в вызове функции get_post_types()(ну и, соответственно, для таксономии get_taxonomies()).
Наполняем смыслами. Пункт админ-меню, форма создания/редактирования
Смыслы в процессе регистрации как типов так и таксономий, создают второй и третий аргументы функций, соответственно.
Для начала, проявим эти пункты в меню администратора — для этого допишем наш код до следующего состояния
В таком виде будет выведен пункт меню верхнего уровня в админке. Если же ключу show_in_menu присвоить слаг существующей страницы, то пункт появится как дочерний. Дальше,
<?php
add_action('init', 'register_post_types');
function register_post_types()
{
// массив для типа записей
$typeArray = [
'label' => 'Book',
'show_ui' => true,
'show_in_menu' => true
];
// массив для таксономий
$taxArray = [
'label' => 'Genres'
];
// регистрируем тип записи
register_post_type('book', $typeArray);
// регистрируем таксономию для типа записи
register_taxonomy('book_cat', 'book', $taxArray);
}
Теперь о странице создания/редактировании поста. По умолчанию, будут доступны поля задания заголовка и поле редактора TinyMCE для контента. Чтобы добавить стандартные метабоксы, в массив $typeArray, в ключ supports впишите массив с идетификаторами всех необходимых меетабоксов: title, editor, author, thumbnail, excerpt, custom-fields, comments, revisions, page-attributes, post-formats. Для того, чтоб работал page-attributes нужно включить вложенность постов, т.е. hierarchical присвоить true.
С thumbnail и post-formats тоже есть своя особенность; их поддержку нужно включить следующим кодом
Еще, малость не забыл, все тексты в меню, форме постов и в фронте сайта задаются в массиве labels… ну, если дефолтные названия не нравятся.
Дальнейшие настройки типов
Следующее, что я предлагаю включить — так это отображение записей моего типа на странице создания меню. Чтоб это сделать, достаточно в массив $typeArray вписать ключ show_in_nav_menus со значением true.
Кстати, как ты видишь, наша таксономия Genres была туда добавлена сразу же, при ее регистрации.
Затем, предлагаю включить посты этого типа в WP REST API; для этого ключу show_in_rest также нужно присвоить значение true. Как ты заметишь, это действие, также, включит редактор Гутенберга. Сам слаг в АПИ будет равен слагу типа поста, однако, его можно изменить в параметре rest_base. По умолчанию, обслуживать АПИ-запросы к этим постам будет WP_REST_Posts_Controller, но параметром rest_controller_class можно изменить(прочти «WP REST API(дефолтное) — взаимодействие с WP сайтом«).
И, последнее, о настройках типов постов, связанное с админкой — установление прав на создание/редактирование постов. По умолчанию, права на сии действия ровны правам на эти действия с записями(post). Но также, при регистрации типа постов есть возможность задавать свои права на эти действия. Для установления своих прав в capability_type достаточно поставить массив из своих слагов(для единственного и множественного чисел — для примера — book books). При этом будут сгенерированы права: если map_meta_cap false(по умолчанию) то создадутся основные(publish_books, edit_books, edit_others_books и read_private_books), но если true — будут созданы и основные и метаправа(delete_published_books, edit_others_books и т.д.).
Также, массив прав можно задать иначе: capability_type поставить в false И в по ключу capabilities создать массив, в котором переназначить слаги существующим возможностям. И теперь, чтобы воспользоваться новыми правами, установим плагин редактирования ролей(к примеру, User Role Editor). С помощью этих плагинов обновим права администратора и создадим новую роль, которой присвоим права на этот тип записей(ну или дадим их существующей роли). Можно, конечно, сделать это кодом — но зачем, если это единоразовое редактирование настроек в таблице БД, после чего плагин следует выкинуть.
И последняя настройка в админке(если твой тип должен использовать дефолтные таксономии) — это ключу taxonomies передать массив со слагами таксономий.
Настройки типа записей для фронта сайта
Во фронте сайта настроек не так уж и много; прежде, чем что-то делать — нужно разрешить публичный доступ к этому типу: ключу publicly_queryable присвоить true. Второе — это конечно же — включение архива записей. Для этого ключу has_archive присваиваем true, после чего обычным циклом в шаблоне темы archive-book.php можно вывести все записи этого типа. Также, в этом параметре можно указать слаг для архива в место true, при этом шаблон отображения страницы тот же. Сами статьи можно просматривать уже после разрешения публичного доступа, который был дан ранее.
Третье — включим результаты поиска на сайте по статьям этого типа — присвоим exclude_from_search 0. Ну вот, пожалуй, все настройки типа записи.
Настройка кастомных таксономий
По аналогии с типом записей — рассмотрим админку, а потом фронт; в админке можно настроить иерархичность таксономии: ключ hierarchical в true(по умолчание) древовидность включена, false — выключена. Cледующее — настроить вид метабокса для отображения таксономии у ключа meta_box_cb назначить функцию отрисовки. Из коробки есть две такие post_categories_meta_box и post_tags_meta_box
После отображения в форме, будем настраивать отображение в админ-меню. Если тип записи в меню как отдельный пункт то таксономии отображаются как подпункты; их отображение можно скрыть поставив public false. Если же, тип записи, к которой принадлежит таксономия, отображается как подпункт в админ-меню — отобразить страницу таксономии настройкой массива не удастся.
Настройка WP REST API и настройка архивной страницы идентичны настройке в типах постов(вплоть до названия ключей).
Относительно прав для таксономий — их также можно настроить. По ключу capabilities задать массив из 4-ех значений:
после этого, все новые роли нужно добавить в пул ролей с помощью тех же плагинов.
Постмета
Постмета и метадата — одно и тоже — то, что ты увидишь в метабоксе custom fields. Собственно, мету можно создавать и через него, но удобнее это делать через свои метабоксы.
Метабоксы можно создавать используя как плагин ACF(забивая на программирование) так и кодом. Рассмотрим их созданиие написание скрипта…
В данной статье попытаюсь рассказать об особенностях WordPress, тонкостях расширения данной системы. Статья направлена, большей мерой, на улучшение понимания движка; и да, она не будет содержать никакого кода. В общем, незря это — «Краткий гайд по WordPress»…
WordPress — очень гибкая, с открытым исходным кодом, CMS(система управления контентом) написанная на PHP. Можно много чего о ней(системе) писать(если хочешь — глянь википедию), но раз ты это читаешь — значит ты интересуешься ней с точки зрения разработки, и, скорее всего — ты начинающий разработчик; хотя… может и опытному будет кое-что интересно…
WordPress расширяется плагинами — место для создания дополнительного или расширения существующих функций в веб-приложении. Здесь я не ошибочно назвал это веб-приложением, т.к. сайт — странички, видимые в браузере; со стороны сервера они генерируются приложением построенном на да-движке. В принципе, сейчас единицы сайтов — чисто сайты — даже лэндинги строят на движках.
Хранение информации в WordPress
Прежде, чем рассказывать об плагинах и темах, хотел бы поговорить о хранении информации. Вся информация делится на основную и ее мету(это правило не касается только инфы о настройках сайта). Мета-информация — информация о другой информации, или данные, относящиеся к дополнительной информации о содержимом или объекте — как говорит нам википедия.
Таблицы с основными данными не содержат в названии «meta_», и наоборот — метаданные содержат.
При разработке своих плагинов(если те будут хранить или создавать свои таблицы) хорошим тоном будет сохранять подобную логику и, соответствующую архитектуру.
О темах WordPress
Тема в вордпрессе — это всего лишь набор шаблонов страниц. В теме подключаются стили, скрипты; декларируется поддержка некоторых стандартных возможностей, а также, регистрируются виджет-зоны и меню.
Немного о ваянии тем. Есть два пути сознания темы: перейти по пути wp-content/themes/, создать там папку {yout-theme-name}. И второй путь, описаный в настольной книге вордпресс разработчика, и по-этому, я считаю, более верный; иди на сайт UNDERSCORES, введи название своей темы и генерируй! Ты получишь шаблон темы, но не беспокойся — это всего лишь глыба мрамора, с которой тебе предстоит вытесать свою Венеру Милосскую; просто открой нужный шпблон страницы и начинай править ее!
И еще немного о темах: если тема, для корректной работы, нуждается в плагинах — то в тему подключают TGM PA класс, в котором задаются ссылки на плагины.
О плагинах WordPress
Как уже отмечалось — плагин — это функционал приложения. Но несмотря на это ему не возбраняется, также, содержать шаблоны, создающие элементы интерфейса для взаимодействия с своим функционалом.
Глубокое знание WordPress, в основном, нужно для создания плагинов, а также нужны знания PHP, классов, функций WordPress, его хуков и порядка их срабатывания. О всем этом я расскажу в параграфах ниже…
Да, мало не забыл… если плагин требует наличия другого плагина — подключи TGM PA класс, о котором я упоминал ранее.
Функции WordPress
Все функции(функции — это не методы классов, если что…) ворпресса можно разделить четыре типа: функции «достань-вставь» что-то в БД(базу данных), функции построения какого-нибудь элемента(к примеру, меню), функции обработки строк и функции, делающие что-то в системе(регистрируют или добавляют). Статьи о регистраторах в движке:
Касательно функций-строителей — о них нужно отдельно о каждой. Они строят меню, пагинации постов и комментариев, само дерево комментариев и его форму. Строители:
Функции обаботки строки используются для обработки строк до вставки в БД или при выведении строки с БД на экран. О классах(их методах) и административных функциях стоит говорить отдельно, поскольку, они, большей мерой, касаются админки.
Хуки WordPress
Хуки — это специально сделанные «лазейки» для расширения и изменения параметров функций; логически(именно логически, т.к. технически они идентичны) они поделены на действия и фильтры. Логично, что фильтры получают переменные, их преобразуют и возращают для дальнейшей работы функции. Действия же, ничего не возращают, а просто совершают заданный кусочек кода.
В общем, они реализованы как функции одна из которых ставит имя функции в массив с ключом хука; вторая же — извлекает эти имена и вызывает соответсвующие функции.
В прошлой статье мы обсудили стандартное WP REST API, принцип чтения о нем информации и использования. В этой части рассмотрим создание и расширение существующего программного интерфейса.
Результатом выполнения этого кода будет пункт «Books» и новый тип записи «book» на сайте. Но данные о записях данного типа будут не доступные по АПИ, для этого нужно изменить массив аргументов
Данный код создаст стандартный «вордпрессовский » API. Кстати, плюсом к включению АПИ в типе поста будет также включатся редактор Guttenberg в админке.
При этом автоматически будет сгенерированная схема.
Создадим WP REST API к какому-нибудь контенту
На самом деле, этот путь можно использовать и для АПИ типов постов, но зачем?.. ведь разработчики предусмотрели эту возможность, и я за то, чтоб максимально использовать все, что усмотрено разрабами.
Но тем не менее, рассмотрим этот способ. Создадим эндпоинт-функцию и прикрепим к ней маршрут
Здесь «(?Pw+)» — обычное регулярное выражение. В данном случаи, перейдя по пути http://example.com/wp-json/custom/v1/custom/hello увидим hello на экране. Этот маршрут, также, появиться в схеме.
Этот способ можно использовать для API любой сложности, однако, согласно рекомендациям разработчиков — это способ для простейших случаев. Для более сложных случаев будет правильнее создавать класс, расширяющий WP_REST_Controller.
Расширяем WP_REST_Controller
Прежде, чем расширять класс, нужно знать его методы(это касается любого расширяемого класса). Класс WP_REST_Controller имеет более 25 методов, однако, для создания на его базе своего интерфейса достаточно знать 5:
register_routes — регистрация маршрутов(их может быть много);
get_items, get_item, create_item, update_item, delete_item — методы, отвечающие на запросы.
Я не буду ничего усложнять и просто перепишу созданное ранее в виде класса
<?php
function rest_callback() {
$route = new Custom_REST_Controller();
return $route->register_routes();
}
add_action( 'rest_api_init', 'rest_callback');
class Custom_REST_Controller extends WP_REST_Controller {
function register_routes() {
register_rest_route( 'custom/v1', '/custom/(?P<message>d+)', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' )
));
}
function get_item( $request ) {
return new WP_REST_Response( $get['message'], 200 );
}
}
Поскольку мой пример только выводит надпись — я использую только get_item(). Вообще, это минимальный минимум для создания маршрута и эндпоинта.
Редактирование эндпоинтов и схемы. Фильтры
Редактирование результатов работы функции, согласно философии wordpress, происходит по средству фильтров. Данное утверждение касается абсолютно всех функций этого движка.
Работают фильтры так: есть функция
<?php
function some_func( $args ) {
// do something
$args = apply_filters( 'slug', $args );// filter install
// do something
}
Если в функции установлен фильтр, то передаваемые аргументы в этом фильтре можно изменить
<?php
function filter_func( $args ) {
// do something with args
return $args;
}
add_filter( 'slug', 'filter_func', 10, 1 );
Здесь 10 — приоритет выполнения функции фильтра, 1 — количество принимаемых аргументов. Приоритет может изменятся если функций-фильтров несколько, чем больше это число — тем позже будет срабатывать фильтр. И еще, регистрация фильтра(add_filter) должна происходить раньше, чем вызов функции, которую нужно отфильтровать.
Но хватит теории, изменим программный интерфейс. Для начала, нужно знать слаг фильтра, его можно найти в документации вордпресс.
Возьмем, для примера, функцию-эндпоинт, которая подготавливает присланный запрос к созданию нового пользователя. В ней есть следующий крючок для фильтра
В этой статье проведено ознакомление с принципами изменения и дополнения WP REST API. Если нужно только работать с дефолтным АПИ — читайте. Для получения более подробной информации рекомендую прочесть REST API Handbook. Это, так сказать, информация из первых рук, т.е. разработчиков ВП АПИ.
Ранее в статья(здесь и здесь) я говорил о дефолтном WP REST API и о методах его кастомизации. Пришло время поговорить об одном из способов его применения; т.е. в прошлых статьях было «о теории», в этой — «о практике»… о ReactJS в WordPress.
ReactJS — JavaScript-библиотека для создания пользовательских интерфейсов — как определяет официальный сайт ReactJS.
Несмотря на всю сложность и запутанность официальной документации и объяснений «для чего он и с чем его едят» — все просто; может быть, меня обозвут нубом либо дураком, «не въехавшим в тему» — но эта библиотека нужна для одной цели: отрисовать HTML страницы. В отличии от многих js-фреймворков, здесь нету:
Системы событий (отличную от нативных DOM событий);
Работы с AJAX;
Какой либо слой данных;
Promises.
Всё, что делает React, это рисует страницу из полученными данными — по сути, то же, что PHP-файл с HTML-тегами. Возможно ты спросиш — зачем React в вордпрессе? Зачем — я не нашел, мое мнение — это способ снять часть нагрузки с сервера и перенести на комп пользователя; ведь проще отдать браузеру кучку статических файлов и JSON-строку, нежели построенный HTML. Хотя, может это(мое, ранее изложенное, предположение) полная чухня. Еще один способ применения, который я вижу, — это вставка кастомного HTML в места, в которые его нельзя вписать(сгенерированным плагином, к примеру).
ReactJS в WordPress — теория
ReactJS вмонтирован в WP лишь в версии 5 как основа для работы редактора Гутенберг, однако, мы можем его использовать. Работает это(ReactJS в WordPress) следующим образом: движок отдает JSON-строк, JS и CSS файлы, а также HTML страницу. JS скрипт формирует HTML шаблон, который будет вставлен в страницу.
Для включения ReactJS в тему необходимо к JS-файлу, в котором вы будете писать шаблоны, в качестве зависимости(при подключении его функцией wp_enqueue_script()) указать wp-element
При этом будут загружены все компоненты библиотеки и абстрактный слой Element. Данный слой нужен(цитирую настольную книгу вордпресс -разработчика):
Во многих приложениях, особенно в тех, которые расширяются великим множеством плагинов, как в случае с WordPress, целесообразно создавать интерфейсы для стороннего кода. Идея заключается в том, что если когда-либо возникнет необходимость изменить или даже заменить базовую реализацию, это можно сделать без катастрофических последствий для зависимого кода, если интерфейс остается неизменным.
Он предоставляет механизм для защиты исполнителей, опуская функции с неопределенным будущим ( createClass, PropTypes).
Это помогает избежать несовместимости между версиями, гарантируя, что каждый плагин работает с единой централизованной версией кода.
Element представлен в виде wp.element и имеет 2 метода: render() и createElement().
ReactJS в WordPress — практика
Поскольку, в основе ReactJS — можно писать код используя его методы, однако, если использовать wp.element, следующий код
Теперь, поговорим о получении данных, чтоб наш шаблон был многоразового использования. Нужно получить данные в JSON формате — здесь то и понадобятся нам знания с предыдущих статей о WP REST API.
Допустим, нужно вывести данные поста с идентификатором 50. В схеме(как ее найти и прочесть было здесь) находим путь к этим данным и по нему получим эти данные
<?php
$request = new WP_REST_Request('GET', '/wp/v2/posts/50');
$result = rest_do_request($request);
В общем, вот полный PHP скрипт, необходимый для получения данных о посте с идентификатором 50 в шаблон страницы
<?php
add_action('wp_enqueue_scripts', 'child_scripts');
function child_scripts()
{
$request = new WP_REST_Request('GET', '/wp/v2/posts/50');
$result = rest_do_request($request);
wp_enqueue_script('my-react-template', get_stylesheet_directory_uri() . '/react.js', ['wp-element'], '', 1);// подключение файла с js шаблоном
wp_localize_script('my-react-template', 'data', $result->get_data());// вывод данных в js обьекте data
}
а вот JS скрипт, использующий эти данные(в данном случаи — вывод заголовка поста)
Мы с вами рассмотрели использование ReactJS в вордпресс, а также, способы получения данных. Конечно, это не все, я не рассмотрел настройку Babel для использования JSX шаблонов; не рассмотрел использование переменной $wp_query для «вытягивания» информации о текущем массиве доступных данных на странице…