Routing
Elgg has two mechanisms to respond to HTTP requests that don’t already go through the Actions and Simplecache systems.
URL Identifier and Segments
After removing the site URL, Elgg splits the URL path by /
into an array. The first
element, the identifier, is shifted off, and the remaining elements are called the
segments. For example, if the site URL is http://example.com/elgg/
, the URL
http://example.com/elgg/blog/owner/jane?foo=123
produces:
Identifier: 'blog'
. Segments: ['owner', 'jane']
. (the query string parameters are
available via get_input()
)
The site URL (home page) is a special case that produces an empty string identifier and an empty segments array.
Warning
URL identifier/segments should be considered potentially dangerous user input. Elgg uses htmlspecialchars
to escapes HTML entities in them.
Page Handling
Elgg offers a facility to manage your plugin pages via custom routes, enabling URLs like http://yoursite/my_plugin/section
.
You can register a new route using elgg_register_route()
, or via routes
config in elgg-plugin.php
.
Routes map to resource views, where you can render page contents.
// 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
In the example above, we have registered a new route that is accessible via http://yoursite/my_plugin/section/<guid>/<subsection>
.
Whenever that route is accessed with a required guid
segment and an optional subsection
segment, the router
will render the specified my_plugin/section
resource view and pass the parameters extracted from the URL to your
resource view with $vars
.
Routes names
Route names can then be used to generate a URL:
$url = elgg_generate_url('my_plugin:section', [
'guid' => $entity->guid,
'subsection' => 'assets',
]);
The route names are unique across all plugins and core, so another plugin can override the route by registering different parameters to the same route name.
Route names follow a certain convention and in certain cases will be used to automatically resolve URLs, e.g. to display an entity.
The following conventions are used in core and recommended for plugins:
- view:<entity_type>:<entity_subtype>
Maps to the entity profile page, e.g.
view:user:user
orview:object:blog
The path must contain aguid
, orusername
for users- edit:<entity_type>:<entity_subtype>
Maps to the form to edit the entity, e.g.
edit:user:user
oredit:object:blog
The path must contain aguid
, orusername
for users If you need to add subresources, use suffixes, e.g.edit:object:blog:images
, keeping at least one subresource as a default without suffix.- add:<entity_type>:<entity_subtype>
Maps to the form to add a new entity of a given type, e.g.
add:object:blog
The path, as a rule, containscontainer_guid
parameter- collection:<entity_type>:<entity_subtype>:<collection_type>
Maps to listing pages. Common route names used in core are, as follows:
collection:object:blog:all
: list all blogscollection:object:blog:owner
: list blogs owned by a user with a given usernamecollection:object:blog:friends
: list blogs owned by friends of the logged in user (or user with a given username)collection:object:blog:group
: list blogs in a group
- default:<entity_type>:<entity_subtype>
Maps to the default page for a resource, e.g. the path
/blog
. Elgg happens to use the “all” collection for these routes.default:object:blog
: handle the generic path/blog
.
<entity_subtype>
can be omitted from route names to register global routes applicable to all entities of a given type.
URL generator will first try to generate a URL using the subtype, and will then fallback to a route name without a subtype.
For example, user profiles are routed to the same resource view regardless of user subtype.
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
Route configuration
Segments can be defined using wildcards, e.g. profile/{username}
, which will match all URLs that contain profile/
followed by
and arbitrary username.
To make a segment optional you can add a ?
(question mark) to the wildcard name, e.g. profile/{username}/{section?}
.
In this case the URL will be matched even if the section
segment is not provided.
You can further constrain segments using regex requirements:
// 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',
],
],
]
];
By default, Elgg will set the following requirements for named URL segments:
$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
];
Plugin dependent routes
If a route requires a specific plugin to be active this can be configured in the route configuration.
// 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
],
],
]
];
Route middleware
Route middleware can be used to prevent access to a certain route, or to perform some business logic before the route is rendered. Middleware can be used, e.g. to implement a paywall, or to log analytics, or to set open graph metatags.
Elgg core implements several middleware handlers. The following middleware can be found in the namespace \Elgg\Router\Middleware
:
Gatekeeper
This gatekeeper will prevent access by non-authenticated users.
AdminGatekeeper
This gatekeeper will prevent access by non-admin users.
LoggedOutGatekeeper
This gatekeeper will prevent access by authenticated users.
AjaxGatekeeper
This gatekeeper will prevent access with non-xhr requests.
PageOwnerGatekeeper
This gatekeeper will prevent access if there is no pageowner entity.
GroupPageOwnerGatekeeper
This gatekeeper extends the PageOwnerGatekeeper
but also requires the pageowner to be a ElggGroup
entity.
UserPageOwnerGatekeeper
This gatekeeper extends the PageOwnerGatekeeper
but also requires the pageowner to be an ElggUser
entity.
PageOwnerCanEditGatekeeper
This gatekeeper will prevent access if there is no pageowner detected and the pageowner can’t be editted.
GroupPageOwnerCanEditGatekeeper
This gatekeeper extends the PageOwnerCanEditGatekeeper
but also requires the pageowner to be a ElggGroup
entity.
UserPageOwnerCanEditGatekeeper
This gatekeeper extends the PageOwnerCanEditGatekeeper
but also requires the pageowner to be an ElggUser
entity.
CsrfFirewall
This middleware will prevent access without the correct CSRF tokens. This middleware will automatically be applied to actions.
ActionMiddleware
This middleware will provide action related logic. This middleware will automatically be applied to actions.
SignedRequestGatekeeper
This gatekeeper will prevent access if the url has been tampered with.
A secure URL can be generated using the elgg_http_get_signed_url
function.
UpgradeGatekeeper
This gatekeeper will prevent access if the upgrade URL is secured and the URL is invalid.
WalledGarden
This middleware will prevent access to a route if the site is configured for authenticated users only and there is no authenticated user logged in. This middleware is automatically enabled for all routes. You can disable the walled garden gatekeeper with a route config option.
Custom Middleware
Middleware handlers can be set to any callable that receives an instance of \Elgg\Request
:
The handler should throw an instance of \Elgg\Exceptions\HttpException
to prevent route access.
The handler can return an instance of \Elgg\Http\ResponseBuilder
to prevent further implementation of the routing sequence (a redirect response can be returned to re-route the request).
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,
]
]);
Route controllers
In certain cases, using resource views is not appropriate. In these cases you can use a controller - any callable
that receives an instance of \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'],
]);
The route:rewrite
event
For URL rewriting, the route:rewrite
event (with similar arguments as route
) is triggered very early,
and allows modifying the request URL path (relative to the Elgg site).
Here we rewrite requests for news/*
to 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');
Warning
The event must be registered directly in your plugin Bootstrap boot
function. The init
function is too late.
Routing overview
For regular pages, Elgg’s program flow is something like this:
A user requests
http://example.com/news/owner/jane
.Plugins are initialized.
Elgg parses the URL to identifier
news
and segments['owner', 'jane']
.Elgg triggers the event
route:rewrite, news
(see above).Elgg finds a registered route that matches the final route path, and renders a resource view associated with it. It calls
elgg_view_resource('blog/owner', $vars)
where$vars
contains the username.The
resources/blog/owner
view gets the username via$vars['username']
, and uses many other views and formatting functions likeelgg_view_layout()
andelgg_view_page()
to create the entire HTML page.PHP invokes Elgg’s shutdown sequence.
The user receives a fully rendered page.
Elgg’s coding standards suggest a particular URL layout, but there is no syntax enforced.