actions.rst 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. Forms + Actions
  2. ###############
  3. Create, update, or delete content.
  4. Elgg forms submit to actions. Actions define the behavior for form submission.
  5. This guide assumes basic familiarity with:
  6. - :doc:`/admin/plugins`
  7. - :doc:`views`
  8. - :doc:`i18n`
  9. .. contents:: Contents
  10. :local:
  11. :depth: 2
  12. Registering actions
  13. ===================
  14. Actions must be registered before use. Use ``elgg_register_action`` for this:
  15. .. code:: php
  16. elgg_register_action("example", __DIR__ . "/actions/example.php");
  17. The ``mod/example/actions/example.php`` script will now be run whenever a form is submitted to ``http://localhost/elgg/action/example``.
  18. .. warning:: 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.
  19. Permissions
  20. -----------
  21. By default, actions are only available to logged in users.
  22. To make an action available to logged out users, pass ``"public"`` as the third parameter:
  23. .. code:: php
  24. elgg_register_action("example", $filepath, "public");
  25. To restrict an action to only administrators, pass ``"admin"`` for the last parameter:
  26. .. code:: php
  27. elgg_register_action("example", $filepath, "admin");
  28. Writing action files
  29. --------------------
  30. Use the ``get_input`` function to get access to request parameters:
  31. .. code:: php
  32. $field = get_input('input_field_name', 'default_value');
  33. You can then use the :doc:`database` api to load entities and perform actions on them accordingly.
  34. To redirect the page once you've completed your actions, use the ``forward`` function:
  35. .. code:: php
  36. forward('url/to/forward/to');
  37. For example, to forward to the user's profile:
  38. .. code:: php
  39. $user = elgg_get_logged_in_user_entity();
  40. forward($user->getURL());
  41. URLs can be relative to the Elgg root:
  42. .. code:: php
  43. $user = elgg_get_logged_in_user_entity();
  44. forward("/example/$user->username");
  45. Redirect to the referring page by using the ``REFERRER`` constant:
  46. .. code:: php
  47. forward(REFERRER);
  48. forward(REFERER); // equivalent
  49. Give feedback to the user about the status of the action by using
  50. ``system_message`` for positive feedback or ``register_error`` for warnings and errors:
  51. .. code:: php
  52. if ($success) {
  53. system_message(elgg_echo(‘actions:example:success’));
  54. } else {
  55. register_error(elgg_echo(‘actions:example:error’));
  56. }
  57. Customizing actions
  58. -------------------
  59. Before executing any action, Elgg triggers a hook:
  60. .. code:: php
  61. $result = elgg_trigger_plugin_hook('action', $action, null, true);
  62. Where ``$action`` is the action being called. If the hook returns ``false`` then the action will not be executed.
  63. Example: Captcha
  64. ^^^^^^^^^^^^^^^^
  65. The captcha module uses this to intercept the ``register`` and ``user/requestnewpassword`` actions and redirect them to a function which checks the captcha code. This check returns ``true`` if valid or ``false`` if not (which prevents the associated action from executing).
  66. This is done as follows:
  67. .. code:: php
  68. elgg_register_plugin_hook_handler("action", "register", "captcha_verify_action_hook");
  69. elgg_register_plugin_hook_handler("action", "user/requestnewpassword", "captcha_verify_action_hook");
  70. ...
  71. function captcha_verify_action_hook($hook, $entity_type, $returnvalue, $params) {
  72. $token = get_input('captcha_token');
  73. $input = get_input('captcha_input');
  74. if (($token) && (captcha_verify_captcha($input, $token))) {
  75. return true;
  76. }
  77. register_error(elgg_echo('captcha:captchafail'));
  78. return false;
  79. }
  80. This lets a plugin extend an existing action without the need to replace the whole action. In the case of the captcha plugin it allows the plugin to provide captcha support in a very loosely coupled way.
  81. To output a form, use the elgg_view_form function like so:
  82. .. code:: php
  83. echo elgg_view_form('example');
  84. Doing this generates something like the following markup:
  85. .. code:: html
  86. <form action="http://localhost/elgg/action/example">
  87. <fieldset>
  88. <input type="hidden" name="__elgg_ts" value="1234567890" />
  89. <input type="hidden" name="__elgg_token" value="3874acfc283d90e34" />
  90. </fieldset>
  91. </form>
  92. Elgg does some things automatically for you when you generate forms this way:
  93. 1. It sets the action to the appropriate URL based on the name of the action you pass to it
  94. 2. It adds some anti-csrf tokens (``__elgg_ts`` and ``__elgg_token``) to help keep your actions secure
  95. 3. It automatically looks for the body of the form in the ``forms/example`` view.
  96. Put the content of your form in your plugin’s ``forms/example`` view:
  97. .. code:: php
  98. // /mod/example/views/default/forms/example.php
  99. echo elgg_view('input/text', array('name' => 'example'));
  100. echo elgg_view('input/submit');
  101. Now when you call ``elgg_view_form('example')``, Elgg will produce:
  102. .. code:: html
  103. <form action="http://localhost/elgg/action/example">
  104. <fieldset>
  105. <input type="hidden" name="__elgg_ts" value="...">
  106. <input type="hidden" name="__elgg_token" value="...">
  107. <input type="text" class="elgg-input-text" name="example">
  108. <input type="submit" class="elgg-button elgg-button-submit" value="Submit">
  109. </fieldset>
  110. </form>
  111. Files and images
  112. ================
  113. Use the input/file view in your form’s content view.
  114. .. code:: php
  115. // /mod/example/views/default/forms/example.php
  116. echo elgg_view(‘input/file’, array(‘name’ => ‘icon’));
  117. Set the enctype of the form to multipart/form-data:
  118. .. code:: php
  119. echo elgg_view_form(‘example’, array(
  120. ‘enctype’ => ‘multipart/form-data’
  121. ));
  122. In your action file, use the ``$_FILES`` global to access the uploaded file:
  123. .. code:: php
  124. $icon = $_FILES[‘icon’]
  125. Sticky forms
  126. ============
  127. Sticky forms are forms that retain user input if saving fails. They are "sticky" because the user's data "sticks" in the form after submitting, though it was never saved to the database. This greatly improves the user experience by minimizing data loss. Elgg 1.8 includes helper functions so you can make any form sticky.
  128. Helper functions
  129. ----------------
  130. Sticky forms are implemented in Elgg 1.8 by the following functions:
  131. ``elgg_make_sticky_form($name)``
  132. Tells the engine to make all input on a form sticky.
  133. ``elgg_clear_sticky_form($name)``
  134. Tells the engine to discard all sticky input on a form.
  135. ``elgg_is_sticky_form($name)``
  136. Checks if $name is a valid sticky form.
  137. ``elgg_get_sticky_values($name)``
  138. Returns all sticky values saved for $name by elgg_make_sticky_form().
  139. Overview
  140. --------
  141. The basic flow of using sticky forms is:
  142. Call ``elgg_make_sticky_form($name)`` at the top of actions for forms you want to be sticky.
  143. Use ``elgg_is_sticky_form($name)`` and ``elgg_get_sticky_values($name)`` to get sticky values when rendering a form view.
  144. Call ``elgg_clear_sticky_form($name)`` after the action has completed successfully or after data has been loaded by ``elgg_get_sticky_values($name)``.
  145. Example: User registration
  146. --------------------------
  147. Simple sticky forms require little logic to determine the input values for the form. This logic is placed at the top of the form body view itself.
  148. The registration form view first sets default values for inputs, then checks if there are sticky values. If so, it loads the sticky values before clearing the sticky form:
  149. .. code:: php
  150. // views/default/forms/register.php
  151. $password = $password2 = '';
  152. $username = get_input('u');
  153. $email = get_input('e');
  154. $name = get_input('n');
  155. if (elgg_is_sticky_form('register')) {
  156. extract(elgg_get_sticky_values('register'));
  157. elgg_clear_sticky_form('register');
  158. }
  159. The registration action sets creates the sticky form and clears it once the action is completed:
  160. .. code:: php
  161. // actions/register.php
  162. elgg_make_sticky_form('register');
  163. ...
  164. $guid = register_user($username, $password, $name, $email, false, $friend_guid, $invitecode);
  165. if ($guid) {
  166. elgg_clear_sticky_form('register');
  167. ....
  168. }
  169. Example: Bookmarks
  170. ------------------
  171. The bundled plugin Bookmarks' save form and action is an example of a complex sticky form.
  172. The form view for the save bookmark action uses ``elgg_extract()`` to pull values from the ``$vars`` array:
  173. .. code:: php
  174. // mod/bookmarks/views/default/forms/bookmarks/save.php
  175. $title = elgg_extract('title', $vars, '');
  176. $desc = elgg_extract('description', $vars, '');
  177. $address = elgg_extract('address', $vars, '');
  178. $tags = elgg_extract('tags', $vars, '');
  179. $access_id = elgg_extract('access_id', $vars, ACCESS_DEFAULT);
  180. $container_guid = elgg_extract('container_guid', $vars);
  181. $guid = elgg_extract('guid', $vars, null);
  182. $shares = elgg_extract('shares', $vars, array());
  183. The page handler scripts prepares the form variables and calls ``elgg_view_form()`` passing the correct values:
  184. .. code:: php
  185. // mod/bookmarks/pages/add.php
  186. $vars = bookmarks_prepare_form_vars();
  187. $content = elgg_view_form('bookmarks/save', array(), $vars);
  188. Similarly, ``mod/bookmarks/pages/edit.php`` uses the same function, but passes the entity that is being edited as an argument:
  189. .. code:: php
  190. $bookmark_guid = get_input('guid');
  191. $bookmark = get_entity($bookmark_guid);
  192. ...
  193. $vars = bookmarks_prepare_form_vars($bookmark);
  194. $content = elgg_view_form('bookmarks/save', array(), $vars);
  195. The library file defines ``bookmarks_prepare_form_vars()``. This function accepts an ``ElggEntity`` as an argument and does 3 things:
  196. 1. Defines the input names and default values for form inputs.
  197. 2. Extracts the values from a bookmark object if it's passed.
  198. 3. Extracts the values from a sticky form if it exists.
  199. TODO: Include directly from lib/bookmarks.php
  200. .. code:: php
  201. // mod/bookmarks/lib/bookmarks.php
  202. function bookmarks_prepare_form_vars($bookmark = null) {
  203. // input names => defaults
  204. $values = array(
  205. 'title' => get_input('title', ''), // bookmarklet support
  206. 'address' => get_input('address', ''),
  207. 'description' => '',
  208. 'access_id' => ACCESS_DEFAULT,
  209. 'tags' => '',
  210. 'shares' => array(),
  211. 'container_guid' => elgg_get_page_owner_guid(),
  212. 'guid' => null,
  213. 'entity' => $bookmark,
  214. );
  215. if ($bookmark) {
  216. foreach (array_keys($values) as $field) {
  217. if (isset($bookmark->$field)) {
  218. $values[$field] = $bookmark->$field;
  219. }
  220. }
  221. }
  222. if (elgg_is_sticky_form('bookmarks')) {
  223. $sticky_values = elgg_get_sticky_values('bookmarks');
  224. foreach ($sticky_values as $key => $value) {
  225. $values[$key] = $value;
  226. }
  227. }
  228. elgg_clear_sticky_form('bookmarks');
  229. return $values;
  230. }
  231. The save action checks the input, then clears the sticky form upon success:
  232. .. code:: php
  233. // mod/bookmarks/actions/bookmarks/save.php
  234. elgg_make_sticky_form('bookmarks');
  235. ...
  236. if ($bookmark->save()) {
  237. elgg_clear_sticky_form('bookmarks');
  238. }
  239. Ajax
  240. ====
  241. See the :doc:`Ajax guide</guides/ajax>` for instructions on calling actions from JavaScript.
  242. Security
  243. ========
  244. 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.
  245. A few views and functions automatically generate security tokens:
  246. .. code:: php
  247. elgg_view('output/url', array('is_action' => TRUE));
  248. elgg_view('input/securitytoken');
  249. $url = elgg_add_action_tokens_to_url("http://localhost/elgg/action/example");
  250. In rare cases, you may need to generate tokens manually:
  251. .. code:: php
  252. $__elgg_ts = time();
  253. $__elgg_token = generate_action_token($__elgg_ts);
  254. You can also access the tokens from javascript:
  255. .. code:: js
  256. elgg.security.token.__elgg_ts;
  257. elgg.security.token.__elgg_token;
  258. These are refreshed periodically so should always be up-to-date.
  259. Security Tokens
  260. ===============
  261. On occasion we need to pass data through an untrusted party or generate an "unguessable token" based on some data.
  262. The industry-standard `HMAC <http://security.stackexchange.com/a/20301/4982>`_ algorithm is the right tool for this.
  263. It allows us to verify that received data were generated by our site, and were not tampered with. Note that even
  264. strong hash functions like SHA-2 should *not* be used without HMAC for these tasks.
  265. Elgg provides ``elgg_build_hmac()`` to generate and validate HMAC message authentication codes that are unguessable
  266. without the site's private key.
  267. .. code:: php
  268. // generate a querystring such that $a and $b can't be altered
  269. $a = 1234;
  270. $b = "hello";
  271. $query = http_build_query([
  272. 'a' => $a,
  273. 'b' => $b,
  274. 'mac' => elgg_build_hmac([$a, $b])->getToken(),
  275. ]);
  276. $url = "action/foo?$query";
  277. // validate the querystring
  278. $a = (int) get_input('a', '', false);
  279. $b = (string) get_input('b', '', false);
  280. $mac = get_input('mac', '', false);
  281. if (elgg_build_hmac([$a, $b])->matchesToken($mac)) {
  282. // $a and $b have not been altered
  283. }
  284. Note: If you use a non-string as HMAC data, you must use types consistently. Consider the following:
  285. .. code:: php
  286. $mac = elgg_build_hmac([123, 456])->getToken();
  287. // type of first array element differs
  288. elgg_build_hmac(["123", 456])->matchesToken($mac); // false
  289. // types identical to original
  290. elgg_build_hmac([123, 456])->matchesToken($mac); // true