В основе любого плагина лежит PHP код, логика его работы и правильное использование API WordPress. Задачей любого плагина должно быть создание нового функционала с минимально возможной нагрузкой, совместимость с ядром WordPress и грамотная работа кода в условиях постоянно-обновляющейся системы.
Создание плагина
Чтобы создать плагин WordPress нужно:
-
Создать папку плагина. В ней будут лежать файлы нашего плагина. Создается она в папке всех плагинов WordPress. Например, создаем папку
/wp-content/plugins/my-plugin-name
. -
Создать главный файл плагина. Рекомендуется чтобы название этого файла совпадало с названием папки плагина. Например,
my-plugin-name.php
→/wp-content/plugins/my-plugin-name/my-plugin-name.php
. -
Создать описание плагина – заголовки плагина. Они нужны чтобы wordpress распознал плагин как плагин, иначе он просто не будет работать. В самое начало главного файла плагина, нужно добавить php комментарий, где указать имя плагина:
<?php /* * Plugin Name: Мой первый плагин */
Готово! Теперь наш плагин можно увидеть в разделе Плагины в админ-панели.
Если плагин является всего одним файлом как Hello Dolly, то его можно поместить прямо в папку плагинов – /plugins/hello.php
и все будет работать. Однако, рекомендуется соблюдать стандарт: файлы плагина должны находиться в своей собственной папке, а главный файл плагина должен иметь название папки плагина.
Дополнительные данные в заголовке
Чтобы плагин начал работать достаточно указать только Plugin Name
(название плагина). Но можно также указать другие параметры плагина – это улучшит отображение плагина в консоли WordPress.
Build In Post
<?php /** * Plugin Name: Название плагина * Description: Описание плагина желательно не очень длинное (140 символов) * Plugin URI: Ссылка на страницу плагина * Author URI: Ссылка на автора * Author: Имя автора * Version: Версия плагина, например 1.0 * * Text Domain: ID перевода, указывается в load_plugin_textdomain() * Domain Path: Путь до файла перевода. * Requires at least: 2.5 * Requires PHP: 5.4 * * License: GPL2 * License URI: https://www.gnu.org/licenses/gpl-2.0.html * * Network: Укажите "true" для возможности активировать плагин для сети Multisite. * Update URI: https://example.com/link_to_update */ // код плагина
- Plugin Name:(обязательный)
- Название плагина, которое отображается в списке плагинов в админке.
- Description:
- Краткое описание плагина, которое отображается в разделе Плагины в в админке. Рекомендуется не превышать 140 символов.
- Version:
-
Номер текущей версии плагина, например, 1.0 или 1.0.3.
При установке версии имейте ввиду, что WP для сравнения версий использует функцию version_compare(). Поэтому при изменении версии убедитесь что новая версия будет выше. Например, 1.02 больше чем 1.1
- Plugin URI:
- Домашняя страница плагина, которая может быть на WordPress.org или на вашем собственном сайте.
- Author:
- Имя автора плагина. В списке может быть более одного автора.
- Author URI:
- Сайт автора или профиль на другом веб-сайте, например, WordPress.org.
- Requires at least:
- Самая низкая версия WordPress, на которой будет работать плагин. Например: 2.5.
- Requires PHP:
- Минимальная требуемая версия PHP. Например: 5.4.
- License:
- Короткое имя лицензии плагина, например GPL2. Более подробную информацию о лицензировании можно найти на WordPress.org.
- License URI:
- Ссылка на лицензию, например, https://www.gnu.org/licenses/gpl-2.0.html .
- Text Domain:
- Идентификатор перевода (домен локализации) используется в функциях локализации и в названии файла перевод mo. Подобрее смотрите цитату здесь.
- Domain Path:
- Нужен если файл перевода находится не в той же папке, в которой находится текущий файл. Например, .mo файл находится в папке
/myplugin/languages
, а файл плагина в/myplugin/myplugin.php
, тогда тут указываем/languages
. - Network:
- Укажите “true” чтобы плагин обязательно активировался по всей сети сайтов в MU сборке WordPress. Это сделает невозможным активировать плагин для одного сайта, если активирована мультисеть.
- Update URI:
- URL для обновления сайта. Используется в функции wp_update_plugins(). Из домена будет создан хук update_plugins_(hostname).
Шаблон для создания плагина WordPress
Чтобы не создавать файлы и структуру с нуля, можно использовать шаблон для создания плагина:
- WordPress Plugin Boilerplate – генератор шаблона, где указывается название плагина, которое будет использовано в названиях папок, классов и функций – WordPress Plugin Boilerplate Generator.
Шаблон представляет собой стандартную и организованную объектно-ориентированную основу.
Шаблон придерживаются стандартов PHP кода для WordPress.
Используя такой подход, можно быть уверенным в более четкой и понятной структуре плагина. Так можно сгенерировать основу и затем просто удалить все ненужные файлы, оставив структуру папок – структура важна!
Хуки в плагине
По всему ядру WordPress расположено множество хуков. Хуки позволяют подключиться в определенных местах к коду ядра WordPress, чтобы изменить его поведение, при этом не затрагивая файлы ядра.
Существует два типа хуков в WordPress:
- События (actions) — позволяют добавлять или изменять функционал WordPress.
- Фильтры (filters) — позволяют изменять данные.
Хуки нужны не только для разработчиков плагинов, но и тем кто будет использовать ваш плагин. Хуки используются везде: в самом ядре WordPress, в плагинах и темах. Именно хуки делают WordPress таким гибким.
Непосредственно к плагинам относятся три функции:
-
register_activation_hook() – регистрирует функцию, которая будет срабатывать во время активации плагина.
Используется для добавления настроек плагина и т.п. -
register_deactivation_hook() – регистрирует функцию, которая должна запускаться после деактивации плагина.
Используется для удаления временных данных плагина. - register_uninstall_hook() – регистрирует функцию, которая вызывается при удалении плагина.
Используется при удалении плагин для удаления всех данных плагина: в настройках, в файлах, в базе данных и т.д.
Можно создавать свои собственные хуки в исходном коде плагина с помощью do_action() или apply_filters(). Они позволят пользователям вашего плагина расширить его возможности, точно также как WordPress позволяет вам расширять свои возможности.
Представьте, что вы создали плагин и его использует другой разработчик, но при этом вы продолжаете улучшать плагин и выпускаете новые версии. При обновлении все предыдущие файлы плагина затираются новыми. Так, если другой разработчик вносил правки прямо в файлы вашего плагина, то его изменения будут утеряны. Чтобы этого не случилось, нужны хуки, подключаясь к которым другой разработчик сможет расширить ваш плагин, не изменяя кода самого плагина.
PHP Функции и WordPress API
WordPress предоставляет ряд API. API могут значительно упростить написание кода. Т.е. не нужно изобретать колесо, когда оно уже есть.
Некоторые API WordPress:
- API настроек – упрощает создание и управление опциями плагина, которые сохраняются в базу данных.
- plugin_dir_url() — Получает URL папки (директории, каталога), где находится указанный файл плагина (со слэшем на конце).
- register_activation_hook() — Регистрирует функцию, которая будет срабатывать во время активации плагина.
- register_deactivation_hook() — Регистрирует функцию, которая будет запускаться после деактивации плагина.
- register_uninstall_hook() — Регистрирует функцию, которая вызывается при удалении плагина, чтобы почистить все следы прибывания плагина в системе.
- HTTP API – упрощает создание HTTP запросов в PHP. Отличная замена велосипедов на cURL.
Как WordPress загружает плагины
При активации плагина WordPress, записывает путь на его главный файл в опцию active_plugins. Далее при загрузке любой страницы (админки и фронта) WordPress просто подключает все файлы из опции active_plugins (пути на них там хранятся в виде массива). Смотрите как это выглядит:
$active_plugins = get_option( 'active_plugins' ); /* Получим в $active_plugins Array ( [0] => hello-dolly/hello-dolly.php [1] => backupwordpress/backupwordpress.php [2] => democracy-poll/democracy.php [3] => disable-emojis/disable-emojis.php ) */
Из всего этого следует: просто своим присутствием плагины не влияют на скорость работы сайта (за исключением подключения файла плагина, а это супер быстрая операция). Неактивные плагины не влияют вообще никак. Подробнее читайте здесь.
Репозиторий плагинов WordPress
Плагин может быть личный (создается только для одного сайта), а может быть публичный (выкладывается в репозиторий плагинов WordPress).
Требования к личному плагину, обычно минимальны, а вот с публичным все сложнее, нужна лицензия, поддержка, совместимость, локализация и прочее. Поэтому публичные плагины создавать в разы сложнее.
Если планируете отправлять плагин на WordPress.org, нужно следовать требованиям к заголовку плагина WordPress.
Лицензия сообщает пользователям, как они могут использовать код плагина в своих целях. Для поддержания совместимости с ядром WordPress рекомендуется выбрать лицензию, работающую с GNU General Public License (GPLv2+).
Время на прочтение
12 мин
Количество просмотров 6.3K
Перед началом…
Всем привет! Это серия постов будет иметь много различных тем, но и я так-же не забыл о написании ядра на Rust, скоро будут продолжения).
Немного понятий
Я думаю, что стоит начать с некоторой основной информацией по созданию плагинов, а именно:
-
Все плагины основываются на Bukkit – API для плагинов, которая может немного отличаться в зависимости от версий.
-
Плагины пишутся в основном на Java или иногда на Kotlin.
-
Некоторая информация плагина исключительно для ядра храниться в plugin.yml, где можно найти версию плагина, главный класс плагина и многое другое.
-
В стандартных случаях плагинам хватает обычного, но существуют моменты, где могут понадобится NMS – net.minecraft.server или же API к самому Minecraft.
-
Плагины без труда могут использовать API других плагинов, если имеется такая возможность, например PlaceholderAPI и Vault.
-
Почти всегда, когда ядро обращается к плагину – выполняется в синхронном потоке сервера.
Думаю этой малой информации может хватить для того, чтобы мы могли подготовить основу нашего плагина и небольшое окружение для работы со своим плагином.
Подготавливаем наш тестовый сервер
Всё-таки я думаю нам сначала стоит именно тестовый сервер так-как тема конкретно написания плагина уже гораздо больше.
Для начала нам стоит выбрать основную версию и ядро, я выбрал 1.16.5 версию так-как сейчас она как основа для версий выше.
Из статистики выше видно, что большинство серверов на 1.16.5 и выше, а ядро Paper и следовательно как изначально планировалось было выбрано ядро Paper. Скачать его версии 1.16.5 можно по этой ссылке.
Скачанный файл по ссылке выше я помещаю в отдельную директорию рядом со своим Start.sh файлом:
Содержимое Start.sh
java -Xms512M -Xmx1G -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:MaxGCPauseMillis=200 -XX:+UnlockExperimentalVMOptions -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -XX:G1HeapWastePercent=5 -XX:G1MixedGCCountTarget=4 -XX:G1MixedGCLiveThresholdPercent=90 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:SurvivorRatio=32 -XX:+PerfDisableSharedMem -XX:MaxTenuringThreshold=1 -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 -XX:G1HeapRegionSize=8M -XX:G1ReservePercent=20 -XX:InitiatingHeapOccupancyPercent=15 -Dusing.aikars.flags=https://mcflags.emc.gs -Daikars.new.flags=true -jar paper-1.16.5-794.jar nogui
После первого запуска будет скачан кеш и создан eula.txt( в котором надо будет установить true вместо false ):
После изменения eula.txt будет такой вывод с полным запуском:
settings:
allow-end: false
warn-on-overload: true
permissions-file: permissions.yml
update-folder: update
plugin-profiling: false
connection-throttle: 0
query-plugins: false
deprecated-verbose: default
shutdown-message: Server closed
minimum-api: none
spawn-limits:
monsters: 10
animals: 10
water-animals: 5
water-ambient: 10
ambient: 5
chunk-gc:
period-in-ticks: 900
ticks-per:
animal-spawns: 1
monster-spawns: 1
water-spawns: 1
water-ambient-spawns: 1
ambient-spawns: 1
autosave: 12000
aliases: now-in-commands.yml
enable-jmx-monitoring=false
rcon.port=25575
level-seed=
gamemode=survival
enable-command-block=false
enable-query=false
generator-settings=
level-name=world
motd=Test Server
query.port=25565
pvp=true
generate-structures=false
difficulty=easy
network-compression-threshold=256
max-tick-time=60000
max-players=20
use-native-transport=true
online-mode=false
enable-status=true
allow-flight=false
broadcast-rcon-to-ops=true
view-distance=6
max-build-height=256
server-ip=
allow-nether=false
server-port=25565
enable-rcon=false
sync-chunk-writes=true
op-permission-level=4
prevent-proxy-connections=false
resource-pack=
entity-broadcast-range-percentage=100
rcon.password=
player-idle-timeout=0
debug=false
force-gamemode=false
rate-limit=0
hardcore=false
white-list=false
broadcast-console-to-ops=true
spawn-npcs=true
spawn-animals=true
snooper-enabled=true
function-permission-level=2
level-type=flat
text-filtering-config=
spawn-monsters=true
enforce-whitelist=false
resource-pack-sha1=
spawn-protection=16
max-world-size=500
После изменений конфигурации я удалил все миры, чтобы был создан только world с плоской генерацией. И теперь консоль после запуска выглядит так:
Теперь для начала нам надо загрузить важны плагины:
-
PlaceholderAPI – API для плейсхолдеров.
-
PlugManX – форк оригинального PlugMan для работы с плагинами(перезагрузка, загрузка, выгрузка и тд).
-
Auto Reload – автоматическая перезагрузка плагинов в случае изменений.
После загрузки можно перезагрузить частично сервер используя reload confirm. И потом плагины загрузятся и некоторые создадут конфигурации.
Изменённые конфигурации
ignored-plugins: [PlugManX]
notify-on-broken-command-removal: true
auto-load:
enabled: true
check-every-seconds: 2
auto-unload:
enabled: true
check-every-seconds: 2
auto-reload:
enabled: false
check-every-seconds: 2
plugins/PlaceholderAPI/config.yml
# PlaceholderAPI
# Version: 2.11.1
# Created by: extended_clip
# Contributors: https://github.com/PlaceholderAPI/PlaceholderAPI/graphs/contributors
# Issues: https://github.com/PlaceholderAPI/PlaceholderAPI/issues
# Expansions: https://api.extendedclip.com/all/
# Wiki: https://github.com/PlaceholderAPI/PlaceholderAPI/wiki
# Discord: https://helpch.at/discord
# No placeholders are provided with this plugin by default.
# Download placeholders: /papi ecloud
check_updates: true
cloud_enabled: true
cloud_sorting: "name"
cloud_allow_unverified_expansions: true
boolean:
'true': 'yes'
'false': 'no'
date_format: MM/dd/yy HH:mm:ss
debug: true
plugins/bStats/config.yml
enabled: false
serverUuid: 00000-00000
logFailedRequests: false
После сделанных изменений надо снова перезагрузить сервер, а после полной перезагрузки мы можем спокойно зайти на наш тестовый сервер с любого клиента, который поддерживает версию 1.16.5, так-же стоит выдать себе все права используя op <ваш ник>.
Подготовка нашего плагина
Так-как плагин будет написан на стандартном Java, то нам надо подумать о сборщике нашего плагина, есть несколько вариантов: IDE компилятор, Maven и Gradle. Давайте посмотрим основные плюсы и минусы, которые я выделил как основные:
-
Компилятор IDE(IDEA, Eclipse и др)
Плюсы:
1) Все зависимости и компиляция настроены прямо в настройках редактора.
2) Может быть быстрым но зависит от настроек компиляции.
Минусы:
1) Для редактирования может потребоваться конкретный редактор или его поддержка в другом редакторе.
2) Часто ограничен возможностями самого редактора.
3) Компиляция часто может идти тяжелее нежели на Maven или Gradle. -
Maven
Плюсы:
1) Возможно настроить и форматирование файлов, все зависимости и репозитори, а так-же плагины компиляции.
2) Не зависит от конкретного редактора.
3) Быстро компилирует, но хуже Gradle.
Минусы:
1) Для компиляции каждый раз запускается новый процесс, который каждый раз заново читает и собирает информацию. -
Gradle
Плюсы:
1) Есть настройки форматирования файлов, зависимостей, репозиторий и различных дополнений(например Lombok).
2) Запускает компиляцию в фоновом процессе, который считывает конфиги и другое после изменений или при другой нужде.
3) Не зависит от редактора как и Maven.
4) Можно писать конфигурацию компиляции на языке Groovy или Kotlin DSL
5) Запуск компиляции проходит с помощью запуска фонового процесса(если не запущен) или обращение к нему.
Минусы:
1) Фоновый процесс постоянно требует некоторое количество ОЗУ.
Вы конечно можете сами выбрать сборщик под себя, но я выбрал Gradle и из-за чего дальше будет информация связанная с ним. Так-же выбрал редактор Intellij IDEA от JetBrains так-как он очень замечательно работает с Java.
Первым делом нужно инициализировать Gradle проект:
После создания проекта я в первую очередь удалил папку test и изменил build.gradle под проект:
Содержимое build.grade
plugins {
id 'java'
}
group 'xyz.distemi'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
maven {
name = "PaperMC"
url = "https://repo.papermc.io/repository/maven-public/"
}
maven {
name = "PlaceholderAPI"
url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/'
}
}
dependencies {
compileOnly 'com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT' // PaperMC
compileOnly 'me.clip:placeholderapi:2.11.1' // API плагина PlaceholderAPI
compileOnly 'org.projectlombok:lombok:1.18.24' // Lombok API
annotationProcessor 'org.projectlombok:lombok:1.18.24' // Lombok процессор
}
И следом я нажал на кнопку синхронизации в своей IDEA.
Теперь нам надо сделать главный класс плагина, который у меня будет xyz.distemi.litesmt.LiteSMT:
Базовый код главного класса плагина LiteSMT
package xyz.distemi.litesmt; // Объявляем наш пакет
// Импортируем Getter из ломбок, абстрактный класс JavaPlugin и
// интерфейс логгера.
import lombok.Getter;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.logging.Logger;
public class LiteSMT extends JavaPlugin {
// Создаём две статичные переменные:
@Getter
private static LiteSMT instance; // Класс плагина.
@Getter
private static Logger jlogger; // Логгер.
@Override
public void onEnable() {
// Устанавливаем наши статичные переменные:
instance = this;
jlogger = super.getLogger();
// Выводим в консоль сообщение Hello from LiteSMT
// от имени плагина.
jlogger.info("Hello from LiteSMT!");
}
}
Для людей, которые не знают Java или хотящие подробное объяснение
В классе выше мы создали файл в папке src/main/xyz/distemi/litesmt файл LiteSMT.java с содержанием выше.
Импорты передают компилятору информацию о использованных классах и другого, без импорта ему неизвестно какой именно класс/интерфейс или другое вы используете.
public class позволяет нам показать класс как главный в этом файле, а это значит, что никто не мешает создать рядом просто class без public.
private является областью видимости поля, которая может быть применена как на метод, так и на переменную.
static – модификатор, говорящий, что данное поле может быть использовано, инициализировано и тд без конструирования самого класса( new Class() ).
Аннотация @Override позволяет нам перезаписать метод из класса-предка(у нас это абстрактный класс JavaPlugin)
Аннотация @Getter из Lombok указывает, что должен будет сгенерироваться дополнительный код, используя процессор аннотаций Lombok-а.
Метод onEnable работает как конструктор, но только вот он исключительно для нашего плагина. Метод возвращает тип void, а именно ничего.
Внутри перезаписанного метода onEnable мы устанавливаем глобальные переменные instance и jlogger, но если с первым случаем ясно, что ссылаемся на сконструированный класс, то во втором случае используем super уже для обращению к “предку”, а именно JavaPlugin.
Следом выводим в консоль сообщение из аргумента.
Чтож, главный класс у нас имеется, но для работы плагина этого недостаточно!
Серверу нужно знать какой же класс главный и другую информацию, для этого нам нужно создать файл plugin.yml, но уже не в качестве кода, а файл-ресурса, в Gradle такие файлы можно создать в директории проекта src/main/resources/, где файлы внутри не могут быть скомпилированы, а копируются в наш jar “сырыми”, но есть например возможность некоторого форматирования, однако пока думаю можно будет обойтись и без него.
Создание plugin.yml
В папке ресурсов я создаю файл plugin.yml, который по умолчанию не имеет ничего я записываю содержимое ниже:
name: LiteSMT
main: xyz.distemi.litesmt.LiteSMT
version: 1.0
author: Distemi
prefix: LSMT
depend:
- PlaceholderAPI
Как вы можете заметить, то мы устанавливаем всего шесть значений, однако некоторые необязательные.
name (Обязательно) – даёт знать ядру о “имени” плагина, которое может использоваться как в некоторых командах сервера, так и других плагинах по типу того же PlugManX.
main (Обязательно) – указывает класс в нашем jar, который становиться главным в работе плагина, 1 jar = 1 плагин.
version (Обязательно) – атрибут, означающий версию нашего плагина, может быть как 1.0 так и 1.0.0.
author – указывает автора плагина.
prefix – префикс в логе вместо названия плагина.
depend – обязательные зависимости для плагина, если каких-то нету, то плагин не будет загружен, в списке указываются “имена” плагинов.
Если интересно почитать о других атрибутах и тд для plugin.yml, то можете почитать по этой ссылке.
Всё почти готово, однако теперь нам нужно собрать плагин и перекинуть в папку с сервером, можно конечно это делать руками, но я больше предпочитаю делать это автоматически, используя свои задачи в Gradle, для чего нам нужно в build.gradle добавить следующее:
task copyToDevEnv_1_16_5() {
doLast {
copy {
from "build/libs/LiteSMT-1.0-SNAPSHOT.jar"
into "../test-server1.16.5/plugins/"
}
}
}
build.finalizedBy copyToDevEnv_1_16_5
Тут мы объявляем задачу, которая копирует готовый jar плагина в папку указанную из into, а build.finalizedBy означает, что задачу build мы всегда заканчиваем с copyToDevEnv_1_16_5. Давайте теперь мы впишем ./gradlew build –offline -x test, где мы запускаем компиляцию без доступа к интернету(–offline) и исключаем задачу(-x) тестов(test). Теперь смотрим в нашу папку с плагинами и видим:
Ура! Наш плагин успешно собрался и сам был помещён в директорию с плагинами. Теперь пробуем запустить наш сервер и видим…
Да! Плагин наш был успешно запущен и при запуске вывел в консоль наше сообщение, однако для проверки автоматической перезагрузки плагина в случае изменений я могу чуть изменить сообщение в коде и заново собрать плагин прошлой командой в консоль/терминал и увидеть уже в консоли сервера:
Всё-таки я добавил “Changed from me!” в строку вывода и после сборки плагин AutoReload сам увидел изменение и перезагрузил плагин, ну не удобство ли, когда надо бывает частенько и главный класс изменить?)
Так-же как вы могли бы заметить, то наш префикс из plugin.yml тоже показывает результат так-как без того атрибута у нас выводился бы LiteSMT.
В плагине мы можем так-же допустим реализовать работу с конфигурацией, событиями и другим, но я в этой части покажу лишь работу с событиями так-как на конфигурацию у меня есть много интересного содержания)
Обработка событий в плагине или как можно приукрасить чат.
Сейчас мы будем работать лишь с тремя из большого количества событий, а именно вход игрока, выход и написание сообщений в чат. Наверное многим не нравиться стандартный формат чата из и игры из-за чего скачивают и устанавливают плагины по типу Chatty и другого, но сейчас мы сделаем некую свою мини альтернативу, правда не всё, но хоть что-то)
Для событий в плагинах используют отдельные классы, которые просто помимо своего существования должны наследовать один из интерфейсов от Bukkit и быть зарегистрированы в событиях Bukkit-а.
Для событий чата будет у меня отдельный пакет в моём jar, а именно xyz.distemi.litesmt.listeners.chat, который в IDEA создаётся в два клика:
В нашем новом пакете создадим класс с именем MainChatListener, который по началу имеет только публикацию класса и пакет. И после его имени мы должны прописать implements Listener, однако как я говорил раньше, то нужно и импортировать классы, чтобы сборщик мог знать какой класс нам нужен и JVM тоже, поэтому IDEA предлагает выбрать Listener из нескольких пакетов, но нам нужен именно с org.bukkit.event и следом появиться импорт org.bukkit.event.Listener. Теперь данный класс для ядра считается неким слушателем событий, однако он не зарегистрирован и пусть, а это значит, что от него нету толка, для этого в главном классе, в onEnable мы прописываем:
Bukkit.getPluginManager().registerEvents(new MainChatListener(), this);
И теперь для bukkit наш слушатель зарегистрирован на все типы событий, а в качестве второго аргумента мы передаём плагин, это означает, что этот слушатель принадлежит нашему плагин.
– Тогда получается возможно регистрировать слушатели и из других плагинов?
– Верно! Однако не стоит так делать.
Если мы и попробуем сейчас собрать плагин и протестировать его, то у нас не будет никаких отличий так-как слушатель хоть и существует, но он пустой. Давайте попробуем создать в нём функцию для форматирования сообщений из чата, а именно установка своего формата сообщений:
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class MainChatListener implements Listener {
@EventHandler
public void onChat(AsyncChatEvent event) {
event.renderer((source, sourceDisplayName, message, viewer) ->
Component.text()
.append(sourceDisplayName.color(TextColor.fromHexString("#a8a432")))
.append(Component.text(" : "))
.append(message)
.build());
}
}
Объяснение кода
Все методы событий обязательно должны иметь аннотацию EventHandler от org.bukkit, чтобы было ясно, что этот метод точно является слушателем событий, а именно AsyncChatEvent так-как именно он указан в аргументе. Далее для форматирования не используется устаревший метод formatter, а новый – renderer, который имеет больше возможностей. Для нашего renderer мы используем так называемый функциональные интерфейсы, в данном случае ChatRenderer и спасибо Java, что тут получилось так сокращённо, ведь иначе вышло бы на несколько строк больше.
Для форматирования мы создаём наш новый чат-компонент Component, но для всего связанного с чатом сейчас используется пакет net.kyori.adventure.text. Component.text() создаёт нам конструктор, который мы используем для связки трёх других компонентов: ник, разделитель для сообщения( : ) и самого сообщения.
Метод TextColor.fromHexString даёт возможность получить нам цвет для чат-компонента из HEX строки c нужным цветом, в моём случае #a8a432. Этот цвет я применяю на переменную компонента sourceDisplayName и добавляю получившейся компонент в конструктор.
Далее я добавляю в конструктор разделитель в сообщении ” : “, который можно получить используя Component.text(” : “).
Последнее добавление в конструктор – само сообщение игрока.
Заканчивается создание форматированного компонента методом build.
Теперь если мы попробуем набрать в консоль любое сообщение, то увидим:
Далее я бы убрал сообщение выхода с сервера таким кодом:
@EventHandler
public void onQuit(PlayerQuitEvent event) {
event.quitMessage(null);
}
Теперь при повторной сборке и следующей проверки выхода мы не увидим сообщения в консоли о выходе игрока, кроме того, что является от самого ядра.
Можно и приукрасить сообщение о входе:
@EventHandler
public void onJoin(PlayerJoinEvent event) {
event.joinMessage(Component.text()
.append(Component.text("["))
.append(Component.text("+", TextColor.fromHexString("#28ff03")))
.append(Component.text("] "))
.append(event.getPlayer().displayName())
.build());
}
И после сборки этого кода, то при заходе будет данное сообщение:
Итог
Вот и была сделана небольшая основа для плагина, которая дальше будет дополняться всё большим функционалом в следующих частях!
Готовый сходный код доступен по этой ссылке.
Содержание
- Введение
- Четыре простых шагов для создания WordPress плагина
- Шаг 1. Подберите имя для вашего плагина
- Шаг 2. Создайте папку с плагином и PHP файл
- Шаг 3. Создайте заголовок файла
- Шаг 4: Добавьте функционал созданному плагину
- Практические советы по созданию своих плагинов для WordPress
- Основные советы
- Структура и архитектура плагина
- Готовый шаблон WordPress плагина
🤖 Шаблон WordPress плагина (генератор)
Стандартизированная, организованная, объектно-ориентированная основа для создания высококачественных WordPress плагинов.
Сгенерировать свой плагин
Раз уж вы попали на эту страницу значит у вас есть сайт на WordPress и вам наверняка знакомы возможности различных плагинов. Однако использование плагинов на сайте и создание собственного плагина – это две разные вещи. Давайте разбираться как создать плагин для WordPress!
Введение
К счастью, создавать собственные плагины для WordPress проще, чем вы могли бы подумать. Первый ключевой шаг – получить четкое представление о том, как работают плагины и как они создаются. После того, как вы определились с основными концепциями, создание и установка собственных плагинов станет довольно простой задачей.
В этом посте мы объясним основы разработки плагинов для WordPress, включая необходимые элементы и то, как они работают вместе.
Давайте начнем!
Важно отметить, что перед активацией нового плагина на своём сайте или редактированием каких-либо файлов вам нужно сначала настроить постановочную среду или, на худой конец, создать резервную копию. Это позволяет вам безопасно экспериментировать, не рискуя сломать ваш действующий сайт.
Шаг 1. Подберите имя для вашего плагина
Первый шаг в разработке плагина для WordPress – придумать официальное название для вашего плагина.
Рекомендуется проверить официальный репозиторий WordPress, чтобы убедиться, что нет других плагинов с тем именем, которое вы собираетесь использовать. Имейте в виду, что именем плагина будет то, что вы используете для папки плагина и файла PHP.
Называя плагин, убедитесь, что он уникален, конкретен и легко сокращается. #WordPress
Вы также можете использовать сокращенную версию названия в качестве префикса, чтобы избежать конфликтов имен (которые мы обсудим позже).
Шаг 2. Создайте папку с плагином и PHP файл
Прежде всего, вашему плагину нужно где-то жить. Поэтому после того, как вы выберете имя для своего плагина, следующим шагом будет создание для него папки.
Для начала перейдите в папку wp-content/plugins в каталоге где установлен WordPress. Создайте новую папку и назовите ее, используя имя плагина, используя дефисы для разделения слов. Например, my-super-plugin.
После того, как вы создали папку с плагинами, следующим шагом будет создание внутри нее файла PHP.
В зависимости от того, насколько сложным будет ваш плагин, он может в конечном итоге содержать один файл PHP или несколько файлов.
Шаг 3. Создайте заголовок файла
После того как вы создадите основной файл плагина, пора добавить заголовок файла. По сути, это комментарий блока PHP, который включает метаданные о вашем плагине.
Внутри файла добавьте следующий код:
/** * Plugin Name: Мой супер плагин * Plugin URI: https://wordpresslab.ru/plugins/kak-sozdat-plagin-dlya-wordpress/ * Description: Этот плагин делает мир лучше! * Version: 1.0.0 * Author: WordPress лаборатория * Author URI: https://wordpresslab.ru/ * License: GPL2 */
Не забудьте заменить приведенную выше информацию на сведения, относящиеся именно к вашему плагину. Кроме того, если в вашем каталоге плагинов есть несколько файлов PHP, убедитесь, что вы добили этот заголовок только к одному из них.
Как минимум, заголовок должен содержать название вашего плагина. Однако вы также можете использовать это пространство для включения сведений об авторе, лицензии и т.д.
Когда вы закончите, сохраните изменения и ваш плагин будет добавлен на сайт. Чтобы убедиться, перейдите на панель администратора WordPress и перейдите в раздел «Плагины»:
Вы должны увидеть свой новый плагин в списке на этом экране. Конечно, впереди еще много работы.
Шаг 4: Добавьте функционал созданному плагину
Большинство плагинов работают с помощью хуков (hook), которые позволяют одному фрагменту кода взаимодействовать с другим. В WordPress есть два типа хуков: действия (actions) и фильтры (filters). Мы обсудим их более подробно в рамках отдельной статьи, пока лишь основы…
Actions (действия)
Actions в WordPress относится к определенному действию, которое должно произойти в определенное время. С помощью действий вы можете добавить или изменить функциональность вашего плагина. Функции, которые связаны с действием, будут выполнены после его запуска.
Пример действия в WordPress – save_post. Действия определяются функцией do_action. Для них требуется параметр $tag (имя действия) и в некоторых случаях $args (дополнительные аргументы, расширяющие то, что делает действие).
Ядро WordPress уже содержит десятками предопределенных действий. Однако вы также можете создать свои собственные. В любом случае, при создании плагина для WordPress вы будете использовать do_action для установки значений к подключенной функции. Затем функция add_action будет использоваться для подключения этой функции к определенному действию.
Подробнее про экшен-хуки читайте тут.
Filters (фильтры)
Фильтры WordPress – это хуки, которые принимают одну переменную или серию переменных, а затем отправляют их обратно после того, как они были изменены. Вкратце, фильтры позволяют вам изменять контент, отображаемый для пользователей.
Фильтры в WordPress создаются с помощью функции apply_filters и определяются внутри функции. Для них требуются аргументы $tag (имя фильтра) и $value (отфильтрованное значение или переменная) с возможностью использования $var для дополнительных значений функции.
Вы можете создать свой собственный фильтр с помощью хука apply_filters. Затем для его выполнения вы можете использовать функцию add_filter. Это позволит вам подключить к фильтру определенную функцию, чтобы вы могли манипулировать переменной и возвращать ее.
Подробнее про фильтр-хуки читайте тут.
Практические советы по созданию своих плагинов для WordPress
Важно понимать, что вы не первый, кто создает свой собственный WordPress плагин. До вас были десятки тысяч и столько же будет после вас. В связи с этим стоит обратить внимание на передовые методики. Это поможет обеспечить плавный процесс разработки и повысить эффективность вашего плагина.
Основные советы
- Избегайте конфликтов имен.
- Все переменные, функции и классы должны иметь префикс с уникальным идентификатором.
- PHP предоставляет ряд функций для проверки существования переменных, функций, классов и констант. Используйте эту возможность!
- Самый простой способ решить проблему коллизии имен – использовать классы для кода вашего плагина. Метод объектно-ориентированного программирования.
- Корневой уровень каталога вашего плагина должен содержать ваш файл название-плагина.php и при желании, файл uninstall.php. Все остальные файлы по возможности должны находится во вложенных папках.
Структура и архитектура плагина
Четкая структура плагина облегчит жизнь вам, и всем тем кто в будущем будет работать с вашим кодом.
Архитектура или организация кода должна зависеть от размера вашего плагина.
Для небольших плагинов которые имеют ограниченное взаимодействие с ядром WordPress, темами или другими подключаемыми модулями, создание сложных классов не принесет особой пользы.
Для больших плагинов с большим количеством кода создание классов является стандартом. Это поможет в организации кода и долгосрочном обслуживании плагина.
Готовый шаблон WordPress плагина
Вместо того, чтобы начинать с нуля для каждого нового плагина, вы можете начать с шаблона.
Одно из преимуществ использования шаблона – согласованность ваших собственных плагинов. Плагины, созданные по шаблонам, также облегчают жизнь другим людям!
🤖 Шаблон WordPress плагина (генератор)
Стандартизированная, организованная, объектно-ориентированная основа для создания высококачественных WordPress плагинов.
Сгенерировать свой плагин
Надеемся эта статья помогла вам чуть больше погрузиться в мир WordPress и вы разобрались как создать плагин для WordPress 😊
Если у вас есть вопросы – спрашивайте в комментариях.
Спасибо.
От автора: для получения максимального результата от урока по разработке плагина для WordPress вы должны понимать базовые понятия, такие как экшены, фильтры, шорткоды, виджеты и объектно-ориентированный дизайн.
Реальный пример – Список филиалов компании
Сразу перейдем к реальному примеру того, как плагин может решать проблемы и улучшать функционал вашего сайта. Зачастую у компаний возникает задача показать адреса различных мест или их офисов. Для каждого места можно создать отдельную страницу, но куда лучше будет реализовать их в виде уникального контента со своими данными на одной странице, и завернуть все это в простой интерфейс.
Данный функционал можно было бы реализовать в дочерней теме, но из-за его универсальности лучше создать плагин, который потом можно будет использовать и на других сайтах.
В примере мы расскажем про разработку плагина, который будет обрабатывать данные и выводить их в виде списка мест. Места будут представлять собой пользовательский тип контента с дополнительными мета данными для хранения информации о местоположении. В плагине будет возможность отображать информацию несколькими способами (отдельная страница мест, виджет со списком мест и шорткод со списком мест).
Настройка
Давайте все настроим, перейдите в папку плагина и создайте следующие папки/файловую структуру
Самый верхний файл wp_simple_location_plugin.php будет главным. Тут мы будем загружать наши стили из папки CSS, а также дополнительные PHP файлы из папки inc.
Основной класс местоположения
В файле wp_simple_location_plugin.php будет прописано ядро плагина. Также в этом файле мы подключим дополнительные файлы, которые нужны для создания виджета и шорткодов.
Непрямой доступ
Рекомендуется закрывать прямой доступ к PHP файлам с помощью проверки на существование константы ABSPATH (если не существует, скрипт прекращает работу). Поместите код ниже прямо после открывающего PHP тега:
defined( ‘ABSPATH’ ) or die( ‘Nope, not accessing this’ ); |
Объявление плагина
Чтобы плагин работал, его сначала необходимо объявить. Объявление выглядит, как набор комментариев для WordPress, в которых содержится информация о плагине. В плагин необходимо добавить код ниже, иначе он просто не появится в менеджере плагинов в WP.
<?php /* Plugin Name: WordPress Simple Location Plugin Plugin URI: //github.com/simonrcodrington/Introduction-to-WordPress-Plugins—Location-Plugin Description: Creates an interfaces to manage store / business locations on your website. Useful for showing location based information quickly. Includes both a widget and shortcode for ease of use. Version: 1.0.0 Author: Simon Codrington Author URI: //www.simoncodrington.com.au License: GPL2 License URI: //www.gnu.org/licenses/gpl-2.0.html */ |
В этом коде вы указываете название плагина, описание, версию и другие данные. Большая часть этих данных будет показываться на странице администрирования, когда пользователь будет активировать плагин.
Класс wp_simple_location Class
Теперь необходимо создать оболочку для класса wp_simple_location. В этом классе будет прописана большая часть функционала плагина, в нем будут храниться наши свойства и методы.
class wp_simple_location{ } |
Подключаем файлы шорткодов и виджета
Так как мы будем использовать и виджет и шорткод, то было решено разбить функционал на два отдельных файла.
Скопируйте код ниже в самый конец класса wp_simple_location:
//шорткоды include(plugin_dir_path(__FILE__) . ‘inc/wp_location_shortcode.php’); //виджеты include(plugin_dir_path(__FILE__) . ‘inc/wp_location_widget.php’); |
Код выше подключает наши файлы. О них мы поговорим чуть позже.
Свойства класса
Свойства класса это переменные, которые используются в функциях класса. С помощью свойств намного проще получать доступ к часто используемым элементам. В данной функции у нас будет всего одно свойство, массив дней, который будет использоваться для отображения рабочего времени для каждого места. Создадим пустой массив, чуть позже мы заполним его значениями.
//Свойства private $wp_location_trading_hour_days = array(); |
Функция _construct
Функция _construct – важная часть плагина. Это мастер-функция, с помощью которой можно обрабатывать и запускать другие функции.
Функция относится к так называемым magic (магическим) функциям. Магические функции представляют собой специальные функции, добавленные в PHP5, автоматически запускающиеся при определенных условиях. Данная функция срабатывает сразу же после создания экземпляра класса (класс создан и создан его экземпляр в виде переменной).
В плагине данную функцию вы будете использовать для добавления всех экшенов, фильтров и вызова других функций. Хуки можно добавлять и из другого места, но пусть лучше они останутся там. Скопируйте код ниже. Мы пройдемся по каждому элементу, чтобы вы поняли, что тут происходит.
//магическая функция (запускается при создании экземпляра класса) public function __construct(){ add_action(‘init’, array($this,‘set_location_trading_hour_days’)); //задает рабочие дни (используется типом контента) add_action(‘init’, array($this,‘register_location_content_type’)); //регистрирует тип контента местоположения add_action(‘add_meta_boxes’, array($this,‘add_location_meta_boxes’)); //добавляет мета поля add_action(‘save_post_wp_locations’, array($this,‘save_location’)); //сохраняет местоположение add_action(‘admin_enqueue_scripts’, array($this,‘enqueue_admin_scripts_and_styles’)); //скрипты и стили администратора add_action(‘wp_enqueue_scripts’, array($this,‘enqueue_public_scripts_and_styles’)); //публичные стили и скрипты add_filter(‘the_content’, array($this,‘prepend_location_meta_to_content’)); //вытягивает наши мета данные и показывает их перед контентом register_activation_hook(__FILE__, array($this,‘plugin_activate’)); //активирует хук register_deactivation_hook(__FILE__, array($this,‘plugin_deactivate’)); //отключает хук } |
Функции register_activation_hook и register_deactivation_hook используются для встраивания в другие функции при активации и отключении плагина. Мы будем использовать эти функции для проверки правильности добавленного типа контента (наши места) и обработки ссылок (мы будем использовать человекопонятные ссылки).
Настройка рабочих часов для определенного филиала
Наш плагин позволяет администратору устанавливать часы открытия и закрытия филиала для каждого дня недели по отдельности.
Рабочие дни хранятся на стороне back-end’а в свойстве $wp_location_trading_hour_days. Для настройки рабочих дней и часов необходимо вызвать функцию set_location_trading_hour_days.
//Устанавливает стандартны рабочие дни и часы (используется на стороне back-end) public function set_location_trading_hour_days(){ //задаем дни, которые будут использоваться для рабочих часов $this–>wp_location_trading_hour_days = apply_filters(‘wp_location_trading_hours_days’, array(‘monday’ => ‘Monday’, ‘tuesday’ => ‘Tuesday’, ‘wednesday’ => ‘Wednesday’, ‘thursday’ => ‘Thursday’, ‘friday’ => ‘Friday’, ‘saturday’ => ‘Saturday’, ‘sunday’ => ‘Sunday’, ) ); } |
После заполнения значений массива необходимо вызвать фильтр wp_location_trading_hours_days. Т.е. тема или другой плагин смогут переписать рабочие дни магазина (они могут фильтровать массив и добавить поле каникул «holidays», чтобы потом задать для этого периода свое рабочее время).
Настройка рабочих часов заданного филиала
Тут мы создадим наш пользовательский тип местоположений, который будем использовать в плагине. Необходимо указать лейблы и аргументы типа контента и передать наши аргументы в функцию register_post_type. На странице кодекса объясняются все варианты пользовательского типа контента. Для нашего типа нам понадобятся заголовок, редактор и встроенное изображение.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
//регистрация типа контента местоположения public function register_location_content_type(){ //Лейблы типа постов $labels = array( ‘name’ => ‘Location’, ‘singular_name’ => ‘Location’, ‘menu_name’ => ‘Locations’, ‘name_admin_bar’ => ‘Location’, ‘add_new’ => ‘Add New’, ‘add_new_item’ => ‘Add New Location’, ‘new_item’ => ‘New Location’, ‘edit_item’ => ‘Edit Location’, ‘view_item’ => ‘View Location’, ‘all_items’ => ‘All Locations’, ‘search_items’ => ‘Search Locations’, ‘parent_item_colon’ => ‘Parent Location:’, ‘not_found’ => ‘No Locations found.’, ‘not_found_in_trash’ => ‘No Locations found in Trash.’, ); //аргументы типа постов $args = array( ‘labels’ => $labels, ‘public’ => true, ‘publicly_queryable’=> true, ‘show_ui’ => true, ‘show_in_nav’ => true, ‘query_var’ => true, ‘hierarchical’ => false, ‘supports’ => array(‘title’,‘thumbnail’,‘editor’), ‘has_archive’ => true, ‘menu_position’ => 20, ‘show_in_admin_bar’ => true, ‘menu_icon’ => ‘dashicons-location-alt’, ‘rewrite’ => array(‘slug’ => ‘locations’, ‘with_front’ => ‘true’) ); //регистрация типа постов register_post_type(‘wp_locations’, $args); } |
После добавления этого кода в меню должна появиться новая строка.
Добавляем мета бокс к новому типу местоположений
Необходимо создать пользовательский мета бокс, который будет показываться на странице местоположений. В этом боксе будут располагаться все дополнительные поля, которые нам понадобятся в виде мета данных (номер телефона, электронная почта, адрес). Внутри функции вызываем функцию add_meta_box() и передаем ей наши аргументы.
//добавление мета бокса к местоположениям*/ public function add_location_meta_boxes(){ add_meta_box( ‘wp_location_meta_box’, //id ‘Location Information’, //название array($this,‘location_meta_box_display’), //функция отображения ‘wp_locations’, //тип поста ‘normal’, //Расположение ‘default’ //Приоритет ); } |
Третье значение add_meta_box – функция, которая будет показывать наш бокс. Тут мы вызываем функцию location_meta_box_display, ее мы создадим следующей.
Функция location_meta_box_display
Эта функция будет вызываться из мета бокса и будет показывать дополнительные поля, которые администратор сможет использовать для записи информации о месте.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
//функция отображения для мета бокса box*/ public function location_meta_box_display($post){ //поле nonce wp_nonce_field(‘wp_location_nonce’, ‘wp_location_nonce_field’); //собираем переменные $wp_location_phone = get_post_meta($post–>ID,‘wp_location_phone’,true); $wp_location_email = get_post_meta($post–>ID,‘wp_location_email’,true); $wp_location_address = get_post_meta($post–>ID,‘wp_location_address’,true); ?> <p>Enter additional information about your location </p> <div class=“field-container”> <?php //делаем хук перед главными элементами формы do_action(‘wp_location_admin_form_start’); ?> <div class=“field”> <label for=“wp_location_phone”>Contact Phone</label> <small>main contact number</small> <input type=“tel” name=“wp_location_phone” id=“wp_location_phone” value=“<?php echo $wp_location_phone;?>“/> </div> <div class=“field”> <label for=“wp_location_email”>Contact Email</label> <small>Email contact</small> <input type=“email” name=“wp_location_email” id=“wp_location_email” value=“<?php echo $wp_location_email;?>“/> </div> <div class=“field”> <label for=“wp_location_address”>Address</label> <small>Physical address of your location</small> <textarea name=“wp_location_address” id=“wp_location_address”><?php echo $wp_location_address;?></textarea> </div> <?php //рабочие часы if(!empty($this–>wp_location_trading_hour_days)){ echo ‘<div class=”field”>’; echo ‘<label>Trading Hours </label>’; echo ‘<small> Trading hours for the location (e.g 9am – 5pm) </small>’; //проходимся в цикле через все рабочие дни foreach($this–>wp_location_trading_hour_days as $day_key => $day_value){ //собираем мета данные о рабочих часах $wp_location_trading_hour_value = get_post_meta($post–>ID,‘wp_location_trading_hours_’ . $day_key, true); //показываем лейблы и инпут echo ‘<label for=”wp_location_trading_hours_’ . $day_key . ‘”>’ . $day_key . ‘</label>’; echo ‘<input type=”text” name=”wp_location_trading_hours_’ . $day_key . ‘” id=”wp_location_trading_hours_’ . $day_key . ‘” value=”‘ . $wp_location_trading_hour_value . ‘”/>’; } echo ‘</div>’; } ?> <?php //хук после основных элементов формы do_action(‘wp_location_admin_form_end’); ?> </div> <?php } |
Что делает функция:
Сперва создается безопасное nonce поле для мета бокса (nonce используется для проверки того, что экшен отправки формы пришел из нужного места).
Поле собираем наши телефоны, почту и адреса (если они есть).
Перед самой формой мы добавляем хук wp_location_admin_form_start. Это позволит другим плагинам или темам встроиться в это место, чтобы добавить дополнительные поля или данные.
Показываем поля телефона, почты и адреса (если есть какие-то данные, то заполняем их).
Показываем список рабочих дней для отдельного мета. Тут администратор может устанавливать рабочие часы для каждого дня в отдельности. Дни могут меняться, если они прикреплены к фильтру wp_location_trading_hours_days.
Перед закрытием формы добавляем хук wp_location_admin_form_end. Это позволит другим плагинам или темам встроиться в этом место и добавить дополнительные поля или данные.
Мета бокс должен показаться на странице места. Выглядеть это будет примерно так:
Регистрация типа контента и обработка правил перезаписи во время активации
Во время первой активации плагина можно вызвать функцию, которая будет выполнена всего раз. Для этого мы будеи использовать функцию plugin_activate.
Мы зарегистрировали тип контента через хук init, но нам все равно нужно вызвать функцию register_location_content_type (чтобы проверить, добавился ли наш тип).
Также мы изменим правила перезаписи, чтобы можно было использовать человекопонятные ссылки (у нас будут ссылки типа example.com/location/mylocation вместо example.com/?p=144)
//Срабатывает при активации плагина (вызывается единожды) public function plugin_activate(){ //вызываем функцию проверки пользовательского типа контента $this–>register_location_content_type(); //обрабатываем ссылки flush_rewrite_rules(); } |
Сброс правил перезаписи при отключении плагина
Функция plugin_deactivate срабатывает при отключении плагина. Так как мы удаляем плагин, а в плагине использовался пользовательский тип контента, то для верности необходимо сбросить правила перезаписи.
// Срабатывает при отключении плагина (вызывается единожды) public function plugin_deactivate(){ //Сброс ссылок flush_rewrite_rules(); } |
Показываем мета данные для конкретного места
Мы создали дополнительные мета данные для всех мест, теперь необходимо создать функцию для отображения этих данных при просмотре каждого отдельного места.
Функция prepend_location_meta_to_content цепляется к фильтру the_content, т.е. мы можем добавить свои данные перед главным контентом страниц.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
public function prepend_location_meta_to_content($content){ global $post, $post_type; //Показываем мета данные только для мест (и если место конкретное) if($post_type == ‘wp_locations’ && is_singular(‘wp_locations’)){ //собираем переменные $wp_location_id = $post->ID; $wp_location_phone = get_post_meta($post–>ID,‘wp_location_phone’,true); $wp_location_email = get_post_meta($post–>ID,‘wp_location_email’,true); $wp_location_address = get_post_meta($post–>ID,‘wp_location_address’,true); //показываем $html = ”; $html .= ‘<section class=”meta-data”>’; //хук для вставки дополнительных данных (в начале формы) do_action(‘wp_location_meta_data_output_start’,$wp_location_id); $html .= ‘<p>’; //телефон if(!empty($wp_location_phone)){ $html .= ‘<b>Location Phone</b> ‘ . $wp_location_phone . ‘</br>’; } //почта if(!empty($wp_location_email)){ $html .= ‘<b>Location Email</b> ‘ . $wp_location_email . ‘</br>’; } //адрес if(!empty($wp_location_address)){ $html .= ‘<b>Location Address</b> ‘ . $wp_location_address . ‘</br>’; } $html .= ‘</p>’; //Место if(!empty($this–>wp_location_trading_hour_days)){ $html .= ‘<p>’; $html .= ‘<b>Location Trading Hours </b></br>’; foreach($this–>wp_location_trading_hour_days as $day_key => $day_value){ $trading_hours = get_post_meta($post–>ID, ‘wp_location_trading_hours_’ . $day_key , true); $html .= ‘<span class=”day”>’ . $day_key . ‘</span><span class=”hours”>’ . $trading_hours . ‘</span></br>’; } $html .= ‘</p>’; } //Хук для вставки дополнительных мета данных (в конце формы) do_action(‘wp_location_meta_data_output_end’,$wp_location_id); $html .= ‘</section>’; $html .= $content; return $html; }else{ return $content; } } |
Что делает функция:
Функция добавлена к хуку the_content, т.е. она будет запускаться при каждом обновлении страницы. Мы используем глобальные переменные $post и $post_type для проверки, что мы находимся на странице конкретного места
Собираем базовую информацию: почта, телефон и адрес.
Перед отображением мета данных вызываем хук wp_location_meta_data_output_start. С его помощью другие плагины или темы смогут встроиться в этом место и добавлять дополнительные мета данные (если кто-то добавит новое поле к месту, этот хук можно будет использовать для отображения сохраненных данных).
Показываем почту, телефон и адрес.
Пробегаемся по переменной wp_location_trading_hour_days и проверяем ее на рабочие дни. Если они есть, пробегаемся по ним в цикле, сохраняем рабочие часы и показываем их.
Почти в самом конце вызываем экшен wp_location_meta_data_output_end. С его помощью можно будет добавить дополнительные данные перед закрытием мета бокса.
Выводим список мест
Необходимо создать функцию для создания HTML кода списка мест. Функция get_locations_output используется как виджетом, так и шорткодом для создания разметки. Так как функция многозадачная, в ней есть несколько важных действий. Разберем все по порядку.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
//главная функция отображения списка мест (используется шорткодами и виджетами) public function get_locations_output($arguments = “”){ //стандартные аргументы $default_args = array( ‘location_id’ => ”, ‘number_of_locations’ => –1 ); //если переданы новые аргументы, обновляем if(!empty($arguments) && is_array($arguments)){ //проходимся в цикле по всем аргументам foreach($arguments as $arg_key => $arg_val){ //если аргумент есть в наших дефолтных, обновляем его значение if(array_key_exists($arg_key, $default_args)){ $default_args[$arg_key] = $arg_val; } } } //находим места $location_args = array( ‘post_type’ => ‘wp_locations’, ‘posts_per_page’=> $default_args[‘number_of_locations’], ‘post_status’ => ‘publish’ ); //if we passed in a single location to display if(!empty($default_args[‘location_id’])){ $location_args[‘include’] = $default_args[‘location_id’]; } //вывод $html = ”; $locations = get_posts($location_args); //Если места есть if($locations){ $html .= ‘<article class=”location_list cf”>’; //пробегаемся циклом по местам foreach($locations as $location){ $html .= ‘<section class=”location”>’; //собираем данные о местах $wp_location_id = $location–>ID; $wp_location_title = get_the_title($wp_location_id); $wp_location_thumbnail = get_the_post_thumbnail($wp_location_id,‘thumbnail’); $wp_location_content = apply_filters(‘the_content’, $location–>post_content); if(!empty($wp_location_content)){ $wp_location_content = strip_shortcodes(wp_trim_words($wp_location_content, 40, ‘…’)); } $wp_location_permalink = get_permalink($wp_location_id); $wp_location_phone = get_post_meta($wp_location_id,‘wp_location_phone’,true); $wp_location_email = get_post_meta($wp_location_id,‘wp_location_email’,true); //применяем фильтр перед выводом основного контента //(позволяем сторонним плагинам и темам встраиваться в HTML для отображения данных) $html = apply_filters(‘wp_location_before_main_content’, $html); //заголовок $html .= ‘<h2 class=”title”>’; $html .= ‘<a href=”‘ . $wp_location_permalink . ‘” title=”view location”>’; $html .= $wp_location_title; $html .= ‘</a>’; $html .= ‘</h2>’; //изображение и контент if(!empty($wp_location_thumbnail) || !empty($wp_location_content)){ $html .= ‘<p class=”image_content”>’; if(!empty($wp_location_thumbnail)){ $html .= $wp_location_thumbnail; } if(!empty($wp_location_content)){ $html .= $wp_location_content; } $html .= ‘</p>’; } //телефон и почта if(!empty($wp_location_phone) || !empty($wp_location_email)){ $html .= ‘<p class=”phone_email”>’; if(!empty($wp_location_phone)){ $html .= ‘<b>Phone: </b>’ . $wp_location_phone . ‘</br>’; } if(!empty($wp_location_email)){ $html .= ‘<b>Email: </b>’ . $wp_location_email; } $html .= ‘</p>’; } // применяем фильтр после вывода основного контента //( позволяем сторонним плагинам и темам встраиваться в HTML для отображения данных) $html = apply_filters(‘wp_location_after_main_content’, $html); //подробнее $html .= ‘<a class=”link” href=”’ . $wp_location_permalink . ‘” title=”view location”>View Location</a>’; $html .= ‘</section>’; } $html .= ‘</article>’; $html .= ‘<div class=”cf”></div>’; } return $html; } |
В функции есть необязательный аргумент arguments. Он используется из-за того, что у нас и виджеты и шорткоды передают свои варианты отображения (для точной настройки того, что будет возвращено).
В массив заносятся стандартные аргументы. Количество мест будет установлено в -1 (все места), а ID будет без значения (т.е. мы хотим показать список мест, а не конкретное место).
Проверяем переданную переменную $arguments на пустоту, и является ли она массивом. Пробегаемся по всем аргументам $arguments и сверяем их ключи на совпадение с нашим массивом $default_args. Если что-то совпало, обновляем массив $default_args.
Массив $default_args используется для создания запроса для get_posts(), который и будет искать все места (если создано всего одно место, его мы и получим).
Теперь переходим к созданию HTML. Создаем переменную $html, куда будем заносить разметку.
Собираем все данные о месте (заголовок, контент, изображение и т.д.) и подготавливаем их к выводу.
Вызываем фильтр wp_location_before_main_content на переменную $html. Так сторонние плагины и темы смогут добавлять дополнительный контент перед заголовком места. Крайне полезно, если мы заранее создали дополнительные поля в панели администратора, которые потом можно выводить.
В вывод добавляются заголовок, изображение, контент, телефон и почта (с условиями на проверку их существования).
Перед выводом кнопки Подробнее мы вызываем наш второй фильтр wp_location_after_main_content. С его помощью сторонние плагины и темы смогут добавлять дополнительный контент прямо перед кнопкой.
Добавляем кнопку Подробнее и возвращаем нашу переменную $html.
Сохранение дополнительных мета данных
При сохранении нового места необходимо запускать функцию, которая будет собирать и обновлять наши дополнительные мета поля (почта, телефон, адрес и т.д). Для этого мы создадим функцию save_location.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
//запускается при добавлении или редактировании места public function save_location($post_id){ //ищем nonce if(!isset($_POST[‘wp_location_nonce_field’])){ return $post_id; } //проверяем nonce if(!wp_verify_nonce($_POST[‘wp_location_nonce_field’], ‘wp_location_nonce’)){ return $post_id; } //ищем автосохранение if(defined(‘DOING_AUTOSAVE’) && DOING_AUTOSAVE){ return $post_id; } //получаем поля телефон, почта, адрес $wp_location_phone = isset($_POST[‘wp_location_phone’]) ? sanitize_text_field($_POST[‘wp_location_phone’]) : ”; $wp_location_email = isset($_POST[‘wp_location_email’]) ? sanitize_text_field($_POST[‘wp_location_email’]) : ”; $wp_location_address = isset($_POST[‘wp_location_address’]) ? sanitize_text_field($_POST[‘wp_location_address’]) : ”; //обновляем поля телефон, почта, адрес update_post_meta($post_id, ‘wp_location_phone’, $wp_location_phone); update_post_meta($post_id, ‘wp_location_email’, $wp_location_email); update_post_meta($post_id, ‘wp_location_address’, $wp_location_address); //ищем рабочие чаты и обновляем их foreach($_POST as $key => $value){ //если нашли, обновляем if(preg_match(‘/^wp_location_trading_hours_/’, $key)){ update_post_meta($post_id, $key, $value); } } //хук сохранения места используется для сохранения дополнительных полей //через ‘wp_location_meta_data_output_end’ или ‘wp_location_meta_data_output_end’ do_action(‘wp_location_admin_save’,$post_id, $_POST); } |
Что мы делаем:
Сначала ищем поле nonce и проверяем его на существование (передается через мета бокс). Также проверяем автосохранение. После всех проверок едем дальше.
Собираем телефон, почту и адрес и очищаем данные с помощью функции sanitize_text_field(). Данные заносятся в переменные и позже используются в нашей функции update_post_meta() для сохранения данных о месте.
Так как наши рабочие часы динамические, то вытягивать и сохранять их мы вынуждены слегка по-другому. Мы не знаем, сколько будет значений, поэтому мы не можем вытягивать их из массива $_POST по имени. Нам придется пройтись по всему массиву и проверять ключи на совпадение с «wp_location_trading_hours_». Если совпадения есть, мы обновляем значения и сохраняем их в качестве мета данных.
Почти в конце вызываем экшен wp_location_admin_save. Экшен сохраняет ID текущего места в $post_id, чтобы сторонние функции могли вытягивать дополнительные данные из массива $_POST и сохранять их в месте.
Загружаем админ и публичные скрипты и стили
Нам необходимо загрузить дополнительные CSS файлы как для front-end, так и для back-end стороны нашего сайта. Для этого мы создадим две функции, которые будут загружать любые скрипты или файлы стилей.
Внутри CSS файлов будут стили полей мета бокса в панели администратора, а также немного стилей front-end’а. Плагин замечательно работает без CSS стилей. Если вам эта часть не интересна, ее можно пропустить. (Если вы пропускаете эту часть, не забудьте удалить вызов экшенов в конструкторе)
//подключаем скрипты и стили на стороне back-end’а public function enqueue_admin_scripts_and_styles(){ wp_enqueue_style(‘wp_location_admin_styles’, plugin_dir_url(__FILE__) . ‘/css/wp_location_admin_styles.css’); } // подключаем скрипты и стили на стороне front-end’а public function enqueue_public_scripts_and_styles(){ wp_enqueue_style(‘wp_location_public_styles’, plugin_dir_url(__FILE__). ‘/css/wp_location_public_styles.css’); } |
Шорткод
Теперь можно перейти к классу шорткода, который будет работать в паре с нашим основным классом. Шорткоды предоставляют администратору простой интерфейс отображения различных мест. Шорткоды будут настраиваемые, администратор сможет задавать конкретный филиал (место) по ID или же показывать все филиалы сразу. Шорткод на странице будет выглядеть примерно следующим образом:
Работать мы будем в файле wp_location_shortcode.php.
Запрет на прямой доступ
Как в случае с основным файлом PHP, тут мы тоже хотим закрыть прямой доступ. Скопируйте код ниже в верхушку файла:
defined( ‘ABSPATH’ ) or die( ‘Nope, not accessing this’ ); |
Класс wp_location_shortcode
Создадим оболочку класса. Этот класс не будет таким большим, как главный, но в нем будем пара функций, по которым мы вкратце пробежимся.
//задаем функционал шорткода для отображения мест class wp_location_shortcode{ } |
Функция __construct
Необходимо задать конструктор для того, чтобы через него добавлять наши экшены и фильтры. В этом классе нужна всего одна функция.
//запускается при создании экземпляра класса public function __construct(){ add_action(‘init’, array($this,‘register_location_shortcodes’)); //шорткоды } |
Функция register_location_shortcodes
С помощью этой функции мы будем добавлять шорткод. Мы вызываем функцию add_shortcode для добавления нового шорткода wp_locations. Для вывода шорткода будем использовать функцию location_shortcode_output.
//шорткод мест public function register_location_shortcodes(){ add_shortcode(‘wp_locations’, array($this,‘location_shortcode_output’)); } |
Вывод шорткода
Функция вывода вызывается из функции add_shortcode и нужна для формирования кода для вывода шорткода на экран.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//отображение шорткода public function location_shortcode_output($atts, $content = ”, $tag){ //делаем класс wp_simple_locations глобальным global $wp_simple_locations; //создаем дефолтные аргументы $arguments = shortcode_atts(array( ‘location_id’ => ”, ‘number_of_locations’ => –1) ,$atts,$tag); //для вывода используем главную функцию вывода из класс location $html = $wp_simple_locations–>get_locations_output($arguments); return $html; } |
Что делает функция:
Функция принимает аргументы шорткода в виде переменной $atts, контент шорткода $content и назавние шорткода $tag. Эти переменные используются для формирования кода на вывод.
Создаем глобальную переменную $wp_simple_locations, чтобы получить доступ к главному классу (и всем его функциям).
Создаем дефолтный массив аргументов шорткода с помощью функции shortcode_atts().
Формируем код на вывод с помощью функции get_locations_output из объекта $wp_simple_locations. В функцию передаем наши аргументы, чтобы контент в шорткоде мог быть динамическим. К примеру, можно передать ID одного места, тогда вернется всего одно местоположение.
Возвращаем шорткод, который можно отобразить на любой странице или посте, куда вы его добавите.
Создаем объект wp_location_shortcode
В самом конце класса необходимо создать объект wp_location_shortcode. Таким образом будет активирован весь функционал класса.
$wp_location_shortcode = new wp_location_shortcode; |
Виджет
Закончим статью классом, работающим с виджетом. Мы добавим виджет, потому что почти все темы сейчас поддерживают виджеты. Виджеты дают администраторам простой и быстрый способ демонстрации чего-либо (в нашем случае это филиалы компании). Откроем файл wp_location_widget.php и начнем.
Запрет прямого доступа
И опять мы запрещаем прямой доступ к PHP файлу с помощью следующей строки:
defined( ‘ABSPATH’ ) or die( ‘Nope, not accessing this’ ); |
Класс wp_location_widget
Создадим базовую структуру класса wp_location_widget. Класс похож на предыдущие; но в этот раз мы расширим уже существующий класс WP_widget.
//главный виджет для отображения филиалов компании class wp_location_widget extends WP_widget{ } |
Функция __construct
В функции _construct мы определим наши базовые стили виджета. А сделаем мы это, перегрузив родительский конструктор и передав в него наши значения. Нам необходимо задать ID, название и описание виджета. Также мы прицепим нашу функцию register_wp_location_widgets к хуку widgets_init, чтобы зарегистрировать наш виджет.
//инициализация значений виджета public function __construct(){ //задаем базовые значения виджета (переписывая родительские) parent::__construct( ‘wp_location_widget’, ‘WP Location Widget’, array(‘description’ => ‘A widget that displays your locations’) ); add_action(‘widgets_init’,array($this,‘register_wp_location_widgets’)); } |
Создаем интерфейс виджета в панели администратора
Интерфейс это то, через что администратор будет взаимодействовать с нашим виджетом. Мы хотим добавить несколько вариантов, чтобы был выбор, по сколько мест отображать в виджете, а также возможность показывать всего одно место.
Функция form() унаследована от родительского класса WP_widget, она будет автоматически вызываться, когда мы будем заходить на экран виджета в панели администратора.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
//код back-end’а виджета //$instance – сохраняет значения формы public function form($instance){ //собираем переменные $location_id = (isset($instance[‘location_id’]) ? $instance[‘location_id’] : ‘default’); $number_of_locations = (isset($instance[‘number_of_locations’]) ? $instance[‘number_of_locations’] : 5); ?> <p>Select your options below</p> <p> <label for=“<?php echo $this–>get_field_name(‘location_id’); ?>“>Location to display</label> <select class=“widefat” name=“<?php echo $this–>get_field_name(‘location_id’); ?>“ id=“<?php echo $this–>get_field_id(‘location_id’); ?>“ value=“<?php echo $location_id; ?>“> <option value=“default”>All Locations</option> <?php $args = array( ‘posts_per_page’ => –1, ‘post_type’ => ‘wp_locations’ ); $locations = get_posts($args); if($locations){ foreach($locations as $location){ if($location–>ID == $location_id){ echo ‘<option selected value=”‘ . $location–>ID . ‘”>’ . get_the_title($location–>ID) . ‘</option>’; }else{ echo ‘<option value=”‘ . $location–>ID . ‘”>’ . get_the_title($location–>ID) . ‘</option>’; } } } ?> </select> </p> <p> <small>If you want to display multiple locations select how many below</small><br/> <label for=“<?php echo $this–>get_field_id(‘number_of_locations’); ?>“>Number of Locations</label> <select class=“widefat” name=“<?php echo $this–>get_field_name(‘number_of_locations’); ?>“ id=“<?php echo $this–>get_field_id(‘number_of_locations’); ?>“ value=“<?php echo $number_of_locations; ?>“> <option value=“default” <?php if($number_of_locations == ‘default’){ echo ‘selected’;}?>>All Locations</option> <option value=“1” <?php if($number_of_locations == ‘1’){ echo ‘selected’;}?>>1</option> <option value=“2” <?php if($number_of_locations == ‘2’){ echo ‘selected’;}?>>2</option> <option value=“3” <?php if($number_of_locations == ‘3’){ echo ‘selected’;}?>>3</option> <option value=“4” <?php if($number_of_locations == ‘4’){ echo ‘selected’;}?>>4</option> <option value=“5” <?php if($number_of_locations == ‘5’){ echo ‘selected’;}?>>5</option> <option value=“10” <?php if($number_of_locations == ’10’){ echo ‘selected’;}?>>10</option> </select> </p> <?php } |
Что тут происходит:
Сперва мы задаем ID и количество мест. Проверяем переменную $instance на наличие этих значений (существуют ли они). Если значения есть, извлекаем их. Если их нет, просто передаем стандартные значения (устанавливаем количество мест на 5 и передаем ID по умолчанию).
Создаем лейбл и форму для показа мест в панели администратора. С помощью функции get_posts() вытягиваем все места и показываем их. Каждое место мы проверяем на совпадение с сохраненным ID (если он был задан).
Создаем список select для отображения всех наших мест. И опять проверяем все варианты на совпадение с переданным значением.
Обновление виджета и сохранение вариантов
Для сохранения значений формы необходимо вызывать функцию обновления виджета. Функция update() унаследована от класса WP_widget. Нам осталось только указать, как сохранять значения.
//обновление виджета //$new_instance – новые значения, $old_instance – старые сохраненные значения public function update($new_instance, $old_instance){ $instance = array(); $instance[‘location_id’] = $new_instance[‘location_id’]; $instance[‘number_of_locations’] = $new_instance[‘number_of_locations’]; return $instance; } |
У нас есть две переменные $new_instance и $old_instance. В $new_instance хранятся текущие значения формы, а в $old_instance хранятся старые значения. Нам необходимо создать новый массив для хранения извлеченных значений для последующего их возврата.
Отображение виджета на стороне front-end’а
Функция widget() унаследована из класса WP_widget, с ее помощью виджет показывается на стороне front-end’а. Для формирования кода на вывод используется функция отображения из класса wp_simple_locations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//публичное отображение виджета //$args – аргументы виджета, $instance – сохраненные значения public function widget( $args, $instance ) { //получаем доступ к классу wp_simple_location class (с его помощью будем выводить виджет на экран) global $wp_simple_locations; //передаем все имеющиеся аргументы из виджета $arguments = array(); //если место задано //если задано конкретное место if($instance[‘location_id’] != ‘default’){ $arguments[‘location_id’] = $instance[‘location_id’]; } //если задано несколько мест if($instance[‘number_of_locations’] != ‘default’){ $arguments[‘number_of_locations’] = $instance[‘number_of_locations’]; } //вывод $html = ”; $html .= $args[‘before_widget’]; $html .= $args[‘before_title’]; $html .= ‘Locations’; $html .= $args[‘after_title’]; //используем главную функцию вывода из класса location $html .= $wp_simple_locations–>get_locations_output($arguments); $html .= $args[‘after_widget’]; echo $html; } |
Что мы тут делаем:
Создаем глобальный объект $wp_simple_locations, нам нужна его функция вывода на экран.
Создаем пустой массив аргументов и проверяем, заданы ли аргументы для виджета (количество мест, к примеру).
Начинаем формировать код на вывод и создаем переменную $html. Вызываем функцию get_locations_output(), прописанную в объекте $wp_simple_locations, и передаем в нее аргументы (нам вернется весь HTML код).
Выводим весь код из переменной $html на экран.
Регистрация виджета
Функция ниже нужна для регистрации виджета в WordPress. Необходимо вызвать функцию register_widget() и передать в нее название класса в качестве значения.
//регистрация виджета public function register_wp_location_widgets(){ register_widget(‘wp_location_widget’); } |
Надеюсь, вам понравился реальный пример разработки плагина для WordPress с нуля. Следите за моими статьями.
Автор: Simon Codrington
Источник: //www.sitepoint.com/
Редакция: Команда webformyself.
Это пошаговое руководство по разработке плагинов для WordPress. В нем описываются действия, которые я выполнял при разработке реального плагина.
- Описание необходимого функционала плагина
- Плагин для одного сайта
- Пошаговое руководство по разработке плагина для WordPress
- Начало
- Примечания к шагу 1
- Запрос последних записей всех типов
- Примечания к шагу 2
- Как запросить все записи, опубликованные в этом месяце
- Объединение запроса даты с основным запросом
- Примечания к шагу 4
- Отладка
- Примечания к шагу 5
- Повторяем попытку
- Примечания к шагу 6
- Это не сработало – отладка
- Примечания к шагу 7
- Пользовательский интерфейс и шорткод
- Примечания к шагу 8
- THE_DATE() работает довольно странно?
- Примечания к шагу 9
- Начало
- Общие мысли о разработке плагинов WordPress
Мы создадим плагин, который будет сообщать о том, какие посты (любого типа) были опубликованы в этом календарном месяце, и кто их авторы.
Я создавал этот плагин как индивидуальное решение для одного сайта. Поэтому нет необходимости делать плагин расширяемым с помощью хуков и тщательно продумывать такие моменты, как пространство имен и перевод.
Последовательность моих шагов:
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_action( 'init', 'wpshout_do_thing' ); function wpshout_do_thing() { if( ! isset( $_GET['wpsdt'] ) ) : return; endif; var_dump("Here"); die; }
Этот шаг позволяет убедиться, что WordPress видит мой плагин. А также что с его помощью можно осуществлять вывод данных на экране.
Обратите внимание на строку if( ! isset( $_GET[‘wpsdt’] ) ). В ней вызывается суперглобальная переменная PHP $_GET для запуска плагина. Она задействуется тогда, когда в URL-адресе есть строка запроса wpsdt. Этот прием позволяет запускать плагин путем изменения параметров URL-адресов.
Я также подключаю событие WordPress init, чтобы плагин запускался раньше остальных. По этой же причине я назвал перехваченную функцию wpshout_do_thing(). Благодаря чему она не вызовет конфликта пространства имен.
Код var_dump(); die позволяет убедиться, что плагин выводит данные и прекращает другие процессы PHP.
Результат работы кода, приведенного выше:
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_action( 'init', 'wpshout_do_thing' ); function wpshout_do_thing() { if( ! isset( $_GET['wpsdt'] ) ) : return; endif; $args = array( 'posts_per_page' => 5, 'post_type' => 'any', 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', ); $query = new WP_Query( $args ); var_dump( $query ); die; }
На этом шаге мы создаем пользовательский запрос WP_Query и выполняем var_dump(), чтобы убедиться, что запрос работает. Он запрашивает последние пять опубликованных записей любого типа.
В результате мы получаем объект WP_Query posts, который представляет собой массив из пяти записей.
Первоначально я не знал, как это реализовать. Но затем мне удалось найти пример, в котором показывается, как передать элемент date_query массива WP_Query:
'date_query' => array( 'after' => array( 'year' => 2012, 'month' => 3, 'day' => 1, ), ),
Но как в PHP указать, что нам нужен текущий месяц? Поиск привел меня к следующему примеру:
'date_query' => array( 'after' => array( 'year' => date( 'Y' ), 'month' => date( 'M' ), 'day' => 1, ), ),
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_action( 'init', 'wpshout_do_thing' ); function wpshout_do_thing() { if( ! isset( $_GET['wpsdt'] ) ) : return; endif; $args = array( 'posts_per_page' => -1, 'post_type' => 'any', 'post_status' => 'publish', 'date_query' => array( 'after' => array( 'year' => date( 'Y' ), 'month' => date( 'M' ), ), 'day' => 1, ), 'orderby' => 'date', 'order' => 'DESC', ); $query = new WP_Query( $args ); var_dump( $query ); die; }
На этом шаге мы добавляем параметр date_query в существующий WP_Query. А также изменяем значение posts_per_page на -1, чтобы получить все интересующие записи.
Результат выполнения приведенного выше кода:
Но этот код работает неправильно. Запрос возвращает не те записи, которые нам нужны.
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_action( 'init', 'wpshout_do_thing' ); function wpshout_do_thing() { if( ! isset( $_GET['wpsdt'] ) ) : return; endif; // $args = array( // 'posts_per_page' => -1, // 'post_type' => 'any', // 'post_status' => 'publish', // 'date_query' => array( // 'after' => array( // 'year' => date( 'Y' ), // 'month' => date( 'M' ), // ), // 'day' => 1, // ), // 'orderby' => 'date', // 'order' => 'DESC', // ); // $query = new WP_Query( $args ); $after = array( 'year' => date( 'Y' ), 'month' => date( 'M' ), ); var_dump($after ); die; }
На этом этапе мы выполняем следующие действия:
- Комментируем код, который ведет себя странно.
- Изучаем этот код.
Вся проблема в элементе after, который является частью массива date_query. Чтобы понять, что пошло не так, выведем информацию об этом элементе с помощью функции var_dump().
Оказывается, месяц является строкой, “Oct”. А date_query может принимать только числовое значение.
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_action( 'init', 'wpshout_do_thing' ); function wpshout_do_thing() { if( ! isset( $_GET['wpsdt'] ) ) : return; endif; $args = array( 'posts_per_page' => -1, 'post_type' => 'any', 'post_status' => 'publish', 'date_query' => array( 'after' => array( 'year' => date( 'Y' ), 'month' => date( 'm' ), ), 'day' => 1, ), 'orderby' => 'date', 'order' => 'DESC', ); $query = new WP_Query( $args ); // $after = array( // 'year' => date( 'Y' ), // 'month' => date( 'M' ), // ); var_dump( $query ); die; }
Замени значение ‘M’ на ‘m’ в date_query. Потому что ‘m’ дает нам числовое обозначение месяца, когда он передается в date().
Запуск этого кода дает следующее:
Но это тоже не сработало. Запрос WP_Query возвратил пустой массив posts.
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_action( 'init', 'wpshout_do_thing' ); function wpshout_do_thing() { if( ! isset( $_GET['wpsdt'] ) ) : return; endif; $args = array( 'posts_per_page' => -1, 'post_type' => 'any', 'post_status' => 'publish', 'date_query' => array( 'after' => array( 'year' => date( 'Y' ), 'month' => date( 'm' ), 'day' => 1, ), ), 'orderby' => 'date', 'order' => 'DESC', ); $query = new WP_Query( $args ); // $after = array( // 'year' => date( 'Y' ), // 'month' => date( 'M' ), // ); var_dump( $query ); die; }
Ошибка заключалась в том, что я использовал day как отдельный элемент массива date_query. А нужно поместить day внутрь элемента after из date_query. Исправление и повторный запуск кода дает следующее:
В результате мы получили десять записей текущего месяца!
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_shortcode( 'wpshout_show_this_months_posts_by_author', 'wpshout_show_this_months_posts_by_author' ); function wpshout_show_this_months_posts_by_author() { if( ! current_user_can( 'administrator' ) ) : return; endif; $args = array( 'posts_per_page' => -1, 'post_type' => 'any', 'post_status' => 'publish', 'date_query' => array( 'after' => array( 'year' => date( 'Y' ), 'month' => date( 'm' ), 'day' => 1, ), ), 'orderby' => 'date', 'order' => 'DESC', ); $query = new WP_Query( $args ); ob_start(); while( $query->have_posts() ) : $query->the_post(); ?> <h2><?php the_title(); ?></h2> By <?php the_author(); ?> on <?php the_date(); ?> <?php endwhile; wp_reset_postdata(); return ob_get_clean(); }
Мы будем использовать шорткоды. Для этого меняем add_action() на add_shortcode() и называем функцию шорткода wpshout_show_this_months_posts_by_author(). С помощью буферизации вывода мы сможем контролировать то, какую разметку будет возвращать шорткод. Внутри цикла while() используются теги шаблонов the_author() и the_date().
Запустив плагин с помощью шорткода wpshout_show_this_months_posts_by_author, мы получаем следующее:
Но почему в одном из выведенных постов отсутствует дата?
<?php /** * Plugin Name: WPShout Show Authorship this Month * Description: Show who's written what this month on WPShout * Version: 1.0 * Author: WPShout * Author URI: https://wpshout.com */ add_shortcode( 'wpshout_show_this_months_posts_by_author', 'wpshout_show_this_months_posts_by_author' ); function wpshout_show_this_months_posts_by_author() { if( ! current_user_can( 'administrator' ) ) : return; endif; $args = array( 'posts_per_page' => -1, 'post_type' => 'any', 'post_status' => 'publish', 'date_query' => array( 'after' => array( 'year' => date( 'Y' ), 'month' => date( 'm' ), 'day' => 1, ), ), 'orderby' => 'date', 'order' => 'DESC', ); $query = new WP_Query( $args ); ob_start(); while( $query->have_posts() ) : $query->the_post(); ?> <h2><?php the_title(); ?></h2> By <?php the_author(); ?> on <?php echo get_the_date( 'l, F d, Y' ); ?> <?php endwhile; wp_reset_postdata(); return ob_get_clean(); }
Оказалось, что функция the_date() выводит только один пост за одну дату. Если существуют две записи с одинаковой датой публикации, она перестает работать.
Также мне удалось выяснить, что у get_the_date() этой проблемы нет. Поэтому используем данную функцию. Результат запуск обновленного кода:
И это именно то, что нужно.
Разработка плагина для WordPress – это обучение, эксперименты, исследования с помощью метода проб и ошибок. Но главное использовать имеющиеся знания для выполнения простых задач и получать новые для выполнения более сложных.