Всем привет и здравствуйте! Сегодня речь пойдет об пространстве имен(nаmespaсe). Эта фича чисто объектно-ориентированного программирования; несмотря на то, что пространство имен есть во многих языках программирования(C++, python, java и т.д.), код, приведенный в примерах, будет на php.
Идентификатор, определённый в пространстве имён, ассоциируется с этим пространством. Один и тот же идентификатор может быть независимо определён в нескольких пространствах. Таким образом, значение, связанное с идентификатором, определённым в одном пространстве имён, может иметь (или не иметь) такое же значение, как и такой же идентификатор, определённый в другом пространстве. Языки с поддержкой пространств имён определяют правила, указывающие, к какому пространству имён принадлежит идентификатор (то есть его определение).
Такое определение дает нам википедия, однако оно, как по мне, достаточно сложное для понимания. Я же определю более просто – это дополнительная координата для обращения к классу. Рассмотрим примеры для большего понимания…
Примеры
Для начала, приведу жизненный пример. Допустим есть группа людей в которой есть несколько человек с одним и тем же именем(Иван). Третий, из этой группы, обращается к Ивану и те не понимают к какому. Так вот, их фамилия и отчество(или еще какие-то координаты) как раз и будут их пространством имен.
Теперь применительно к коду: 2 программиста написали по одинаково названному классу
<?php
class ClassName {
// some methods
}
и отправили третьему. Тот, должен использовать эти классы для сборки приложения – но как к ним обращаться? Вариант №1 – на берегу договориться о наименовании классов – это подойдет для небольшого их количества, но если их десятки и сотни? Вариант №2 -правильный вариант – договориться об использовании пространств имен
<?php
namespace developer1;
class ClassName {
// some methods
}
<?php
namespace developer2;
class ClassName {
// some methods
}
вот так это будет выглядеть в php. Далее, третьему нужно использовать эти классы в своем коде. Чтоб обратиться к такому классу, нужно сначала указать пространство имен, а затем имя класса
<?php
$var1 = new developer1ClassName();
$var2 = new developer2ClassName();
это годится при единичном вызове каждого класса, но что если ссылок на каждый класс множество? каждый раз писать пространство имени класса? По меньшей мере, это не удобно. Для этого есть директива use as (для php, в других языках есть аналоги)
<?php
use developer1ClassName as ClassName1;
use developer2ClassName as ClassName2;
$var1 = new ClassName1();
$var2 = new ClassName2();
По сути, это работает как “локальное переименование”, т.е классы ClassName1 и ClassName2 будут существовать только в пределах файла с этими директивами. В случаи если начальные классы имеют разные названия, директиву use as можно сократить до use
<?php
use developer1ClassName;
$var = new ClassName();
Работать это будет так: при обращении к классу ClassName будет проверены все директивы use и если есть та, которая на конце имеет такое же имя класса, будет обращение к пространству этого имени.
Автозагрузка классов
Обычно, чтоб получить доступ к файлу, используют функцию на подобие require(), применяя ее к каждому файлу. Когда проект большой и требует подключения множества файлов, а также написан с соблюдением принципов ООП(что в нынешних реалиях маст хэв,
т.е. обязательно) это неудобно. В этом случаи удобно использовать “автозагрузку классов”. Дело в том, что при обращении к незнакомому классу, возбуждается функция, которой передается название требуемого класса(если известно его имя пространства – будет передано и оно).
Таким образом, объявив эту функцию раньше вызова неизвестного класса, можно подключить ее файл. Реализую это на php.
Сначала приведем структуру директорий и названий классов в соответствие с namespace и именем класса. Есть две функции для подобной работы – __autoload()(устаревшая с php 7.2) и spl_autoload_register()(появившаяся в php 5.0). Также, с php 5.3 добавлены анонимные функции и код автозагрузчика будет выглядеть так
Использование анонимной функции необязательно, можно, как аргумент, указать имя функции-загрузчика.
Заключение
В этой, сравнительно небольшой, статье рассмотрены пространства имен для классов. Наведены 2 способа их использования и, несмотря на то, что код приведен лишь для языка php, для других – эти принципы работы, также, сохраняются.
Редактор 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. Это, так сказать, информация из первых рук, т.е. разработчиков ВП АПИ.
HTML тег meta — тег, предназначенный поисковым роботам. Именно они(теги) являются основными источниками информации о содержимом страницы при индексации ботами. В множественном числе(о теге meta)потому, что их много видов и все они нужны. И кста, когда вы нажимание кнопку «Поделится» — внешний вид репоста формируется специальными метатегами. Нет, вроде как, могут и обычные использоваться, но мы о всех технических моментах ниже и поговорим.
HTML тег meta — одиночный тег который размещается исключительно блоке head. Имеет четыре атрибута: name, content, charset и http-equiv. Первые два — те, которые и образуют мета-данные страницы
Приблизительно так выглядит html-разметка страницы. Кодировку(charset) достаточно указать единожды.
А теперь перейдем к рассмотрению значений атрибута name, ведь он, имхо, самый важный(на пару с content) парень. Он может иметь 7 значений:
title — сео название документа, до 70 символов;
application-name — в случаи, если это веб-приложение — то в content его название;
author — автор данного документа:
description — описание документа. до 160 символов;
generator — если документ сгенерирован программой — название этой программы;
keywords — ключевые слова, до 130 символов, до 10 фраз через кому;
viewport — поиска и индексации страницы не касается, от него зависит отображение страницы при масштабировании. Крайне рекомендую ко всем страницам, в head добавить
Как пишет support google «каждый атрибут контента может содержать до 1024 символов. Пользовательский поиск может обрабатывать до 50 метатегов для каждой страницы…»
Протокол Open Graph
Протокол Open Graph позволяет любой веб-странице стать полноценным объектом в социальных сетях. Например, протокол используется на Facebook, чтобы разрешить любой веб-странице иметь такую же функциональность, как и любой другой объект на Facebook.
Иначе говоря — это метаданные, по которым современные социальные сети строят отображение репоста.
В этом протоколе есть свой атрибут, который полностью замещает name, — это property. У этого атрибута также есть свои значения:
Основные:
og:title — аналог title в name;
og:type — тип объекта от значения этого атрибута зависит наличие других свойств;
og:image — значение этого метатега должно содержать ссылку на изображение(репоста);
og:url — канонический урл, говоря простыми словами — постоянная ссылка на пост.
Дополнительные(необязательные, но рекомендуемые)
og:audio — ссылка на аудиофайл страницы, если есть;
og: description — аналог description в name;
og:determiner — слово, которое появляется перед названием этого объекта в предложении. Строковое значение, которое является членом перечисления(an, a, the…);
og:locale — тег физического местоположения в формате language_TERRITORY(en_US) ;
og:locale:alternate — альтернативные локали, каждая с которых записывается в свой тег;
og:site_name — если это страница сайта, то здесь — название сайта;
og:video — ссылка на видеофайл, если он есть.
Разметка Twitter Cards
Это отдельная разметка метатегов созданная Twitterом для построения твитов. Это те же мета-теги, что и в обычном HTML.
далее, должна идти одна из двоих строк, в зависимости от-того, у кого есть твит-аккаунт
<--! аккаунт есть у проекта -->
<meta name="twitter: site" content="@название аккаунта"/>
<--! аккаунт есть у автора страницы -->
<meta name="twitter: creator" content="@название аккаунта"/>
После этого, твиттер воспринимает нижеследующую обычную Open Graph разметку.
Заключение
Здесь рассмотрено техническое использование тега <meta>. Он который является основным, но не единственным(теги a, h%d, p…), для СЕО страницы. Однако же, если содержимого метатегов не будет в заголовках(h1, h2…) или параграфах(p) — поисковые роботы обидятся на сайт и в результатах поиска вы его не увидите. И, кстати, если вам нужно больше инфы по Open Graph — загляните сюда.
Ранее в статья(здесь и здесь) я говорил о дефолтном 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 для «вытягивания» информации о текущем массиве доступных данных на странице…
Эта задача крайне редкая для сайтов на вордпресс. Скажу прямо — в моей практика не было задач(возможно, я нуб или это потому, что апи делают серьезным проектам, а на вордпресс, зачастую, клепают более простые). В любом случаи, я считаю это нужно знать. Ведь это дает возможность, допустим, создать приложение по управлению блогом, не входя в блог(сайт) или тему на ReactJS. В этой статье я расскажу о WP API, которое получается при установке свежего вордпресс версии 5.2.4.
API (программный интерфейс приложения, интерфейс прикладного программирования) (англ. application programming interface) — описание способов (набор классов, процедур, функций, структур или констант), которыми одна компьютерная программа может взаимодействовать с другой программой.
В нашем случаи, это способ взаимодействия между сайтом на ВП и приложением. Как и везде(по крайней мере, других вариантов я не встечал), это(обмен информации) реализовано в JSON-формате.
Используем WP REST API
Для использования АПИ сайта нужно знать пути к точкам входа(Routes & Endpoints). Эту информацию мы можем получить следующим образом
<?php
$url = 'http://example.com/wp-json/';//если ЧПУ ссылки
// не включены замените 'wp-json' на 'rest_route='
// запрос в общем случаи
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
$result = curl_exec( $ch );
// в случаи, если вы делаете запрос с админки
// или с одного ВП сайта в другой(есть доступ
// к классу WP_REST_Request)
$request = new WP_REST_Request( 'GET', $url );
$result = rest_do_request( $request );
В результате, мы получим JSON-строку, декодировав которую(json_decode) — объект, содержащий всю информацию об АПИ сайта. Конечно, можно и в REST API Handbook посмотреть(там указаны дефолтные пути), но их можно изменять — об этом потом.
Рассмотрим маршрут чтения создания и редактирования поста.
Маршруты и точки входа
Рассмотрим маршрут ‘/wp/v2/posts’. Чтоб получить информацию об этом объекте — перейдем по ключу routes->{‘/wp/v2/posts’}. Вот полный объект содержащийся по этой ссылке(версия вордпресс 5.2.3, плагины отсутствуют)
stdClass Object
(
[namespace] => wp/v2
[methods] => Array
(
[0] => GET
[1] => POST
)
[endpoints] => Array
(
[0] => stdClass Object
(
[methods] => Array
(
[0] => GET
)
[args] => stdClass Object
(
[context] => stdClass Object
(
[required] =>
[default] => view
[enum] => Array
(
[0] => view
[1] => embed
[2] => edit
)
[description] => Рамки в которых сделан запрос, определяют поля в ответе.
[type] => string
)
[page] => stdClass Object
(
[required] =>
[default] => 1
[description] => Текущая страница коллекции.
[type] => integer
)
[per_page] => stdClass Object
(
[required] =>
[default] => 10
[description] => Максимальное число объектов возвращаемое в выборке.
[type] => integer
)
[search] => stdClass Object
(
[required] =>
[description] => Ограничить результаты до совпадающих со строкой.
[type] => string
)
[after] => stdClass Object
(
[required] =>
[description] => Ограничить ответ записями опубликованными после заданной ISO8601 совместимой даты.
[type] => string
)
[author] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Ограничить выборку записями определенных авторов.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[author_exclude] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Убедиться что выборка исключает записи назначенные определенным авторам.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[before] => stdClass Object
(
[required] =>
[description] => Ограничить ответ записями опубликованными до заданной ISO8601 совместимой даты.
[type] => string
)
[exclude] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Убедиться что выборка исключает определенные ID.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[include] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Ограничить выборку до определенных ID.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[offset] => stdClass Object
(
[required] =>
[description] => Сдвиг выборки на определенное число объектов.
[type] => integer
)
[order] => stdClass Object
(
[required] =>
[default] => desc
[enum] => Array
(
[0] => asc
[1] => desc
)
[description] => Упорядочить сортировку атрибута по возрастанию или убыванию.
[type] => string
)
[orderby] => stdClass Object
(
[required] =>
[default] => date
[enum] => Array
(
[0] => author
[1] => date
[2] => id
[3] => include
[4] => modified
[5] => parent
[6] => relevance
[7] => slug
[8] => include_slugs
[9] => title
)
[description] => Сортировать коллекцию по атрибуту объекта.
[type] => string
)
[slug] => stdClass Object
(
[required] =>
[description] => Ограничить выборку до записей с одним или несколькими установленными конкретными ярлыками.
[type] => array
[items] => stdClass Object
(
[type] => string
)
)
[status] => stdClass Object
(
[required] =>
[default] => publish
[description] => Ограничить выборку до записей с одним или несколькими установленными статусами.
[type] => array
[items] => stdClass Object
(
[enum] => Array
(
[0] => publish
[1] => future
[2] => draft
[3] => pending
[4] => private
[5] => trash
[6] => auto-draft
[7] => inherit
[8] => request-pending
[9] => request-confirmed
[10] => request-failed
[11] => request-completed
[12] => any
)
[type] => string
)
)
[categories] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Ограничить выборку до объектов с установленным указанным элементом в таксономии categories.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[categories_exclude] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Ограничить выборку до всех объектов кроме тех, что имеют указанные элементы назначенные в таксономии categories.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[tags] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Ограничить выборку до объектов с установленным указанным элементом в таксономии tags.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[tags_exclude] => stdClass Object
(
[required] =>
[default] => Array
(
)
[description] => Ограничить выборку до всех объектов кроме тех, что имеют указанные элементы назначенные в таксономии tags.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[sticky] => stdClass Object
(
[required] =>
[description] => Ограничить выборку прилепленными объектами.
[type] => boolean
)
)
)
[1] => stdClass Object
(
[methods] => Array
(
[0] => POST
)
[args] => stdClass Object
(
[date] => stdClass Object
(
[required] =>
[description] => Дата публикации объекта, по временной зоне сайта.
[type] => string
)
[date_gmt] => stdClass Object
(
[required] =>
[description] => Время публикации объекта, по GMT.
[type] => string
)
[slug] => stdClass Object
(
[required] =>
[description] => Буквенно-цифровой идентификатор для объекта уникальный для его типа.
[type] => string
)
[status] => stdClass Object
(
[required] =>
[enum] => Array
(
[0] => publish
[1] => future
[2] => draft
[3] => pending
[4] => private
)
[description] => Именованный статус для объекта.
[type] => string
)
[password] => stdClass Object
(
[required] =>
[description] => Пароль для защиты содержания и отрывка.
[type] => string
)
[title] => stdClass Object
(
[required] =>
[description] => Название для объекта.
[type] => object
)
[content] => stdClass Object
(
[required] =>
[description] => Содержимое объекта.
[type] => object
)
[author] => stdClass Object
(
[required] =>
[description] => ID автора объекта.
[type] => integer
)
[excerpt] => stdClass Object
(
[required] =>
[description] => Отрывок объекта.
[type] => object
)
[featured_media] => stdClass Object
(
[required] =>
[description] => ID избранного медиа для объекта.
[type] => integer
)
[comment_status] => stdClass Object
(
[required] =>
[enum] => Array
(
[0] => open
[1] => closed
)
[description] => Открыты ли комментарии для объекта.
[type] => string
)
[ping_status] => stdClass Object
(
[required] =>
[enum] => Array
(
[0] => open
[1] => closed
)
[description] => Принимает ли объект уведомления.
[type] => string
)
[format] => stdClass Object
(
[required] =>
[enum] => Array
(
[0] => standard
[1] => aside
[2] => chat
[3] => gallery
[4] => link
[5] => image
[6] => quote
[7] => status
[8] => video
[9] => audio
)
[description] => Формат для объекта.
[type] => string
)
[meta] => stdClass Object
(
[required] =>
[description] => Мета поля.
[type] => object
)
[sticky] => stdClass Object
(
[required] =>
[description] => Считать ли объект прилепленным или нет.
[type] => boolean
)
[template] => stdClass Object
(
[required] =>
[description] => Файл темы используемый для показа объекта.
[type] => string
)
[categories] => stdClass Object
(
[required] =>
[description] => Элементы назначенные объекту в таксономии category.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
[tags] => stdClass Object
(
[required] =>
[description] => Элементы назначенные объекту в таксономии post_tag.
[type] => array
[items] => stdClass Object
(
[type] => integer
)
)
)
)
)
[_links] => stdClass Object
(
[self] => http://wordpress.loc/wp-json/wp/v2/posts
)
)
Проанализирую данный объект, кстати, аналогично можно прочесть и прочие объекты WP REST API.
Под ключем _links->self содержится абсолютная ссылка для запросов к апи постов этого сайта. methods — массив методов, которыми можно отправить запрос, endpoints — массив, содержащий описание возможных аргументов и методов их отправки; анализ одного из возможных аргументов — например context: required — обязателен ли, default дефолтное значение, enum массив возможных значений, description описание аргумента, type тип переменной, содержащейся в аргументе.
Context — массив возможностей для этого маршрута. В данном маршруте можно просматривать(view), вставлять(embed) и обновлять(edit) пост. Но с обновлением поста не все так просто — в конец маршрута нужно дописать идентификатор поста — а это уже новый маршрут, по этому я не буду это действие рассмартивать.
Объект endpoints содержит два массива потому, что первый описывает запрос на извлечение данных, второй — добавление.
Схема ответа
Знать, как составить запрос и куда его отправлять — это, хоть и большая, но все же часть дела. Другая часть — это обработка ответа, для чего нужно знать его схему. Данный код получает эту схему
<?php
$url = 'http://example.com/wp-json/wp/v2/posts';
// запрос в общем случаи
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, 'OPTIONS' );
$result = curl_exec( $ch );
// в случаи, если вы делаете запрос с админки
// или с одного ВП сайта в другой(есть доступ
// к классу WP_REST_Request)
$request = new WP_REST_Request( 'OPTIONS', $url );
$result = rest_do_request( $request );
декодировав строку получим объект. В объекте по ключу schema получим схему; она представляет собой ключ в объекте и тип данных, которую принимает переменная.
Запросы и ответы
Получим список постов, в которых есть слово «привет» — произведем обычный поиск, а также добавим условие: чтоб автором постов был автор с id = 1
<?php
$url = 'http://example.com/wp-json/wp/v2/posts';
$args = array(
'search' => 'привет',
'author' => array( 1 )
);
// запрос в общем случаи
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url . '?' . http_build_query( $args ) );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
$result = curl_exec( $ch );
// в случаи, если вы делаете запрос с админки
// или с одного ВП сайта в другой(есть доступ
// к классу WP_REST_Request)
$request = new WP_REST_Request( 'GET', $url );
$request->set_param( 'search', 'привет' );
$request->set_param( 'author', array( 1 ) );
$result = rest_do_request( $request );
Создадим пост с заголовком «АПИ». Для этого нужна аутентификация, которая на данную версию(5.2.3) в ядро ВП не включено. Чтобы создать эту возможность — установите плагин WordPress REST API Authentication(или ему подобные). В плагине(конкретно в этом) есть настройки; я выбрал Basic Auth и вариант Username : Password , по этому мои заголовки в коде такие
<?php
$url = 'http://example.com/wp-json/wp/v2/posts';
$args = array(
'title' => 'АПИ',
);
$headers = array(
'Authorization:Basic ' . base64_encode( 'Username : Password' )
);
// запрос в общем случаи
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt($ch, CURLOPT_POSTFIELDS, $args );
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers );
$result = curl_exec( $ch );
// в случаи, если вы делаете запрос с админки
// или с одного ВП сайта в другой(есть доступ
// к классу WP_REST_Request)
$request = new WP_REST_Request( 'POST', $url );
$request->set_param( 'search', 'привет' );
$request->set_headers( $headers );
$result = rest_do_request( $request );
При успешном создании поста будет отдан объект данных о посте, зашифрованный в JSON-строку. Если же произошла ошибка при создании — вернет информацию об ошибке.
Заключение
Здесь было рассмотрено WordPress REST API «из коробки»(если не учитывать плагин аутентификации, который, я надеюсь, скоро включат в ядро) движка. В одной из следующих статей рассмотрим возможности кастомизации маршрутов, схем и точек входа — в общем — WP API.