Skip to content

Commit

Permalink
feat(forms): automatically support sticky forms
Browse files Browse the repository at this point in the history
fixes #11141
  • Loading branch information
jeabakker committed Nov 22, 2022
1 parent 7a7cfcb commit 320a811
Show file tree
Hide file tree
Showing 79 changed files with 1,071 additions and 725 deletions.
4 changes: 0 additions & 4 deletions actions/register.php
Expand Up @@ -8,8 +8,6 @@

/* @var $request \Elgg\Request */

elgg_make_sticky_form('register', ['password', 'password2']);

// Get variables
$username = (string) $request->getParam('username');
$password = (string) $request->getParam('password', null, false);
Expand Down Expand Up @@ -68,8 +66,6 @@
throw $e;
}

elgg_clear_sticky_form('register');

$response_data = [
'user' => $new_user,
];
Expand Down
4 changes: 0 additions & 4 deletions actions/useradd.php
Expand Up @@ -5,8 +5,6 @@

use Elgg\Exceptions\Configuration\RegistrationException;

elgg_make_sticky_form('useradd', ['password', 'password2']);

// Get variables
$username = get_input('username');
$password = get_input('password', null, false);
Expand Down Expand Up @@ -50,8 +48,6 @@
$new_user->makeAdmin();
}

elgg_clear_sticky_form('useradd');

$new_user->admin_created = true;
$new_user->created_by_guid = elgg_get_logged_in_user_guid();

Expand Down
18 changes: 17 additions & 1 deletion docs/appendix/upgrade-notes/4.x-to-5.0.rst
Expand Up @@ -369,13 +369,29 @@ Deprecated APIs
* ``elgg_trigger_plugin_hook`` use ``elgg_trigger_event_result``
* ``elgg_unregister_plugin_hook_handler`` use ``elgg_unregister_event_handler``

Removed classes
~~~~~~~~~~~~~~~

* ``Elgg\WebServices\ApiKeyForm``

Removed functions
~~~~~~~~~~~~~~~~~

* ``blog_prepare_form_vars``
* ``bookmarks_prepare_form_vars``
* ``discussion_prepare_form_vars``
* ``elgg_get_breadcrumbs``
* ``elgg_pop_breadcrumb``
* ``elgg_trigger_deprecated_plugin_hook``
* ``\ElggWidget->saveSettings()``
* ``file_prepare_form_vars``
* ``groups_prepare_form_vars``
* ``messages_prepare_form_vars``
* ``pages_prepare_form_vars``

Removed class functions
~~~~~~~~~~~~~~~~~~~~~~~

* ``\ElggWidget::saveSettings()``

Removed events
~~~~~~~~~~~~~~
Expand Down
115 changes: 60 additions & 55 deletions docs/guides/actions.rst
Expand Up @@ -462,6 +462,17 @@ The basic flow of using sticky forms is:
2. Use ``elgg_is_sticky_form($name)`` and ``elgg_get_sticky_values($name)`` to get sticky values when rendering a form view.
3. Call ``elgg_clear_sticky_form($name)`` after the action has completed successfully or after data has been loaded by ``elgg_get_sticky_values($name)``.

.. note::

As of Elgg 5.0 forms rendered with ``elgg_view_form()`` can set the ``$form_vars['sticky_enabled'] = true`` flag to automatically
get sticky form support. The submitted values to the action will automatically be filled in the ``$body_vars`` when an error occured in the action.

``elgg_view_form()`` supports the following ``$form_vars`` to help with sticky form support:

* ``sticky_enabled``: a ``bool`` to enable automatic sticky form support
* ``sticky_form_name``: an optional ``string`` to set where the sticky form values are saved. This defaults to the ``$action_name`` and should only be changed if the ``$action_name`` is different from the actual action
* ``sticky_ignored_fields: an ``array`` with the names fo the form fields that should be saved. For example password fields

Example: User registration
--------------------------

Expand Down Expand Up @@ -524,15 +535,14 @@ The form view for the save bookmark action uses ``elgg_extract()`` to pull value
$guid = elgg_extract('guid', $vars, null);
$shares = elgg_extract('shares', $vars, array());
The page handler scripts prepares the form variables and calls ``elgg_view_form()`` passing the correct values:
The page handler scripts enables sticky form support by passing the correct values to ``elgg_view_form()``:

.. code-block:: php
// mod/bookmarks/pages/add.php
$vars = bookmarks_prepare_form_vars();
$content = elgg_view_form('bookmarks/save', array(), $vars);
$content = elgg_view_form('bookmarks/save', ['sticky_enabled' => true]);
Similarly, ``mod/bookmarks/pages/edit.php`` uses the same function, but passes the entity that is being edited as an argument:
Similarly, ``mod/bookmarks/pages/edit.php`` uses the same sticky support, but passes the entity that is being edited:

.. code-block:: php
Expand All @@ -541,64 +551,59 @@ Similarly, ``mod/bookmarks/pages/edit.php`` uses the same function, but passes t
...
$vars = bookmarks_prepare_form_vars($bookmark);
$content = elgg_view_form('bookmarks/save', array(), $vars);
$content = elgg_view_form('bookmarks/save', ['sticky_enabled' => true], ['entity' => $bookmark]);
The library file defines ``bookmarks_prepare_form_vars()``. This function accepts an ``ElggEntity`` as an argument and does 3 things:
The plugin has an event listener on the ``'form:prepare:fields', 'bookmarks/save'`` event and the handler does 2 things:

1. Defines the input names and default values for form inputs.
2. Extracts the values from a bookmark object if it's passed.
3. Extracts the values from a sticky form if it exists.

.. code-block:: php
// mod/bookmarks/lib/bookmarks.php
function bookmarks_prepare_form_vars($bookmark = null) {
// input names => defaults
$values = array(
'title' => get_input('title', ''), // bookmarklet support
'address' => get_input('address', ''),
'description' => '',
'access_id' => ACCESS_DEFAULT,
'tags' => '',
'shares' => array(),
'container_guid' => elgg_get_page_owner_guid(),
'guid' => null,
'entity' => $bookmark,
);
if ($bookmark) {
foreach (array_keys($values) as $field) {
if (isset($bookmark->$field)) {
$values[$field] = $bookmark->$field;
}
}
}
if (elgg_is_sticky_form('bookmarks')) {
$sticky_values = elgg_get_sticky_values('bookmarks');
foreach ($sticky_values as $key => $value) {
$values[$key] = $value;
}
}
elgg_clear_sticky_form('bookmarks');
return $values;
}
The save action checks the input, then clears the sticky form upon success:

.. code-block:: php
// mod/bookmarks/actions/bookmarks/save.php
elgg_make_sticky_form('bookmarks');
...
if ($bookmark->save()) {
elgg_clear_sticky_form('bookmarks');
}
// mod/bookmarks/classes/Elgg/Bookmarks/Forms/PrepareFields.php
/**
* Prepare the fields for the bookmarks/save form
*
* @since 5.0
*/
class PrepareFields {
/**
* Prepare fields
*
* @param \Elgg\Event $event 'form:prepare:fields', 'bookmarks/save'
*
* @return array|null
*/
public function __invoke(\Elgg\Event $event): ?array {
$vars = $event->getValue();
// input names => defaults
$values = [
'title' => get_input('title', ''), // bookmarklet support
'address' => get_input('address', ''),
'description' => '',
'access_id' => ACCESS_DEFAULT,
'tags' => '',
'container_guid' => elgg_get_page_owner_guid(),
'guid' => null,
];
$bookmark = elgg_extract('entity', $vars);
if ($bookmark instanceof \ElggBookmark) {
// load current bookmark values
foreach (array_keys($values) as $field) {
if (isset($bookmark->$field)) {
$values[$field] = $bookmark->$field;
}
}
}
return array_merge($vars, $values);
}
}
The save action doesn't need to do anything with sticky form support as this is all handled by the system.

Ajax
====
Expand Down
4 changes: 4 additions & 0 deletions docs/guides/events-list.rst
Expand Up @@ -914,6 +914,10 @@ Views
**config, htmlawed** |results|
Filter the HTMLawed ``$config`` array.

**form:prepare:fields, <form_name>** |results|
Prepare field values for use in the form. Eg. when editing a blog, fill this with the current values of the blog.
Sticky form values will automatically be added to the field values (when available).

**head, page** |results|
In ``elgg_view_page()``, filters ``$vars['head']``
Return value contains an array with ``title``, ``metas`` and ``links`` keys,
Expand Down
47 changes: 47 additions & 0 deletions engine/classes/Elgg/Forms/PrepareFields.php
@@ -0,0 +1,47 @@
<?php

namespace Elgg\Forms;

/**
* Prepare sticky form fields
*
* @since 5.0
*/
class PrepareFields {

/**
* Generic sticky form fields handler
*
* @param \Elgg\Event $event 'form:prepare:fields', 'all'
*
* @return array|null
*/
public function __invoke(\Elgg\Event $event): ?array {
if (!(bool) $event->getParam('sticky_enabled')) {
return null;
}

$form_name = $event->getParam('sticky_form_name');
if (empty($form_name) || !elgg_is_sticky_form($form_name)) {
return null;
}

$ignored_fields = (array) $event->getParam('sticky_ignored_fields');
$body_vars = $event->getValue();

// merge the sticky values into the fields
$sticky_vars = elgg_get_sticky_values($form_name);
foreach ($sticky_vars as $key => $value) {
if (in_array($key, $ignored_fields)) {
// this shouldn't happen, but just in case
continue;
}
$body_vars[$key] = $value;
}

// clear the sticky values
elgg_clear_sticky_form($form_name);

return $body_vars;
}
}
3 changes: 3 additions & 0 deletions engine/classes/Elgg/Forms/StickyForms.php
Expand Up @@ -41,6 +41,9 @@ public function makeStickyForm(string $form_name, array $ignored_field_names = [
$default_ignored_field_names = [
'__elgg_ts', // never store CSRF tokens
'__elgg_token', // never store CSRF tokens
'_elgg_sticky_form_name', // from sticky form support
'_elgg_sticky_ignored_fields', // from sticky form support
'_route', // added by router
];
$ignored_field_names = array_merge($default_ignored_field_names, $ignored_field_names);

Expand Down
30 changes: 22 additions & 8 deletions engine/classes/Elgg/FormsService.php
Expand Up @@ -15,28 +15,35 @@ class FormsService {

use Loggable;

/**
* @var EventsService
*/
protected $events;

/**
* @var ViewsService
*/
private $views;
protected $views;

/**
* @var bool
*/
private $rendering;
protected $rendering;

/**
* @var string
*/
private $footer = '';
protected $footer = '';

/**
* Constructor
*
* @param ViewsService $views Views service
* @param ViewsService $views Views service
* @param EventsService $events Events service
*/
public function __construct(ViewsService $views) {
public function __construct(ViewsService $views, EventsService $events) {
$this->views = $views;
$this->events = $events;
}

/**
Expand Down Expand Up @@ -80,12 +87,16 @@ public function render(string $action, array $form_vars = [], array $body_vars =
'action' => elgg_generate_action_url($action, [], false),
'method' => 'post',
'ajax' => false,
'sticky_enabled' => false,
'sticky_form_name' => $action,
'sticky_ignored_fields' => [],
];

// append elgg-form class to any class options set
$form_vars['class'] = (array) elgg_extract('class', $form_vars, []);
$form_vars['class'][] = 'elgg-form-' . preg_replace('/[^a-z0-9]/i', '-', $action);

$form_vars['class'] = elgg_extract_class($form_vars, [
'elgg-form-' . preg_replace('/[^a-z0-9]/i', '-', $action)],
);

$form_vars = array_merge($defaults, $form_vars);

if (!isset($form_vars['enctype']) && strtolower($form_vars['method']) == 'post') {
Expand All @@ -104,6 +115,9 @@ public function render(string $action, array $form_vars = [], array $body_vars =
$form_vars['prevent_double_submit'] = (bool) elgg_extract('prevent_double_submit', $form_vars, true);

if (!isset($form_vars['body'])) {
// prepare body vars
$body_vars = (array) $this->events->triggerResults('form:prepare:fields', $action, $form_vars, $body_vars);

$this->rendering = true;
$this->footer = '';

Expand Down

0 comments on commit 320a811

Please sign in to comment.