Formularios y acciones¶
Los formularios y las acciones permiten crear, actualizar o eliminar contenido.
Los formularios de Elgg envían información a las acciones. Las acciones definen el comportamiento ante los datos recibidos.
Esta guía asume que usted está ya familiarizado con:
Contents
Registrar acciones¶
Las acciones deben registrarse antes de poder usarlas. Para registrar acciones, use elgg_register_action
:
elgg_register_action("example", __DIR__ . "/actions/example.php");
El script mod/example/actions/example.php
se ejecutará a partir de ahora cada vez que se envíe un formulario a http://localhost/elgg/action/example
.
Advertencia
A stumbling point for many new developers is the URL for actions. The URL always uses /action/
(singular) and never /actions/
(plural). However, action script files are usually saved under the directory /actions/
(plural) and always have an extension.
Permisos¶
De manera predeterminada, las acciones sólo están disponibles para usuarios registrados.
To make an action available to logged out users, pass "public"
as the third parameter:
elgg_register_action("example", $filepath, "public");
To restrict an action to only administrators, pass "admin"
for the last parameter:
elgg_register_action("example", $filepath, "admin");
Writing action files¶
Use la función get_input
para obtener acceso a los parámetros de la solicitud:
$field = get_input('input_field_name', 'default_value');
Puede usar la API Base de datos para cargar entidades y realizar acciones sobre ellas.
To indicate a successful action, use elgg_ok_response()
. This function accepts data that you want to make available
to the client for XHR calls (this data will be ignored for non-XHR calls)
$user = get_entity($guid);
// do something
$action_data = [
'entity' => $user,
'stats' => [
'friends' => $user->getFriends(['count' => true]);
],
];
return elgg_ok_response($action_data, 'Action was successful', 'url/to/forward/to');
To indicate an error, use elgg_error_response()
$user = elgg_get_logged_in_user_entity();
if (!$user) {
// show an error and forward the user to the referring page
// send 404 error code on AJAX calls
return elgg_error_response('User not found', REFERRER, ELGG_HTTP_NOT_FOUND);
}
if (!$user->canEdit()) {
// show an error and forward to user's profile
// send 403 error code on AJAX calls
return elgg_error_response('You are not allowed to perform this action', $user->getURL(), ELGG_HTTP_FORBIDDEN);
}
Personalizar acciones¶
Antes de ejecutar cualquier acción, Elgg desencadena un gancho:
$result = elgg_trigger_plugin_hook('action', $action, null, true);
Donde $action
es la acción a la que se llama. Si el gancho devuelve false
, la acción no se llega a ejecutar.
Ejemplo: CAPTCHA¶
El módulo CAPTCHA usa lo siguiente para interceptar las acciones register` y user/requestnewpassword
y las redirige a una función que comprueba el código del CAPTCHA. Si el código es correcto, la comprobación devuelve true
, mientras que si no lo es devuelve false
, lo que evita que se ejecute la acción asociada.
Esto se hace como se detalla a continuación:
elgg_register_plugin_hook_handler("action", "register", "captcha_verify_action_hook");
elgg_register_plugin_hook_handler("action", "user/requestnewpassword", "captcha_verify_action_hook");
...
function captcha_verify_action_hook($hook, $entity_type, $returnvalue, $params) {
$token = get_input('captcha_token');
$input = get_input('captcha_input');
if (($token) && (captcha_verify_captcha($input, $token))) {
return true;
}
register_error(elgg_echo('captcha:captchafail'));
return false;
}
Esto permite a un complemento extender una acción existente sin necesidad de substituir la acción por completo. En el caso de complemento CAPTCHA, esto le permite al complemento ofrecer la funcionalidad de CAPTCHA sin necesidad de reescribir toda la acción y actualizar su definición cada vez que ésta cambie en Elgg.
Actions available in core¶
entity/delete
¶
If your plugin does not implement any custom logic when deleting an entity, you can use bundled delete action
$guid = 123;
// You can provide optional forward path as a URL query parameter
$forward_url = 'path/to/forward/to';
echo elgg_view('output/url', array(
'text' => elgg_echo('delete'),
'href' => "action/entity/delete?guid=$guid&forward_url=$forward_url",
'confirm' => true,
));
You can customize the success message keys for your entity type and subtype, using "entity:delete:$type:$subtype:success"
and "entity:delete:$type:success"
keys.
// to add a custom message when a blog post or file is deleted
// add the translations keys in your language files
return array(
'entity:delete:object:blog:success' => 'Blog post has been deleted,
'entity:delete:object:file:success' => 'File titled %s has been deleted',
);
Forms¶
Para mostrar un formulario, utilice elgg_view_form()
de la siguiente manera:
echo elgg_view_form('example');
Doing this generates something like the following markup:
<form action="http://localhost/elgg/action/example">
<fieldset>
<input type="hidden" name="__elgg_ts" value="1234567890" />
<input type="hidden" name="__elgg_token" value="3874acfc283d90e34" />
</fieldset>
</form>
Elgg hace algunas cosas de manera automática por usted cuando genera formularios de esta manera:
- It sets the action to the appropriate URL based on the name of the action you pass to it
- Añade algunos códigos aleatorios (
__elgg_ts
y__elgg_token
) para evitar falsificaciones de peticiones entre sitios distintos, ayudando así a mantener la seguridad de las acciones.- Busca de manera automática el cuerpo del formulario en la vista
forms/example
.
Sitúe el contenido del formulario en la vista forms/example
del complemento:
// /mod/example/views/default/forms/example.php
echo elgg_view('input/text', array('name' => 'example'));
// defer form footer rendering
// this will allow other plugins to extend forms/example view
elgg_set_form_footer(elgg_view('input/submit'));
Ahora, cuando ejecute elgg_view_form('example')
, Elgg producirá lo siguiente:
<form action="http://localhost/elgg/action/example">
<fieldset>
<input type="hidden" name="__elgg_ts" value="...">
<input type="hidden" name="__elgg_token" value="...">
<input type="text" class="elgg-input-text" name="example">
<div class="elgg-foot elgg-form-footer">
<input type="submit" class="elgg-button elgg-button-submit" value="Submit">
</div>
</fieldset>
</form>
Inputs¶
To render a form input, use one of the bundled input views, which cover all standard HTML input elements. See individual view files for a list of accepted parameters.
echo elgg_view('input/select', array(
'required' => true,
'name' => 'status',
'options_values' => array(
'draft' => elgg_echo('status:draft'),
'published' => elgg_echo('status:published'),
),
// most input views will render additional parameters passed to the view
// as tag attributes
'data-rel' => 'blog',
));
The above example will render a dropdown select input:
<select required="required" name="status" data-rel="blog" class="elgg-input-dropdown">
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
To ensure consistency in field markup, use elgg_view_field()
, which accepts
all the parameters of the input being rendered, as well as #label
and #help
parameters (both of which are optional and accept HTML or text).
echo elgg_view_field(array(
'#type' => 'select',
'#label' => elgg_echo('blog:status:label'),
'#help' => elgg_view_icon('help') . elgg_echo('blog:status:help'),
'required' => true,
'name' => 'status',
'options_values' => array(
'draft' => elgg_echo('status:draft'),
'published' => elgg_echo('status:published'),
),
'data-rel' => 'blog',
));
The above will generate the following markup:
<div class="elgg-field elgg-field-required">
<label for="elgg-field-1" class="elgg-field-label">Blog status<span title="Required" class="elgg-required-indicator">*</span></label>
<select required="required" name="status" data-rel="blog" id="elgg-field-1" class="elgg-input-dropdown">
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
<div class="elgg-field-help elgg-text-help">
<span class="elgg-icon-help elgg-icon"></span>This indicates whether or not the blog is visible in the feed
</div>
</div>
Input types¶
A list of bundled input types/views:
input/text
- renders a text input<input type="text">
input/plaintext
- renders a textarea<textarea></textarea>
input/longtext
- renders a WYSIWYG text inputinput/url
- renders a url input<input type="url">
input/email
- renders an email input<input type="email">
input/checkbox
- renders a single checkbox<input type="checkbox">
input/checkboxes
- renders a set of checkboxes with the same nameinput/radio
- renders one or more radio buttons<input type="radio">
input/submit
- renders a submit button<input type="submit">
input/button
- renders a button<button></button>
input/file
- renders a file input<input type="file">
input/select
- renders a select input<select></select>
input/hidden
- renders a hidden input<input type="hidden">
input/password
- renders a password input<input type="password">
input/number
- renders a number input<input type="number">
input/date
- renders a jQuery datepickerinput/access
- renders an Elgg access level selectinput/tags
- renders an Elgg tags inputinput/autocomplete
- renders an Elgg entity autocompleteinput/captcha
- placeholder view for plugins to extendinput/friendspicker
- renders an Elgg friend pickerinput/userpicker
- renders an Elgg user autocompleteinput/location
renders an Elgg location input
Ficheros e imágenes¶
Use la vista «input/file» en la vista de contenido del formulario.
// /mod/example/views/default/forms/example.php
echo elgg_view(‘input/file’, array(‘name’ => ‘icon’));
Elija « multipart/form-data» como el «enctype» del formulario:
echo elgg_view_form(‘example’, array(
‘enctype’ => ‘multipart/form-data’
));
En el fichero de acciones, use la variable global $_FILES
para acceder al fichero enviado:
$icon = $_FILES[‘icon’]
Formularios persistentes¶
Los formularios persistentes son formularios que mantienen los datos introducidos por el usuario si algo evita que se puedan guardar los datos. Son «persistentes» porque los datos del usuario «persisten» en el formulario una vez enviado, a pesar de que dichos datos no han sido guardados en la base de datos. Esto mejora de manera drástica la experiencia de usuario minimizando la pérdida de datos. Elgg 1.8 incluye funciones de asistencia que le permiten convertir en persistente cualquier formulario.
Funciones de asistencia¶
Los formularios persistentes se añadieron a Elgg 1.8 mediante las siguientes funciones:
elgg_make_sticky_form($name):
Le indica al motor de Elgg que todos los campos de entrada del formulario deben ser persistentes.
elgg_clear_sticky_form($name):
Le indica al motor de Elgg que debe descartar todos los campos de entrada persistentes del formulario.
elgg_is_sticky_form($name):
Comprueba si $name
es un formulario persistente válido.
elgg_get_sticky_values($name):
Devuelve todos los valores persistentes almacenados para $name
por elgg_make_sticky_form()
.
Resumen¶
El flujo básico de uso de formularios persistentes consiste en: (1) Llamar a elgg_make_sticky_form($name)
al principio de las acciones para formularios que desee hacer persistentes, (2) usar elgg_is_sticky_form($name)
y elgg_get_sticky_values($name)
para obtener los valores persistidos a la hora de generar la vista del formulario y (3) llamar a elgg_clear_sticky_form($name)
una vez la acción se completase correctamente o después de que los datos se cargasen mediante elgg_get_sticky_values($name)
.
Ejemplo: Registro de una cuenta de usuario¶
Los formularios persistentes simples requieren un poco de lógica para determinar los campos de entrada del formulario. La lógica se coloca en la parte superior del cuerpo de la vista del propio formulario.
La vista del formulario de registro establece en primer lugar los valores predeterminados de los campos de entrada, y a continuación comprueba si entre ellos hay campos con valores persistidos. De haber campos con valores persistidos, el formulario carga dichos valores antes de vaciar el formulario persistente:
// views/default/forms/register.php
$password = $password2 = '';
$username = get_input('u');
$email = get_input('e');
$name = get_input('n');
if (elgg_is_sticky_form('register')) {
extract(elgg_get_sticky_values('register'));
elgg_clear_sticky_form('register');
}
La acción de registro crea el formulario persistente y lo vacía una vez se completa la acción:
// actions/register.php
elgg_make_sticky_form('register');
...
$guid = register_user($username, $password, $name, $email, false, $friend_guid, $invitecode);
if ($guid) {
elgg_clear_sticky_form('register');
....
}
Ejemplo: Marcadores¶
La acción y formulario de guardado incluidos en el complemento «Marcadores» son un ejemplo de un formulario persistente complejo.
La vista de formulario para la acción de guardar un marcador usa elgg_extract()
para obtener valores del vector $vars
:
// mod/bookmarks/views/default/forms/bookmarks/save.php
$title = elgg_extract('title', $vars, '');
$desc = elgg_extract('description', $vars, '');
$address = elgg_extract('address', $vars, '');
$tags = elgg_extract('tags', $vars, '');
$access_id = elgg_extract('access_id', $vars, ACCESS_DEFAULT);
$container_guid = elgg_extract('container_guid', $vars);
$guid = elgg_extract('guid', $vars, null);
$shares = elgg_extract('shares', $vars, array());
Los scripts del gestor de páginas prepara las variables del formulario y llama a elgg_view_form()
pasándole los valores correctos:
// mod/bookmarks/pages/add.php
$vars = bookmarks_prepare_form_vars();
$content = elgg_view_form('bookmarks/save', array(), $vars);
De manera semejante, mod/bookmarks/pages/edit.php
usa la misma función, pero le pasa la entidad que se está editando como argumento:
$bookmark_guid = get_input('guid');
$bookmark = get_entity($bookmark_guid);
...
$vars = bookmarks_prepare_form_vars($bookmark);
$content = elgg_view_form('bookmarks/save', array(), $vars);
El fichero de la biblioteca define bookmarks_prepare_form_vars()
. Esta función acepta una instancia de ElggEntity
como argumento y hace 3 cosas:
- Define los nombres de los campos de entrada y sus valores predeterminados.
- Extrae los valores de un objeto de marcador si lo recibe.
- Extrae los valores de un formulario persistente si éste existe.
Por hacer: incluir directamente desde «lib/bookmarks.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;
}
La acción de guardar comprueba los campos de entrada, y luego vacía el formulario persistente cuando se completa correctamente:
// mod/bookmarks/actions/bookmarks/save.php
elgg_make_sticky_form('bookmarks');
...
if ($bookmark->save()) {
elgg_clear_sticky_form('bookmarks');
}
AJAX¶
See the Ajax guide for instructions on calling actions from JavaScript.
Seguridad¶
For enhanced security, all actions require an CSRF token. Calls to action URLs that do not include security tokens will be ignored and a warning will be generated.
Algunas vistas y funciones generan códigos aleatorios de seguridad de forma automática:
elgg_view('output/url', array('is_action' => TRUE));
elgg_view('input/securitytoken');
$url = elgg_add_action_tokens_to_url("http://localhost/elgg/action/example");
En algunos casos excepcionales, puede que necesite generar esos códigos manualmente:
$__elgg_ts = time();
$__elgg_token = generate_action_token($__elgg_ts);
También puede acceder a los códigos de seguridad desde JavaScript:
elgg.security.token.__elgg_ts;
elgg.security.token.__elgg_token;
Éstos se actualizan de manera periódica, por lo que deberían estar siempre al día.
Security Tokens¶
On occasion we need to pass data through an untrusted party or generate an «unguessable token» based on some data. The industry-standard HMAC algorithm is the right tool for this. It allows us to verify that received data were generated by our site, and were not tampered with. Note that even strong hash functions like SHA-2 should not be used without HMAC for these tasks.
Elgg provides elgg_build_hmac()
to generate and validate HMAC message authentication codes that are unguessable
without the site’s private key.
// generate a querystring such that $a and $b can't be altered
$a = 1234;
$b = "hello";
$query = http_build_query([
'a' => $a,
'b' => $b,
'mac' => elgg_build_hmac([$a, $b])->getToken(),
]);
$url = "action/foo?$query";
// validate the querystring
$a = (int) get_input('a', '', false);
$b = (string) get_input('b', '', false);
$mac = get_input('mac', '', false);
if (elgg_build_hmac([$a, $b])->matchesToken($mac)) {
// $a and $b have not been altered
}
Note: If you use a non-string as HMAC data, you must use types consistently. Consider the following:
$mac = elgg_build_hmac([123, 456])->getToken();
// type of first array element differs
elgg_build_hmac(["123", 456])->matchesToken($mac); // false
// types identical to original
elgg_build_hmac([123, 456])->matchesToken($mac); // true
Signed URLs¶
Signed URLs offer a limited level of security for situations where action tokens are not suitable, for example when sending a confirmation link via email. URL signatures verify that the URL has been generated by your Elgg installation (using site secret) and that the URL query elements were not tampered with.
URLs a signed with an unguessable SHA-256 HMAC key. See Security Tokens for more details.
$url = elgg_http_add_url_query_element(elgg_normalize_url('confirm'), [
'user_guid' => $user_guid,
]);
$url = elgg_http_get_signed_url($url);
notify_user($user_guid, $site->guid, 'Confirm', "Please confirm by clicking this link: $url");
Advertencia
Signed URLs do not offer CSRF protection and should not be used instead of action tokens.