Маршрутизация
Elgg имеет два механизма для ответа на HTTP-запросы, которые ещё не проходят через системы Формы и действия и Simplecache.
Содержание
Идентификатор и сегменты URL
После удаления URL сайта Elgg разделяет путь URL по / на массив. Первый элемент, идентификатор, сдвигается, а оставшиеся элементы называются сегментами. Например, если URL сайта — http://example.com/elgg/, URL http://example.com/elgg/blog/owner/jane?foo=123 выдаёт:
Идентификатор: 'blog'. Сегменты: ['owner', 'jane']. (параметры строки запроса доступны через get_input())
URL сайта (домашняя страница) — это особый случай, который выдаёт пустой строковый идентификатор и пустой массив сегментов.
Предупреждение
Идентификатор/сегменты URL следует считать потенциально опасным пользовательским вводом. Elgg использует htmlspecialchars для экранирования сущностей HTML в них.
Обработка страниц
Elgg предлагает возможность управления страницами вашего плагина через пользовательские маршруты, позволяя использовать такие URL, как http://yoursite/my_plugin/section. Вы можете зарегистрировать новый маршрут с помощью elgg_register_route() или через конфигурацию routes в elgg-plugin.php. Маршруты сопоставляются с представлениями ресурсов, где вы можете отрисовывать содержимое страниц.
// in your 'init', 'system' handler
elgg_register_route('my_plugin:section' [
'path' => '/my_plugin/section/{guid}/{subsection?}',
'resource' => 'my_plugin/section',
'requirements' => [
'guid' => '\d+',
'subsection' => '\w+',
],
]);
// in my_plugin/views/default/resources/my_plugin/section.php
$guid = elgg_extract('guid', $vars);
$subsection = elgg_extract('subsection', $vars);
// render content
В приведённом выше примере мы зарегистрировали новый маршрут, доступный через http://yoursite/my_plugin/section/<guid>/<subsection>. Каждый раз, когда этот маршрут запрашивается с обязательным сегментом guid и необязательным сегментом subsection, маршрутизатор отрисует указанное представление ресурса my_plugin/section и передаст параметры, извлечённые из URL, в ваше представление ресурса с $vars.
Имена маршрутов
Затем имена маршрутов можно использовать для генерации URL:
$url = elgg_generate_url('my_plugin:section', [
'guid' => $entity->guid,
'subsection' => 'assets',
]);
Имена маршрутов уникальны для всех плагинов и ядра, поэтому другой плагин может переопределить маршрут, зарегистрировав другие параметры для того же имени маршрута.
Имена маршрутов следуют определённому соглашению и в определённых случаях будут использоваться для автоматического разрешения URL, например, для отображения сущности.
Следующие соглашения используются в ядре и рекомендуются для плагинов:
- view:<entity_type>:<entity_subtype>
Сопоставляется со страницей профиля сущности, например,
view:user:userилиview:object:blog. Путь должен содержатьguidилиusernameдля пользователей- edit:<entity_type>:<entity_subtype>
Сопоставляется с формой для редактирования сущности, например,
edit:user:userилиedit:object:blog. Путь должен содержатьguidилиusernameдля пользователей. Если вам нужно добавить подресурсы, используйте суффиксы, например,edit:object:blog:images, сохраняя хотя бы один подресурс по умолчанию без суффикса.- add:<entity_type>:<entity_subtype>
Сопоставляется с формой для добавления новой сущности заданного типа, например,
add:object:blog. Путь, как правило, содержит параметрcontainer_guid- collection:<entity_type>:<entity_subtype>:<collection_type>
Сопоставляется со страницами списков. Общие имена маршрутов, используемые в ядре, следующие:
collection:object:blog:all: список всех блоговcollection:object:blog:owner: список блогов, принадлежащих пользователю с заданным именем пользователяcollection:object:blog:friends: список блогов, принадлежащих друзьям авторизованного пользователя (или пользователя с заданным именем пользователя)collection:object:blog:group: список блогов в группе
- default:<entity_type>:<entity_subtype>
Сопоставляется со страницей по умолчанию для ресурса, например, путь
/blog. Elgg по совпадению использует коллекцию «all» для этих маршрутов.default:object:blog: обработка общего пути/blog.
<entity_subtype> можно опустить из имён маршрутов для регистрации глобальных маршрутов, применимых ко всем сущностям заданного типа. Генератор URL сначала попытается сгенерировать URL с использованием подтипа, а затем откатится к имени маршрута без подтипа. Например, профили пользователей маршрутизируются к одному и тому же представлению ресурса независимо от подтипа пользователя.
elgg_register_route('view:object:attachments', [
'path' => '/attachments/{guid}',
'resource' => 'attachments',
]);
elgg_register_route('view:object:blog:attachments', [
'path' => '/blog/view/{guid}/attachments',
'resource' => 'blog/attachments',
]);
$blog = get_entity($blog_guid);
$url = elgg_generate_entity_url($blog, 'view', 'attachments'); // /blog/view/$blog_guid/attachments
$other = get_entity($other_guid);
$url = elgg_generate_entity_url($other, 'view', 'attachments'); // /attachments/$other_guid
Конфигурация маршрута
Сегменты можно определять с использованием подстановочных знаков, например, profile/{username}, который будет соответствовать всем URL, содержащим profile/, за которым следует произвольное имя пользователя.
Чтобы сделать сегмент необязательным, вы можете добавить ? (вопросительный знак) к имени подстановочного знака, например, profile/{username}/{section?}. В этом случае URL будет соответствовать, даже если сегмент section не предоставлен.
Вы можете дополнительно ограничить сегменты, используя требования регулярных выражений:
// elgg-plugin.php
return [
'routes' => [
'profile' => [
'path' => '/profile/{username}/{section?}',
'resource' => 'profile',
'requirements' => [
'username' => '[\p{L}\p{Nd}._-]+', // only allow valid usernames
'section' => '\w+', // can only contain alphanumeric characters
],
'defaults' => [
'section' => 'index',
],
],
]
];
По умолчанию Elgg установит следующие требования для именованных сегментов URL:
$patterns = [
'guid' => '\d+', // only digits
'group_guid' => '\d+', // only digits
'container_guid' => '\d+', // only digits
'owner_guid' => '\d+', // only digits
'username' => '[\p{L}\p{Nd}._-]+', // letters, digits, underscores, dashes
];
Примечание
Вы можете иметь параметры маршрута username и/или guid, заполненные значениями авторизованного пользователя по умолчанию, если вы добавите флаг use_logged_in и установите его в true.
Маршруты, зависящие от плагина
Если маршрут требует, чтобы конкретный плагин был активен, это можно настроить в конфигурации маршрута.
// elgg-plugin.php
return [
'routes' => [
'collection:object:blog:friends' => [
'path' => '/blog/friends/{username?}/{lower?}/{upper?}',
'resource' => 'blog/friends',
'required_plugins' => [
'friends', // route only allowed when friends plugin is active
],
],
]
];
Промежуточное ПО маршрута
Промежуточное ПО маршрута можно использовать для предотвращения доступа к определённому маршруту или для выполнения некоторой бизнес-логики перед отрисовкой маршрута. Промежуточное ПО можно использовать, например, для реализации платного доступа, или для логирования аналитики, или для установки метатегов Open Graph.
Ядро Elgg реализует несколько обработчиков промежуточного ПО. Следующее промежуточное ПО можно найти в пространстве имён \Elgg\Router\Middleware:
Привратник
Этот привратник предотвратит доступ неавторизованных пользователей.
AdminGatekeeper
Этот привратник предотвратит доступ неадминистраторов.
LoggedOutGatekeeper
Этот привратник предотвратит доступ авторизованных пользователей.
AjaxGatekeeper
Этот привратник предотвратит доступ с запросами, не являющимися XHR.
PageOwnerGatekeeper
Этот привратник предотвратит доступ, если нет сущности владельца страницы.
GroupPageOwnerGatekeeper
Этот привратник расширяет PageOwnerGatekeeper, но также требует, чтобы владелец страницы был сущностью ElggGroup.
GroupToolGatekeeper
Этот привратник расширяет GroupPageOwnerGatekeeper и дополнительно проверит, что настроенный групповой инструмент включён.
Чтобы предоставить GroupToolGatekeeper групповой инструмент для проверки, добавьте его в опции маршрута.
// in elgg-plugin.php
'routes' => [
'collection:object:news:group' => [
'path' => '/news/group/{guid}',
'controller' => \Elgg\Controllers\GenericContentListing::class,
'options' => [
'group_tool' => 'news',
],
],
],
Примечание
Когда групповой инструмент настроен в опциях маршрута, GroupToolGatekeeper автоматически добавляется как промежуточное ПО. Если предоставлен групповой инструмент, нет необходимости добавлять промежуточное ПО GroupPageOwnerGatekeeper.
UserPageOwnerGatekeeper
Этот привратник расширяет PageOwnerGatekeeper, но также требует, чтобы владелец страницы был сущностью ElggUser.
PageOwnerCanEditGatekeeper
Этот привратник предотвратит доступ, если не обнаружен владелец страницы и владелец страницы не может быть отредактирован.
GroupPageOwnerCanEditGatekeeper
Этот привратник расширяет PageOwnerCanEditGatekeeper, но также требует, чтобы владелец страницы был сущностью ElggGroup.
UserPageOwnerCanEditGatekeeper
Этот привратник расширяет PageOwnerCanEditGatekeeper, но также требует, чтобы владелец страницы был сущностью ElggUser.
CsrfFirewall
Это промежуточное ПО предотвратит доступ без правильных токенов CSRF. Это промежуточное ПО будет автоматически применено к действиям.
ActionMiddleware
Это промежуточное ПО предоставит логику, связанную с действиями. Это промежуточное ПО будет автоматически применено к действиям.
SignedRequestGatekeeper
Этот привратник предотвратит доступ, если URL был изменён. Безопасный URL можно сгенерировать с помощью функции elgg_http_get_signed_url.
UpgradeGatekeeper
Этот привратник предотвратит доступ, если URL обновления защищён и URL недействителен.
WalledGarden
Это промежуточное ПО предотвратит доступ к маршруту, если сайт настроен только для авторизованных пользователей и нет авторизованного пользователя. Это промежуточное ПО автоматически включено для всех маршрутов. Вы можете отключить привратника закрытого сада с помощью опции конфигурации маршрута.
Пользовательское промежуточное ПО
Обработчики промежуточного ПО можно установить в любой вызываемый объект, который получает экземпляр \Elgg\Request: Обработчик должен выбросить экземпляр \Elgg\Exceptions\HttpException, чтобы предотвратить доступ к маршруту. Обработчик может вернуть экземпляр \Elgg\Http\ResponseBuilder, чтобы предотвратить дальнейшую реализацию последовательности маршрутизации (можно вернуть ответ перенаправления для перенаправления запроса).
class MyMiddleware {
public function __invoke(\Elgg\Request $request) {
$entity = $request->getEntityParam();
if ($entity) {
// do stuff
} else {
throw new EntityNotFoundException();
}
}
}
elgg_register_route('myroute', [
'path' => '/myroute/{guid?}',
'resource' => 'myroute',
'middleware' => [
\Elgg\Router\Middleware\Gatekeeper::class,
MyMiddleware::class,
]
]);
Контроллеры маршрутов
В определённых случаях использование представлений ресурсов нецелесообразно. В этих случаях вы можете использовать контроллер — любой вызываемый объект, который получает экземпляр \Elgg\Request:
class MyController {
public function handleFoo(\Elgg\Request $request) {
elgg_set_http_header('Content-Type: application/json');
$data = [
'entity' => $request->getEntityParam(),
];
return elgg_ok_response($data);
}
}
elgg_register_route('myroute', [
'path' => '/myroute/{guid?}',
'controller' => [MyController::class, 'handleFoo'],
]);
Контроллер списка
Elgg предлагает контроллер списка по умолчанию. Этот контроллер отрисует полную страницу с:
Заголовок
Навигационная цепочка
Вкладки фильтра по умолчанию
Кнопка добавления нового контента (если доступна)
Список элементов контента
Всё это делается на основе рекомендованных имён маршрутов.
collection:<entity_type>:<entity_subtype>:<collection_type> default:<entity_type>:<entity_subtype>
Заголовок страницы будет языковым ключом collection:<entity_type>:<entity_subtype>:<collection_type>, для маршрута default:<entity_type>:<entity_subtype> это будет collection:<entity_type>:<entity_subtype>:all.
// elgg-plugin.php
return [
'routes' => [
'collection:object:my_content:all' => [
'path' => '/my_path/all',
'controller' => \Elgg\Controllers\GenericContentListing::class,
],
'collection:object:my_content:friends' => [
'path' => '/my_path/friends/{username}',
'controller' => \Elgg\Controllers\GenericContentListing::class,
'middleware' => [
\Elgg\Router\Middleware\UserPageOwnerGatekeeper::class,
],
],
'collection:object:my_content:group' => [
'path' => '/my_path/group/{guid}',
'controller' => \Elgg\Controllers\GenericContentListing::class,
'middleware' => [
\Elgg\Router\Middleware\GroupPageOwnerGatekeeper::class, // no needed if a group tool is provided
],
'options' => [
'group_tool' => 'my_group_tool', // optional, if provided the group will need to have this tool enabled
],
],
'collection:object:my_content:owner' => [
'path' => '/my_path/owner/{username}',
'controller' => \Elgg\Controllers\GenericContentListing::class,
'middleware' => [
\Elgg\Router\Middleware\UserPageOwnerGatekeeper::class,
],
'options' => [
'sidebar_view' => 'my_content/sidebar', // optional
],
],
'default:object:my_content' => [
'path' => '/my_path',
'controller' => \Elgg\Controllers\GenericContentListing::class,
],
],
];
Если у вас есть дополнительный список коллекции, вы можете расширить класс \Elgg\Controllers\GenericContentListing и добавить функцию listCollectionType, где CollectionType берётся из имени маршрута и преобразуется в camel case, например, collection:object:my_content:my_listing вызовет функцию listMyListing().
В контроллере есть вспомогательные функции, которые вы можете расширить, если вам нужно просто изменить некоторые параметры.
Примечание
Если вы предоставите определение маршрута с опцией sidebar_view, это представление будет вызвано во время отрисовки страницы.
Событие route:rewrite
Для перезаписи URL событие route:rewrite (с аналогичными аргументами, как route) запускается очень рано и позволяет изменять путь запроса URL (относительно сайта Elgg).
Здесь мы перезаписываем запросы для news/* на blog/*:
function myplugin_rewrite_handler(\Elgg\Event $event) {
$value = $event->getValue();
$value['identifier'] = 'blog';
return $value;
}
elgg_register_event_handler('route:rewrite', 'news', 'myplugin_rewrite_handler');
Предупреждение
Событие должно быть зарегистрировано напрямую в функции boot загрузки вашего плагина. Функция init слишком поздняя.
Обзор маршрутизации
Для обычных страниц поток программы Elgg выглядит примерно так:
Пользователь запрашивает
http://example.com/news/owner/jane.Плагины инициализируются.
Elgg разбирает URL на идентификатор
newsи сегменты['owner', 'jane'].Elgg запускает событие
route:rewrite, news(см. выше).Elgg находит зарегистрированный маршрут, соответствующий окончательному пути маршрута, и отрисовывает связанное с ним представление ресурса. Он вызывает
elgg_view_resource('blog/owner', $vars), где$varsсодержит имя пользователя.Представление
resources/blog/ownerполучает имя пользователя через$vars['username']и использует множество других представлений и функций форматирования, таких какelgg_view_layout()иelgg_view_page(), для создания всей страницы HTML.PHP вызывает последовательность завершения работы Elgg.
Пользователь получает полностью отрисованную страницу.
Стандарты кодирования Elgg предлагают определённый макет URL, но синтаксис не принудителен.