Skip to content

Commit

Permalink
Merge 9fbd4a8 into de13eb4
Browse files Browse the repository at this point in the history
  • Loading branch information
mrclay committed Jan 3, 2018
2 parents de13eb4 + 9fbd4a8 commit 12b9bd3
Show file tree
Hide file tree
Showing 72 changed files with 1,545 additions and 550 deletions.
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -42,7 +42,8 @@
"symfony/var-dumper": "~3.3",
"fzaninotto/faker": "^1.6",
"peppeocchi/php-cron-scheduler": "2.*",
"bower-asset/normalize-css": "dev-master"
"bower-asset/normalize-css": "dev-master",
"symfony/routing": "^3.3"
},
"config": {
"process-timeout": 0,
Expand Down
86 changes: 82 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 20 additions & 5 deletions docs/guides/context.rst
@@ -1,11 +1,26 @@
Context
=======

Within the Elgg framework, context can be used to by your plugin's functions to determine if they should run or not. You will be registering callbacks to be executed when particular :doc:`events are triggered <events-list>`. Sometimes the events are generic and you only want to run your callback when your plugin caused the event to be triggered. In that case, you can use the page's context.
.. warning::

You can explicitly set the context with ``set_context()``. The context is a string and typically you set it to the name of your plugin. You can retrieve the context with the function ``get_context()``.
It's however better to use ``elgg_push_context($string)`` to add a context to the stack. You can check if the context you want in in the current stack by calling ``elgg_in_context($context)``. Don't forget to pop (with ``elgg_pop_context()``) the context after you push one and don't need it anymore.
The contents of this page are outdated. While the functionality is still in place, using global context to
determine your business logic is bad practice, and will make your code less testable and succeptive to bugs.

If you don't set it, Elgg tries to guess the context. If the page was called through the page handler, the context is set to the name of the handler which was set in ``elgg_register_page_handler()``. If the page wasn't called through the page handler, it uses the name of your plugin directory. If it cannot determine that, it returns main as the default context.

Sometimes a view will return different HTML depending on the context. A plugin can take advantage of that by setting the context before calling ``elgg_view()`` on the view and then setting the context back. This is frequently done with the search context.
Within the Elgg framework, context can be used by your plugin's functions to determine if they should run or not.
You will be registering callbacks to be executed when particular :doc:`events are triggered <events-list>`.
Sometimes the events are generic and you only want to run your callback when your plugin caused the event to be triggered.
In that case, you can use the page's context.

You can explicitly set the context with ``set_context()``. The context is a string and typically you set it to the name of your plugin.
You can retrieve the context with the function ``get_context()``.
It's however better to use ``elgg_push_context($string)`` to add a context to the stack.
You can check if the context you want in in the current stack by calling ``elgg_in_context($context)``.
Don't forget to pop (with ``elgg_pop_context()``) the context after you push one and don't need it anymore.

If you don't set it, Elgg tries to guess the context. If the page was called through the router,
the context is set to the first segment of the current route, e.g. ``profile`` in ``profile/username``.

Sometimes a view will return different HTML depending on the context.
A plugin can take advantage of that by setting the context before calling ``elgg_view()`` on the view and then setting the context back.
This is frequently done with the search context.
1 change: 1 addition & 0 deletions docs/guides/hooks-list.rst
Expand Up @@ -250,6 +250,7 @@ Action hooks

**forward, <reason>**
Filter the URL to forward a user to when ``forward($url, $reason)`` is called.
In certain cases, the ``params`` array will contain an instance of ``HttpException`` that triggered the error.

**response, action:<action>**
Filter an instance of ``\Elgg\Http\ResponseBuilder`` before it is sent to the client.
Expand Down
1 change: 0 additions & 1 deletion docs/guides/index.rst
Expand Up @@ -23,7 +23,6 @@ Customize Elgg's behavior with plugins.
javascript
menus
notifications
pagehandler
routing
services
search
Expand Down
27 changes: 0 additions & 27 deletions docs/guides/pagehandler.rst

This file was deleted.

142 changes: 106 additions & 36 deletions docs/guides/routing.rst
Expand Up @@ -20,47 +20,120 @@ 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 Handler
============
Page Handling
=============

To handle all URLs that begin with a particular identifier, you can register a function to
act as a :doc:`/guides/pagehandler`. When the handler is called, the segments array is
passed in as the first argument.

The following code registers a page handler for "blog" URLs and shows how one might route
the request to a resource view.
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.

.. code-block:: php
elgg_register_page_handler('blog', 'blog_page_handler');
// 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+',
],
]);
function blog_page_handler(array $segments) {
// if the URL is http://example.com/elgg/blog/view/123/my-blog-post
// $segments contains: ['view', '123', 'my-blog-post']
// in my_plugin/views/default/resources/my_plugin/section.php
$guid = elgg_extract('guid', $vars);
$subsection = elgg_extract('subsection', $vars);
$subpage = elgg_extract(0, $segments);
if ($subpage === 'view') {
// render content
// use a view for the page logic to allow other plugins to easily change it
$resource = elgg_view_resource('blog/view', [
'guid' => (int)elgg_extract(1, $segments);
]);
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``.

return elgg_ok_response($resource);
}

// redirect to a different location
if ($subpage === '') {
return elgg_redirect_response('blog/all');
}
Routes names
------------

// send an error page
if ($subpage === 'owner' && !elgg_entity_exists($segments[1])) {
return elgg_error_response('User not found', 'blog/all', ELGG_HTTP_NOT_FOUND);
}
// ... handle other subpages
}
Route names can then be used to generate a URL:

.. code::php
$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`` or ``view:object:blog``
The path must contain a ``guid``, or ``username`` for users

**edit:<entity_type>:<entity_subtype>**
Maps to the form to edit the entity, e.g. ``edit:user:user`` or ``edit:object:blog``
The path must contain a ``guid``, or ``username`` 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, contains ``container_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 blogs
- ``collection:object:blog:owner``: list blogs owned by a user with a given username
- ``collection: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


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:

.. php::code
// 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:

.. php::code
$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
];
The ``route`` Plugin Hook
Expand Down Expand Up @@ -130,13 +203,10 @@ For regular pages, Elgg's program flow is something like this:
#. Elgg parses the URL to identifier ``news`` and segments ``['owner', 'jane']``.
#. Elgg triggers the plugin hook ``route:rewrite, news`` (see above).
#. Elgg triggers the plugin hook ``route, blog`` (was rewritten in the rewrite hook).
#. Elgg finds a registered page handler (see above) for ``blog``, and calls the function, passing in
the segments.
#. The page handler function determines it needs to render a single user's blog. It calls
``elgg_view_resource('blog/owner', $vars)`` where ``$vars`` contains the username.
#. 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 like ``elgg_view_layout()`` and ``elgg_view_page()`` to create the entire HTML page.
#. The page handler echos the view HTML and returns ``true`` to indicate it handled the request.
#. PHP invokes Elgg's shutdown sequence.
#. The user receives a fully rendered page.

Expand Down
6 changes: 4 additions & 2 deletions docs/guides/walled-garden.rst
Expand Up @@ -13,13 +13,15 @@ From the Advanced Settings page, find the option labelled "Restrict pages to log
Exposing pages through Walled Gardens
-------------------------------------

Many plugins extend Elgg by adding pages. Walled Garden mode will prevent these pages from being viewed by logged out users. Elgg uses :ref:`plugin hook <design/events#plugin-hooks>` to manage which pages are visible through the Walled Garden.
Many plugins extend Elgg by adding pages. Walled Garden mode will prevent these pages from being viewed by logged out users.
Elgg uses :ref:`plugin hook <design/events#plugin-hooks>` to manage which pages are visible through the Walled Garden.

Plugin authors must register pages as public if they should be viewable through Walled Gardens by responding to the ``public_pages``, ``walled_garden`` plugin hook.

The returned value is an array of regexp expressions for public pages.

The following code shows how to expose http://example.org/my_plugin/public_page through a Walled Garden. This assumes the plugin has registered a :doc:`pagehandler` for ``my_plugin``.
The following code shows how to expose http://example.org/my_plugin/public_page through a Walled Garden.
This assumes the plugin has registered a :doc:`route </guides/routing>` for ``my_plugin/public_page``.

.. code-block:: php
Expand Down

0 comments on commit 12b9bd3

Please sign in to comment.