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:

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:

  1. It sets the action to the appropriate URL based on the name of the action you pass to it
  2. 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.
  3. 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 input
  • input/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 name
  • input/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 datepicker
  • input/access - renders an Elgg access level select
  • input/tags - renders an Elgg tags input
  • input/autocomplete - renders an Elgg entity autocomplete
  • input/captcha - placeholder view for plugins to extend
  • input/friendspicker - renders an Elgg friend picker
  • input/userpicker - renders an Elgg user autocomplete
  • input/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:

  1. Define los nombres de los campos de entrada y sus valores predeterminados.
  2. Extrae los valores de un objeto de marcador si lo recibe.
  3. 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.