Projet

Général

Profil

Paste
Télécharger (45,8 ko) Statistiques
| Branche: | Révision:

root / htmltest / includes / ajax.inc @ a5572547

1 85ad3d82 Assos Assos
<?php
2
3
/**
4
 * @file
5
 * Functions for use with Drupal's Ajax framework.
6
 */
7
8
/**
9
 * @defgroup ajax Ajax framework
10
 * @{
11
 * Functions for Drupal's Ajax framework.
12
 *
13
 * Drupal's Ajax framework is used to dynamically update parts of a page's HTML
14
 * based on data from the server. Upon a specified event, such as a button
15
 * click, a callback function is triggered which performs server-side logic and
16
 * may return updated markup, which is then replaced on-the-fly with no page
17
 * refresh necessary.
18
 *
19
 * This framework creates a PHP macro language that allows the server to
20
 * instruct JavaScript to perform actions on the client browser. When using
21
 * forms, it can be used with the #ajax property.
22
 * The #ajax property can be used to bind events to the Ajax framework. By
23
 * default, #ajax uses 'system/ajax' as its path for submission and thus calls
24
 * ajax_form_callback() and a defined #ajax['callback'] function.
25
 * However, you may optionally specify a different path to request or a
26
 * different callback function to invoke, which can return updated HTML or can
27
 * also return a richer set of
28
 * @link ajax_commands Ajax framework commands @endlink.
29
 *
30
 * Standard form handling is as follows:
31
 *   - A form element has a #ajax property that includes #ajax['callback'] and
32
 *     omits #ajax['path']. See below about using #ajax['path'] to implement
33
 *     advanced use-cases that require something other than standard form
34
 *     handling.
35
 *   - On the specified element, Ajax processing is triggered by a change to
36
 *     that element.
37
 *   - The browser submits an HTTP POST request to the 'system/ajax' Drupal
38
 *     path.
39
 *   - The menu page callback for 'system/ajax', ajax_form_callback(), calls
40
 *     drupal_process_form() to process the form submission and rebuild the
41
 *     form if necessary. The form is processed in much the same way as if it
42
 *     were submitted without Ajax, with the same #process functions and
43
 *     validation and submission handlers called in either case, making it easy
44
 *     to create Ajax-enabled forms that degrade gracefully when JavaScript is
45
 *     disabled.
46
 *   - After form processing is complete, ajax_form_callback() calls the
47
 *     function named by #ajax['callback'], which returns the form element that
48
 *     has been updated and needs to be returned to the browser, or
49
 *     alternatively, an array of custom Ajax commands.
50
 *   - The page delivery callback for 'system/ajax', ajax_deliver(), renders the
51
 *     element returned by #ajax['callback'], and returns the JSON string
52
 *     created by ajax_render() to the browser.
53
 *   - The browser unserializes the returned JSON string into an array of
54
 *     command objects and executes each command, resulting in the old page
55
 *     content within and including the HTML element specified by
56
 *     #ajax['wrapper'] being replaced by the new content returned by
57
 *     #ajax['callback'], using a JavaScript animation effect specified by
58
 *     #ajax['effect'].
59
 *
60
 * A simple example of basic Ajax use from the
61
 * @link http://drupal.org/project/examples Examples module @endlink follows:
62
 * @code
63
 * function main_page() {
64
 *   return drupal_get_form('ajax_example_simplest');
65
 * }
66
 *
67
 * function ajax_example_simplest($form, &$form_state) {
68
 *   $form = array();
69
 *   $form['changethis'] = array(
70
 *     '#type' => 'select',
71
 *     '#options' => array(
72
 *       'one' => 'one',
73
 *       'two' => 'two',
74
 *       'three' => 'three',
75
 *     ),
76
 *     '#ajax' => array(
77
 *       'callback' => 'ajax_example_simplest_callback',
78
 *       'wrapper' => 'replace_textfield_div',
79
 *      ),
80
 *   );
81
82
 *   // This entire form element will be replaced with an updated value.
83
 *   $form['replace_textfield'] = array(
84
 *     '#type' => 'textfield',
85
 *     '#title' => t("The default value will be changed"),
86
 *     '#description' => t("Say something about why you chose") . "'" .
87
 *       (!empty($form_state['values']['changethis'])
88
 *       ? $form_state['values']['changethis'] : t("Not changed yet")) . "'",
89
 *     '#prefix' => '<div id="replace_textfield_div">',
90
 *     '#suffix' => '</div>',
91
 *   );
92
 *   return $form;
93
 * }
94
 *
95
 * function ajax_example_simplest_callback($form, $form_state) {
96
 *   // The form has already been submitted and updated. We can return the replaced
97
 *   // item as it is.
98
 *   return $form['replace_textfield'];
99
 * }
100
 * @endcode
101
 *
102
 * In the above example, the 'changethis' element is Ajax-enabled. The default
103
 * #ajax['event'] is 'change', so when the 'changethis' element changes,
104
 * an Ajax call is made. The form is submitted and reprocessed, and then the
105
 * callback is called. In this case, the form has been automatically
106
 * built changing $form['replace_textfield']['#description'], so the callback
107
 * just returns that part of the form.
108
 *
109
 * To implement Ajax handling in a form, add '#ajax' to the form
110
 * definition of a field. That field will trigger an Ajax event when it is
111
 * clicked (or changed, depending on the kind of field). #ajax supports
112
 * the following parameters (either 'path' or 'callback' is required at least):
113
 * - #ajax['callback']: The callback to invoke to handle the server side of the
114
 *   Ajax event, which will receive a $form and $form_state as arguments, and
115
 *   returns a renderable array (most often a form or form fragment), an HTML
116
 *   string, or an array of Ajax commands. If returning a renderable array or
117
 *   a string, the value will replace the original element named in
118
 *   #ajax['wrapper'], and
119
 *   theme_status_messages()
120
 *   will be prepended to that
121
 *   element. (If the status messages are not wanted, return an array
122
 *   of Ajax commands instead.)
123
 *   #ajax['wrapper']. If an array of Ajax commands is returned, it will be
124
 *   executed by the calling code.
125
 * - #ajax['path']: The menu path to use for the request. This is often omitted
126
 *   and the default is used. This path should map
127
 *   to a menu page callback that returns data using ajax_render(). Defaults to
128
 *   'system/ajax', which invokes ajax_form_callback(), eventually calling
129
 *   the function named in #ajax['callback']. If you use a custom
130
 *   path, you must set up the menu entry and handle the entire callback in your
131
 *   own code.
132
 * - #ajax['wrapper']: The CSS ID of the area to be replaced by the content
133
 *   returned by the #ajax['callback'] function. The content returned from
134
 *   the callback will replace the entire element named by #ajax['wrapper'].
135
 *   The wrapper is usually created using #prefix and #suffix properties in the
136
 *   form. Note that this is the wrapper ID, not a CSS selector. So to replace
137
 *   the element referred to by the CSS selector #some-selector on the page,
138
 *   use #ajax['wrapper'] = 'some-selector', not '#some-selector'.
139
 * - #ajax['effect']: The jQuery effect to use when placing the new HTML.
140
 *   Defaults to no effect. Valid options are 'none', 'slide', or 'fade'.
141
 * - #ajax['speed']: The effect speed to use. Defaults to 'slow'. May be
142
 *   'slow', 'fast' or a number in milliseconds which represents the length
143
 *   of time the effect should run.
144
 * - #ajax['event']: The JavaScript event to respond to. This is normally
145
 *   selected automatically for the type of form widget being used, and
146
 *   is only needed if you need to override the default behavior.
147
 * - #ajax['prevent']: A JavaScript event to prevent when 'event' is triggered.
148
 *   Defaults to 'click' for #ajax on #type 'submit', 'button', and
149
 *   'image_button'. Multiple events may be specified separated by spaces.
150
 *   For example, when binding #ajax behaviors to form buttons, pressing the
151
 *   ENTER key within a textfield triggers the 'click' event of the form's first
152
 *   submit button. Triggering Ajax in this situation leads to problems, like
153
 *   breaking autocomplete textfields. Because of that, Ajax behaviors are bound
154
 *   to the 'mousedown' event on form buttons by default. However, binding to
155
 *   'mousedown' rather than 'click' means that it is possible to trigger a
156
 *   click by pressing the mouse, holding the mouse button down until the Ajax
157
 *   request is complete and the button is re-enabled, and then releasing the
158
 *   mouse button. For this case, 'prevent' can be set to 'click', so an
159
 *   additional event handler is bound to prevent such a click from triggering a
160
 *   non-Ajax form submission. This also prevents a textfield's ENTER press
161
 *   triggering a button's non-Ajax form submission behavior.
162
 * - #ajax['method']: The jQuery method to use to place the new HTML.
163
 *   Defaults to 'replaceWith'. May be: 'replaceWith', 'append', 'prepend',
164
 *   'before', 'after', or 'html'. See the
165
 *   @link http://api.jquery.com/category/manipulation/ jQuery manipulators documentation @endlink
166
 *   for more information on these methods.
167
 * - #ajax['progress']: Choose either a throbber or progress bar that is
168
 *   displayed while awaiting a response from the callback, and add an optional
169
 *   message. Possible keys: 'type', 'message', 'url', 'interval'.
170
 *   More information is available in the
171
 *   @link forms_api_reference.html Form API Reference @endlink
172
 *
173
 * In addition to using Form API for doing in-form modification, Ajax may be
174
 * enabled by adding classes to buttons and links. By adding the 'use-ajax'
175
 * class to a link, the link will be loaded via an Ajax call. When using this
176
 * method, the href of the link can contain '/nojs/' as part of the path. When
177
 * the Ajax framework makes the request, it will convert this to '/ajax/'.
178
 * The server is then able to easily tell if this request was made through an
179
 * actual Ajax request or in a degraded state, and respond appropriately.
180
 *
181
 * Similarly, submit buttons can be given the class 'use-ajax-submit'. The
182
 * form will then be submitted via Ajax to the path specified in the #action.
183
 * Like the ajax-submit class above, this path will have '/nojs/' replaced with
184
 * '/ajax/' so that the submit handler can tell if the form was submitted
185
 * in a degraded state or not.
186
 *
187
 * When responding to Ajax requests, the server should do what it needs to do
188
 * for that request, then create a commands array. This commands array will
189
 * be converted to a JSON object and returned to the client, which will then
190
 * iterate over the array and process it like a macro language.
191
 *
192
 * Each command item is an associative array which will be converted to a
193
 * command object on the JavaScript side. $command_item['command'] is the type
194
 * of command, e.g. 'alert' or 'replace', and will correspond to a method in the
195
 * Drupal.ajax[command] space. The command array may contain any other data that
196
 * the command needs to process, e.g. 'method', 'selector', 'settings', etc.
197
 *
198
 * Commands are usually created with a couple of helper functions, so they
199
 * look like this:
200
 * @code
201
 *   $commands = array();
202
 *   // Replace the content of '#object-1' on the page with 'some html here'.
203
 *   $commands[] = ajax_command_replace('#object-1', 'some html here');
204
 *   // Add a visual "changed" marker to the '#object-1' element.
205
 *   $commands[] = ajax_command_changed('#object-1');
206
 *   // Menu 'page callback' and #ajax['callback'] functions are supposed to
207
 *   // return render arrays. If returning an Ajax commands array, it must be
208
 *   // encapsulated in a render array structure.
209
 *   return array('#type' => 'ajax', '#commands' => $commands);
210
 * @endcode
211
 *
212
 * When returning an Ajax command array, it is often useful to have
213
 * status messages rendered along with other tasks in the command array.
214
 * In that case the the Ajax commands array may be constructed like this:
215
 * @code
216
 *   $commands = array();
217
 *   $commands[] = ajax_command_replace(NULL, $output);
218
 *   $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
219
 *   return array('#type' => 'ajax', '#commands' => $commands);
220
 * @endcode
221
 *
222
 * See @link ajax_commands Ajax framework commands @endlink
223
 */
224
225
/**
226
 * Renders a commands array into JSON.
227
 *
228
 * @param $commands
229
 *   A list of macro commands generated by the use of ajax_command_*()
230
 *   functions.
231
 */
232
function ajax_render($commands = array()) {
233
  // Ajax responses aren't rendered with html.tpl.php, so we have to call
234
  // drupal_get_css() and drupal_get_js() here, in order to have new files added
235
  // during this request to be loaded by the page. We only want to send back
236
  // files that the page hasn't already loaded, so we implement simple diffing
237
  // logic using array_diff_key().
238
  foreach (array('css', 'js') as $type) {
239
    // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty,
240
    // since the base page ought to have at least one JS file and one CSS file
241
    // loaded. It probably indicates an error, and rather than making the page
242
    // reload all of the files, instead we return no new files.
243
    if (empty($_POST['ajax_page_state'][$type])) {
244
      $items[$type] = array();
245
    }
246
    else {
247
      $function = 'drupal_add_' . $type;
248
      $items[$type] = $function();
249
      drupal_alter($type, $items[$type]);
250
      // @todo Inline CSS and JS items are indexed numerically. These can't be
251
      //   reliably diffed with array_diff_key(), since the number can change
252
      //   due to factors unrelated to the inline content, so for now, we strip
253
      //   the inline items from Ajax responses, and can add support for them
254
      //   when drupal_add_css() and drupal_add_js() are changed to use a hash
255
      //   of the inline content as the array key.
256
      foreach ($items[$type] as $key => $item) {
257
        if (is_numeric($key)) {
258
          unset($items[$type][$key]);
259
        }
260
      }
261
      // Ensure that the page doesn't reload what it already has.
262
      $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]);
263
    }
264
  }
265
266
  // Render the HTML to load these files, and add AJAX commands to insert this
267
  // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
268
  // data from being altered again, as we already altered it above. Settings are
269
  // handled separately, afterwards.
270
  if (isset($items['js']['settings'])) {
271
    unset($items['js']['settings']);
272
  }
273
  $styles = drupal_get_css($items['css'], TRUE);
274
  $scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
275
  $scripts_header = drupal_get_js('header', $items['js'], TRUE);
276
277
  $extra_commands = array();
278
  if (!empty($styles)) {
279
    $extra_commands[] = ajax_command_prepend('head', $styles);
280
  }
281
  if (!empty($scripts_header)) {
282
    $extra_commands[] = ajax_command_prepend('head', $scripts_header);
283
  }
284
  if (!empty($scripts_footer)) {
285
    $extra_commands[] = ajax_command_append('body', $scripts_footer);
286
  }
287
  if (!empty($extra_commands)) {
288
    $commands = array_merge($extra_commands, $commands);
289
  }
290
291
  // Now add a command to merge changes and additions to Drupal.settings.
292
  $scripts = drupal_add_js();
293
  if (!empty($scripts['settings'])) {
294
    $settings = $scripts['settings'];
295
    array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
296
  }
297
298
  // Allow modules to alter any Ajax response.
299
  drupal_alter('ajax_render', $commands);
300
301
  return drupal_json_encode($commands);
302
}
303
304
/**
305
 * Gets a form submitted via #ajax during an Ajax callback.
306
 *
307
 * This will load a form from the form cache used during Ajax operations. It
308
 * pulls the form info from $_POST.
309
 *
310
 * @return
311
 *   An array containing the $form and $form_state. Use the list() function
312
 *   to break these apart:
313
 *   @code
314
 *     list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
315
 *   @endcode
316
 */
317
function ajax_get_form() {
318
  $form_state = form_state_defaults();
319
320
  $form_build_id = $_POST['form_build_id'];
321
322
  // Get the form from the cache.
323
  $form = form_get_cache($form_build_id, $form_state);
324
  if (!$form) {
325
    // If $form cannot be loaded from the cache, the form_build_id in $_POST
326
    // must be invalid, which means that someone performed a POST request onto
327
    // system/ajax without actually viewing the concerned form in the browser.
328
    // This is likely a hacking attempt as it never happens under normal
329
    // circumstances, so we just do nothing.
330
    watchdog('ajax', 'Invalid form POST data.', array(), WATCHDOG_WARNING);
331
    drupal_exit();
332
  }
333
334
  // Since some of the submit handlers are run, redirects need to be disabled.
335
  $form_state['no_redirect'] = TRUE;
336
337
  // When a form is rebuilt after Ajax processing, its #build_id and #action
338
  // should not change.
339
  // @see drupal_rebuild_form()
340
  $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
341
  $form_state['rebuild_info']['copy']['#action'] = TRUE;
342
343
  // The form needs to be processed; prepare for that by setting a few internal
344
  // variables.
345
  $form_state['input'] = $_POST;
346
  $form_id = $form['#form_id'];
347
348
  return array($form, $form_state, $form_id, $form_build_id);
349
}
350
351
/**
352
 * Menu callback; handles Ajax requests for the #ajax Form API property.
353
 *
354
 * This rebuilds the form from cache and invokes the defined #ajax['callback']
355
 * to return an Ajax command structure for JavaScript. In case no 'callback' has
356
 * been defined, nothing will happen.
357
 *
358
 * The Form API #ajax property can be set both for buttons and other input
359
 * elements.
360
 *
361
 * This function is also the canonical example of how to implement
362
 * #ajax['path']. If processing is required that cannot be accomplished with
363
 * a callback, re-implement this function and set #ajax['path'] to the
364
 * enhanced function.
365
 *
366
 * @see system_menu()
367
 */
368
function ajax_form_callback() {
369
  list($form, $form_state) = ajax_get_form();
370
  drupal_process_form($form['#form_id'], $form, $form_state);
371
372
  // We need to return the part of the form (or some other content) that needs
373
  // to be re-rendered so the browser can update the page with changed content.
374
  // Since this is the generic menu callback used by many Ajax elements, it is
375
  // up to the #ajax['callback'] function of the element (may or may not be a
376
  // button) that triggered the Ajax request to determine what needs to be
377
  // rendered.
378
  if (!empty($form_state['triggering_element'])) {
379
    $callback = $form_state['triggering_element']['#ajax']['callback'];
380
  }
381
  if (!empty($callback) && function_exists($callback)) {
382
    return $callback($form, $form_state);
383
  }
384
}
385
386
/**
387
 * Theme callback for Ajax requests.
388
 *
389
 * Many different pages can invoke an Ajax request to system/ajax or another
390
 * generic Ajax path. It is almost always desired for an Ajax response to be
391
 * rendered using the same theme as the base page, because most themes are built
392
 * with the assumption that they control the entire page, so if the CSS for two
393
 * themes are both loaded for a given page, they may conflict with each other.
394
 * For example, Bartik is Drupal's default theme, and Seven is Drupal's default
395
 * administration theme. Depending on whether the "Use the administration theme
396
 * when editing or creating content" checkbox is checked, the node edit form may
397
 * be displayed in either theme, but the Ajax response to the Field module's
398
 * "Add another item" button should be rendered using the same theme as the rest
399
 * of the page. Therefore, system_menu() sets the 'theme callback' for
400
 * 'system/ajax' to this function, and it is recommended that modules
401
 * implementing other generic Ajax paths do the same.
402
 *
403
 * @see system_menu()
404
 * @see file_menu()
405
 */
406
function ajax_base_page_theme() {
407
  if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) {
408
    $theme = $_POST['ajax_page_state']['theme'];
409
    $token = $_POST['ajax_page_state']['theme_token'];
410
411
    // Prevent a request forgery from giving a person access to a theme they
412
    // shouldn't be otherwise allowed to see. However, since everyone is allowed
413
    // to see the default theme, token validation isn't required for that, and
414
    // bypassing it allows most use-cases to work even when accessed from the
415
    // page cache.
416
    if ($theme === variable_get('theme_default', 'bartik') || drupal_valid_token($token, $theme)) {
417
      return $theme;
418
    }
419
  }
420
}
421
422
/**
423
 * Packages and sends the result of a page callback as an Ajax response.
424
 *
425
 * This function is the equivalent of drupal_deliver_html_page(), but for Ajax
426
 * requests. Like that function, it:
427
 * - Adds needed HTTP headers.
428
 * - Prints rendered output.
429
 * - Performs end-of-request tasks.
430
 *
431
 * @param $page_callback_result
432
 *   The result of a page callback. Can be one of:
433
 *   - NULL: to indicate no content.
434
 *   - An integer menu status constant: to indicate an error condition.
435
 *   - A string of HTML content.
436
 *   - A renderable array of content.
437
 *
438
 * @see drupal_deliver_html_page()
439
 */
440
function ajax_deliver($page_callback_result) {
441
  // Browsers do not allow JavaScript to read the contents of a user's local
442
  // files. To work around that, the jQuery Form plugin submits forms containing
443
  // a file input element to an IFRAME, instead of using XHR. Browsers do not
444
  // normally expect JSON strings as content within an IFRAME, so the response
445
  // must be customized accordingly.
446
  // @see http://malsup.com/jquery/form/#file-upload
447
  // @see Drupal.ajax.prototype.beforeSend()
448
  $iframe_upload = !empty($_POST['ajax_iframe_upload']);
449
450
  // Emit a Content-Type HTTP header if none has been added by the page callback
451
  // or by a wrapping delivery callback.
452
  if (is_null(drupal_get_http_header('Content-Type'))) {
453
    if (!$iframe_upload) {
454
      // Standard JSON can be returned to a browser's XHR object, and to
455
      // non-browser user agents.
456
      // @see http://www.ietf.org/rfc/rfc4627.txt?number=4627
457
      drupal_add_http_header('Content-Type', 'application/json; charset=utf-8');
458
    }
459
    else {
460
      // Browser IFRAMEs expect HTML. With most other content types, Internet
461
      // Explorer presents the user with a download prompt.
462
      drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
463
    }
464
  }
465
466
  // Print the response.
467
  $commands = ajax_prepare_response($page_callback_result);
468
  $json = ajax_render($commands);
469
  if (!$iframe_upload) {
470
    // Standard JSON can be returned to a browser's XHR object, and to
471
    // non-browser user agents.
472
    print $json;
473
  }
474
  else {
475
    // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification
476
    // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into
477
    // links. This corrupts the JSON response. Protect the integrity of the
478
    // JSON data by making it the value of a textarea.
479
    // @see http://malsup.com/jquery/form/#file-upload
480
    // @see http://drupal.org/node/1009382
481
    print '<textarea>' . $json . '</textarea>';
482
  }
483
484
  // Perform end-of-request tasks.
485
  ajax_footer();
486
}
487
488
/**
489
 * Converts the return value of a page callback into an Ajax commands array.
490
 *
491
 * @param $page_callback_result
492
 *   The result of a page callback. Can be one of:
493
 *   - NULL: to indicate no content.
494
 *   - An integer menu status constant: to indicate an error condition.
495
 *   - A string of HTML content.
496
 *   - A renderable array of content.
497
 *
498
 * @return
499
 *   An Ajax commands array that can be passed to ajax_render().
500
 */
501
function ajax_prepare_response($page_callback_result) {
502
  $commands = array();
503
  if (!isset($page_callback_result)) {
504
    // Simply delivering an empty commands array is sufficient. This results
505
    // in the Ajax request being completed, but nothing being done to the page.
506
  }
507
  elseif (is_int($page_callback_result)) {
508
    switch ($page_callback_result) {
509
      case MENU_NOT_FOUND:
510
        $commands[] = ajax_command_alert(t('The requested page could not be found.'));
511
        break;
512
513
      case MENU_ACCESS_DENIED:
514
        $commands[] = ajax_command_alert(t('You are not authorized to access this page.'));
515
        break;
516
517
      case MENU_SITE_OFFLINE:
518
        $commands[] = ajax_command_alert(filter_xss_admin(variable_get('maintenance_mode_message',
519
          t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
520
        break;
521
    }
522
  }
523
  elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax')) {
524
    // Complex Ajax callbacks can return a result that contains an error message
525
    // or a specific set of commands to send to the browser.
526
    $page_callback_result += element_info('ajax');
527
    $error = $page_callback_result['#error'];
528
    if (isset($error) && $error !== FALSE) {
529
      if ((empty($error) || $error === TRUE)) {
530
        $error = t('An error occurred while handling the request: The server received invalid input.');
531
      }
532
      $commands[] = ajax_command_alert($error);
533
    }
534
    else {
535
      $commands = $page_callback_result['#commands'];
536
    }
537
  }
538
  else {
539
    // Like normal page callbacks, simple Ajax callbacks can return HTML
540
    // content, as a string or render array. This HTML is inserted in some
541
    // relationship to #ajax['wrapper'], as determined by which jQuery DOM
542
    // manipulation method is used. The method used is specified by
543
    // #ajax['method']. The default method is 'replaceWith', which completely
544
    // replaces the old wrapper element and its content with the new HTML.
545
    $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result);
546
    $commands[] = ajax_command_insert(NULL, $html);
547
    // Add the status messages inside the new content's wrapper element, so that
548
    // on subsequent Ajax requests, it is treated as old content.
549
    $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
550
  }
551
552
  return $commands;
553
}
554
555
/**
556
 * Performs end-of-Ajax-request tasks.
557
 *
558
 * This function is the equivalent of drupal_page_footer(), but for Ajax
559
 * requests.
560
 *
561
 * @see drupal_page_footer()
562
 */
563
function ajax_footer() {
564
  // Even for Ajax requests, invoke hook_exit() implementations. There may be
565
  // modules that need very fast Ajax responses, and therefore, run Ajax
566
  // requests with an early bootstrap.
567
  if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) {
568
    module_invoke_all('exit');
569
  }
570
571
  // Commit the user session. See above comment about the possibility of this
572
  // function running without session.inc loaded.
573
  if (function_exists('drupal_session_commit')) {
574
    drupal_session_commit();
575
  }
576
}
577
578
/**
579
 * Form element processing handler for the #ajax form property.
580
 *
581
 * @param $element
582
 *   An associative array containing the properties of the element.
583
 *
584
 * @return
585
 *   The processed element.
586
 *
587
 * @see ajax_pre_render_element()
588
 */
589
function ajax_process_form($element, &$form_state) {
590
  $element = ajax_pre_render_element($element);
591
  if (!empty($element['#ajax_processed'])) {
592
    $form_state['cache'] = TRUE;
593
  }
594
  return $element;
595
}
596
597
/**
598
 * Adds Ajax information about an element to communicate with JavaScript.
599
 *
600
 * If #ajax['path'] is set on an element, this additional JavaScript is added
601
 * to the page header to attach the Ajax behaviors. See ajax.js for more
602
 * information.
603
 *
604
 * @param $element
605
 *   An associative array containing the properties of the element.
606
 *   Properties used:
607
 *   - #ajax['event']
608
 *   - #ajax['prevent']
609
 *   - #ajax['path']
610
 *   - #ajax['options']
611
 *   - #ajax['wrapper']
612
 *   - #ajax['parameters']
613
 *   - #ajax['effect']
614
 *
615
 * @return
616
 *   The processed element with the necessary JavaScript attached to it.
617
 */
618
function ajax_pre_render_element($element) {
619
  // Skip already processed elements.
620
  if (isset($element['#ajax_processed'])) {
621
    return $element;
622
  }
623
  // Initialize #ajax_processed, so we do not process this element again.
624
  $element['#ajax_processed'] = FALSE;
625
626
  // Nothing to do if there is neither a callback nor a path.
627
  if (!(isset($element['#ajax']['callback']) || isset($element['#ajax']['path']))) {
628
    return $element;
629
  }
630
631
  // Add a reasonable default event handler if none was specified.
632
  if (isset($element['#ajax']) && !isset($element['#ajax']['event'])) {
633
    switch ($element['#type']) {
634
      case 'submit':
635
      case 'button':
636
      case 'image_button':
637
        // Pressing the ENTER key within a textfield triggers the click event of
638
        // the form's first submit button. Triggering Ajax in this situation
639
        // leads to problems, like breaking autocomplete textfields, so we bind
640
        // to mousedown instead of click.
641
        // @see http://drupal.org/node/216059
642
        $element['#ajax']['event'] = 'mousedown';
643
        // Retain keyboard accessibility by setting 'keypress'. This causes
644
        // ajax.js to trigger 'event' when SPACE or ENTER are pressed while the
645
        // button has focus.
646
        $element['#ajax']['keypress'] = TRUE;
647
        // Binding to mousedown rather than click means that it is possible to
648
        // trigger a click by pressing the mouse, holding the mouse button down
649
        // until the Ajax request is complete and the button is re-enabled, and
650
        // then releasing the mouse button. Set 'prevent' so that ajax.js binds
651
        // an additional handler to prevent such a click from triggering a
652
        // non-Ajax form submission. This also prevents a textfield's ENTER
653
        // press triggering this button's non-Ajax form submission behavior.
654
        if (!isset($element['#ajax']['prevent'])) {
655
          $element['#ajax']['prevent'] = 'click';
656
        }
657
        break;
658
659
      case 'password':
660
      case 'textfield':
661
      case 'textarea':
662
        $element['#ajax']['event'] = 'blur';
663
        break;
664
665
      case 'radio':
666
      case 'checkbox':
667
      case 'select':
668
        $element['#ajax']['event'] = 'change';
669
        break;
670
671
      case 'link':
672
        $element['#ajax']['event'] = 'click';
673
        break;
674
675
      default:
676
        return $element;
677
    }
678
  }
679
680
  // Attach JavaScript settings to the element.
681
  if (isset($element['#ajax']['event'])) {
682
    $element['#attached']['library'][] = array('system', 'jquery.form');
683
    $element['#attached']['library'][] = array('system', 'drupal.ajax');
684
685
    $settings = $element['#ajax'];
686
687
    // Assign default settings.
688
    $settings += array(
689
      'path' => 'system/ajax',
690
      'options' => array(),
691
    );
692
693
    // @todo Legacy support. Remove in Drupal 8.
694
    if (isset($settings['method']) && $settings['method'] == 'replace') {
695
      $settings['method'] = 'replaceWith';
696
    }
697
698
    // Change path to URL.
699
    $settings['url'] = url($settings['path'], $settings['options']);
700
    unset($settings['path'], $settings['options']);
701
702
    // Add special data to $settings['submit'] so that when this element
703
    // triggers an Ajax submission, Drupal's form processing can determine which
704
    // element triggered it.
705
    // @see _form_element_triggered_scripted_submission()
706
    if (isset($settings['trigger_as'])) {
707
      // An element can add a 'trigger_as' key within #ajax to make the element
708
      // submit as though another one (for example, a non-button can use this
709
      // to submit the form as though a button were clicked). When using this,
710
      // the 'name' key is always required to identify the element to trigger
711
      // as. The 'value' key is optional, and only needed when multiple elements
712
      // share the same name, which is commonly the case for buttons.
713
      $settings['submit']['_triggering_element_name'] = $settings['trigger_as']['name'];
714
      if (isset($settings['trigger_as']['value'])) {
715
        $settings['submit']['_triggering_element_value'] = $settings['trigger_as']['value'];
716
      }
717
      unset($settings['trigger_as']);
718
    }
719
    elseif (isset($element['#name'])) {
720
      // Most of the time, elements can submit as themselves, in which case the
721
      // 'trigger_as' key isn't needed, and the element's name is used.
722
      $settings['submit']['_triggering_element_name'] = $element['#name'];
723
      // If the element is a (non-image) button, its name may not identify it
724
      // uniquely, in which case a match on value is also needed.
725
      // @see _form_button_was_clicked()
726
      if (isset($element['#button_type']) && empty($element['#has_garbage_value'])) {
727
        $settings['submit']['_triggering_element_value'] = $element['#value'];
728
      }
729
    }
730
731
    // Convert a simple #ajax['progress'] string into an array.
732
    if (isset($settings['progress']) && is_string($settings['progress'])) {
733
      $settings['progress'] = array('type' => $settings['progress']);
734
    }
735
    // Change progress path to a full URL.
736
    if (isset($settings['progress']['path'])) {
737
      $settings['progress']['url'] = url($settings['progress']['path']);
738
      unset($settings['progress']['path']);
739
    }
740
741
    $element['#attached']['js'][] = array(
742
      'type' => 'setting',
743
      'data' => array('ajax' => array($element['#id'] => $settings)),
744
    );
745
746
    // Indicate that Ajax processing was successful.
747
    $element['#ajax_processed'] = TRUE;
748
  }
749
  return $element;
750
}
751
752
/**
753
 * @} End of "defgroup ajax".
754
 */
755
756
/**
757
 * @defgroup ajax_commands Ajax framework commands
758
 * @{
759
 * Functions to create various Ajax commands.
760
 *
761
 * These functions can be used to create arrays for use with the
762
 * ajax_render() function.
763
 */
764
765
/**
766
 * Creates a Drupal Ajax 'alert' command.
767
 *
768
 * The 'alert' command instructs the client to display a JavaScript alert
769
 * dialog box.
770
 *
771
 * This command is implemented by Drupal.ajax.prototype.commands.alert()
772
 * defined in misc/ajax.js.
773
 *
774
 * @param $text
775
 *   The message string to display to the user.
776
 *
777
 * @return
778
 *   An array suitable for use with the ajax_render() function.
779
 */
780
function ajax_command_alert($text) {
781
  return array(
782
    'command' => 'alert',
783
    'text' => $text,
784
  );
785
}
786
787
/**
788
 * Creates a Drupal Ajax 'insert' command using the method in #ajax['method'].
789
 *
790
 * This command instructs the client to insert the given HTML using whichever
791
 * jQuery DOM manipulation method has been specified in the #ajax['method']
792
 * variable of the element that triggered the request.
793
 *
794
 * This command is implemented by Drupal.ajax.prototype.commands.insert()
795
 * defined in misc/ajax.js.
796
 *
797
 * @param $selector
798
 *   A jQuery selector string. If the command is a response to a request from
799
 *   an #ajax form element then this value can be NULL.
800
 * @param $html
801
 *   The data to use with the jQuery method.
802
 * @param $settings
803
 *   An optional array of settings that will be used for this command only.
804
 *
805
 * @return
806
 *   An array suitable for use with the ajax_render() function.
807
 */
808
function ajax_command_insert($selector, $html, $settings = NULL) {
809
  return array(
810
    'command' => 'insert',
811
    'method' => NULL,
812
    'selector' => $selector,
813
    'data' => $html,
814
    'settings' => $settings,
815
  );
816
}
817
818
/**
819
 * Creates a Drupal Ajax 'insert/replaceWith' command.
820
 *
821
 * The 'insert/replaceWith' command instructs the client to use jQuery's
822
 * replaceWith() method to replace each element matched matched by the given
823
 * selector with the given HTML.
824
 *
825
 * This command is implemented by Drupal.ajax.prototype.commands.insert()
826
 * defined in misc/ajax.js.
827
 *
828
 * @param $selector
829
 *   A jQuery selector string. If the command is a response to a request from
830
 *   an #ajax form element then this value can be NULL.
831
 * @param $html
832
 *   The data to use with the jQuery replaceWith() method.
833
 * @param $settings
834
 *   An optional array of settings that will be used for this command only.
835
 *
836
 * @return
837
 *   An array suitable for use with the ajax_render() function.
838
 *
839
 * See
840
 * @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
841
 */
842
function ajax_command_replace($selector, $html, $settings = NULL) {
843
  return array(
844
    'command' => 'insert',
845
    'method' => 'replaceWith',
846
    'selector' => $selector,
847
    'data' => $html,
848
    'settings' => $settings,
849
  );
850
}
851
852
/**
853
 * Creates a Drupal Ajax 'insert/html' command.
854
 *
855
 * The 'insert/html' command instructs the client to use jQuery's html()
856
 * method to set the HTML content of each element matched by the given
857
 * selector while leaving the outer tags intact.
858
 *
859
 * This command is implemented by Drupal.ajax.prototype.commands.insert()
860
 * defined in misc/ajax.js.
861
 *
862
 * @param $selector
863
 *   A jQuery selector string. If the command is a response to a request from
864
 *   an #ajax form element then this value can be NULL.
865
 * @param $html
866
 *   The data to use with the jQuery html() method.
867
 * @param $settings
868
 *   An optional array of settings that will be used for this command only.
869
 *
870
 * @return
871
 *   An array suitable for use with the ajax_render() function.
872
 *
873
 * @see http://docs.jquery.com/Attributes/html#val
874
 */
875
function ajax_command_html($selector, $html, $settings = NULL) {
876
  return array(
877
    'command' => 'insert',
878
    'method' => 'html',
879
    'selector' => $selector,
880
    'data' => $html,
881
    'settings' => $settings,
882
  );
883
}
884
885
/**
886
 * Creates a Drupal Ajax 'insert/prepend' command.
887
 *
888
 * The 'insert/prepend' command instructs the client to use jQuery's prepend()
889
 * method to prepend the given HTML content to the inside each element matched
890
 * by the given selector.
891
 *
892
 * This command is implemented by Drupal.ajax.prototype.commands.insert()
893
 * defined in misc/ajax.js.
894
 *
895
 * @param $selector
896
 *   A jQuery selector string. If the command is a response to a request from
897
 *   an #ajax form element then this value can be NULL.
898
 * @param $html
899
 *   The data to use with the jQuery prepend() method.
900
 * @param $settings
901
 *   An optional array of settings that will be used for this command only.
902
 *
903
 * @return
904
 *   An array suitable for use with the ajax_render() function.
905
 *
906
 * @see http://docs.jquery.com/Manipulation/prepend#content
907
 */
908
function ajax_command_prepend($selector, $html, $settings = NULL) {
909
  return array(
910
    'command' => 'insert',
911
    'method' => 'prepend',
912
    'selector' => $selector,
913
    'data' => $html,
914
    'settings' => $settings,
915
  );
916
}
917
918
/**
919
 * Creates a Drupal Ajax 'insert/append' command.
920
 *
921
 * The 'insert/append' command instructs the client to use jQuery's append()
922
 * method to append the given HTML content to the inside of each element matched
923
 * by the given selector.
924
 *
925
 * This command is implemented by Drupal.ajax.prototype.commands.insert()
926
 * defined in misc/ajax.js.
927
 *
928
 * @param $selector
929
 *   A jQuery selector string. If the command is a response to a request from
930
 *   an #ajax form element then this value can be NULL.
931
 * @param $html
932
 *   The data to use with the jQuery append() method.
933
 * @param $settings
934
 *   An optional array of settings that will be used for this command only.
935
 *
936
 * @return
937
 *   An array suitable for use with the ajax_render() function.
938
 *
939
 * @see http://docs.jquery.com/Manipulation/append#content
940
 */
941
function ajax_command_append($selector, $html, $settings = NULL) {
942
  return array(
943
    'command' => 'insert',
944
    'method' => 'append',
945
    'selector' => $selector,
946
    'data' => $html,
947
    'settings' => $settings,
948
  );
949
}
950
951
/**
952
 * Creates a Drupal Ajax 'insert/after' command.
953
 *
954
 * The 'insert/after' command instructs the client to use jQuery's after()
955
 * method to insert the given HTML content after each element matched by
956
 * the given selector.
957
 *
958
 * This command is implemented by Drupal.ajax.prototype.commands.insert()
959
 * defined in misc/ajax.js.
960
 *
961
 * @param $selector
962
 *   A jQuery selector string. If the command is a response to a request from
963
 *   an #ajax form element then this value can be NULL.
964
 * @param $html
965
 *   The data to use with the jQuery after() method.
966
 * @param $settings
967
 *   An optional array of settings that will be used for this command only.
968
 *
969
 * @return
970
 *   An array suitable for use with the ajax_render() function.
971
 *
972
 * @see http://docs.jquery.com/Manipulation/after#content
973
 */
974
function ajax_command_after($selector, $html, $settings = NULL) {
975
  return array(
976
    'command' => 'insert',
977
    'method' => 'after',
978
    'selector' => $selector,
979
    'data' => $html,
980
    'settings' => $settings,
981
  );
982
}
983
984
/**
985
 * Creates a Drupal Ajax 'insert/before' command.
986
 *
987
 * The 'insert/before' command instructs the client to use jQuery's before()
988
 * method to insert the given HTML content before each of elements matched by
989
 * the given selector.
990
 *
991
 * This command is implemented by Drupal.ajax.prototype.commands.insert()
992
 * defined in misc/ajax.js.
993
 *
994
 * @param $selector
995
 *   A jQuery selector string. If the command is a response to a request from
996
 *   an #ajax form element then this value can be NULL.
997
 * @param $html
998
 *   The data to use with the jQuery before() method.
999
 * @param $settings
1000
 *   An optional array of settings that will be used for this command only.
1001
 *
1002
 * @return
1003
 *   An array suitable for use with the ajax_render() function.
1004
 *
1005
 * @see http://docs.jquery.com/Manipulation/before#content
1006
 */
1007
function ajax_command_before($selector, $html, $settings = NULL) {
1008
  return array(
1009
    'command' => 'insert',
1010
    'method' => 'before',
1011
    'selector' => $selector,
1012
    'data' => $html,
1013
    'settings' => $settings,
1014
  );
1015
}
1016
1017
/**
1018
 * Creates a Drupal Ajax 'remove' command.
1019
 *
1020
 * The 'remove' command instructs the client to use jQuery's remove() method
1021
 * to remove each of elements matched by the given selector, and everything
1022
 * within them.
1023
 *
1024
 * This command is implemented by Drupal.ajax.prototype.commands.remove()
1025
 * defined in misc/ajax.js.
1026
 *
1027
 * @param $selector
1028
 *   A jQuery selector string. If the command is a response to a request from
1029
 *   an #ajax form element then this value can be NULL.
1030
 *
1031
 * @return
1032
 *   An array suitable for use with the ajax_render() function.
1033
 *
1034
 * @see http://docs.jquery.com/Manipulation/remove#expr
1035
 */
1036
function ajax_command_remove($selector) {
1037
  return array(
1038
    'command' => 'remove',
1039
    'selector' => $selector,
1040
  );
1041
}
1042
1043
/**
1044
 * Creates a Drupal Ajax 'changed' command.
1045
 *
1046
 * This command instructs the client to mark each of the elements matched by the
1047
 * given selector as 'ajax-changed'.
1048
 *
1049
 * This command is implemented by Drupal.ajax.prototype.commands.changed()
1050
 * defined in misc/ajax.js.
1051
 *
1052
 * @param $selector
1053
 *   A jQuery selector string. If the command is a response to a request from
1054
 *   an #ajax form element then this value can be NULL.
1055
 * @param $asterisk
1056
 *   An optional CSS selector which must be inside $selector. If specified,
1057
 *   an asterisk will be appended to the HTML inside the $asterisk selector.
1058
 *
1059
 * @return
1060
 *   An array suitable for use with the ajax_render() function.
1061
 */
1062
function ajax_command_changed($selector, $asterisk = '') {
1063
  return array(
1064
    'command' => 'changed',
1065
    'selector' => $selector,
1066
    'asterisk' => $asterisk,
1067
  );
1068
}
1069
1070
/**
1071
 * Creates a Drupal Ajax 'css' command.
1072
 *
1073
 * The 'css' command will instruct the client to use the jQuery css() method
1074
 * to apply the CSS arguments to elements matched by the given selector.
1075
 *
1076
 * This command is implemented by Drupal.ajax.prototype.commands.css()
1077
 * defined in misc/ajax.js.
1078
 *
1079
 * @param $selector
1080
 *   A jQuery selector string. If the command is a response to a request from
1081
 *   an #ajax form element then this value can be NULL.
1082
 * @param $argument
1083
 *   An array of key/value pairs to set in the CSS for the selector.
1084
 *
1085
 * @return
1086
 *   An array suitable for use with the ajax_render() function.
1087
 *
1088
 * @see http://docs.jquery.com/CSS/css#properties
1089
 */
1090
function ajax_command_css($selector, $argument) {
1091
  return array(
1092
    'command' => 'css',
1093
    'selector' => $selector,
1094
    'argument' => $argument,
1095
  );
1096
}
1097
1098
/**
1099
 * Creates a Drupal Ajax 'settings' command.
1100
 *
1101
 * The 'settings' command instructs the client either to use the given array as
1102
 * the settings for ajax-loaded content or to extend Drupal.settings with the
1103
 * given array, depending on the value of the $merge parameter.
1104
 *
1105
 * This command is implemented by Drupal.ajax.prototype.commands.settings()
1106
 * defined in misc/ajax.js.
1107
 *
1108
 * @param $argument
1109
 *   An array of key/value pairs to add to the settings. This will be utilized
1110
 *   for all commands after this if they do not include their own settings
1111
 *   array.
1112
 * @param $merge
1113
 *   Whether or not the passed settings in $argument should be merged into the
1114
 *   global Drupal.settings on the page. By default (FALSE), the settings that
1115
 *   are passed to Drupal.attachBehaviors will not include the global
1116
 *   Drupal.settings.
1117
 *
1118
 * @return
1119
 *   An array suitable for use with the ajax_render() function.
1120
 */
1121
function ajax_command_settings($argument, $merge = FALSE) {
1122
  return array(
1123
    'command' => 'settings',
1124
    'settings' => $argument,
1125
    'merge' => $merge,
1126
  );
1127
}
1128
1129
/**
1130
 * Creates a Drupal Ajax 'data' command.
1131
 *
1132
 * The 'data' command instructs the client to attach the name=value pair of
1133
 * data to the selector via jQuery's data cache.
1134
 *
1135
 * This command is implemented by Drupal.ajax.prototype.commands.data()
1136
 * defined in misc/ajax.js.
1137
 *
1138
 * @param $selector
1139
 *   A jQuery selector string. If the command is a response to a request from
1140
 *   an #ajax form element then this value can be NULL.
1141
 * @param $name
1142
 *   The name or key (in the key value pair) of the data attached to this
1143
 *   selector.
1144
 * @param $value
1145
 *   The value of the data. Not just limited to strings can be any format.
1146
 *
1147
 * @return
1148
 *   An array suitable for use with the ajax_render() function.
1149
 *
1150
 * @see http://docs.jquery.com/Core/data#namevalue
1151
 */
1152
function ajax_command_data($selector, $name, $value) {
1153
  return array(
1154
    'command' => 'data',
1155
    'selector' => $selector,
1156
    'name' => $name,
1157
    'value' => $value,
1158
  );
1159
}
1160
1161
/**
1162
 * Creates a Drupal Ajax 'invoke' command.
1163
 *
1164
 * The 'invoke' command will instruct the client to invoke the given jQuery
1165
 * method with the supplied arguments on the elements matched by the given
1166
 * selector. Intended for simple jQuery commands, such as attr(), addClass(),
1167
 * removeClass(), toggleClass(), etc.
1168
 *
1169
 * This command is implemented by Drupal.ajax.prototype.commands.invoke()
1170
 * defined in misc/ajax.js.
1171
 *
1172
 * @param $selector
1173
 *   A jQuery selector string. If the command is a response to a request from
1174
 *   an #ajax form element then this value can be NULL.
1175
 * @param $method
1176
 *   The jQuery method to invoke.
1177
 * @param $arguments
1178
 *   (optional) A list of arguments to the jQuery $method, if any.
1179
 *
1180
 * @return
1181
 *   An array suitable for use with the ajax_render() function.
1182
 */
1183
function ajax_command_invoke($selector, $method, array $arguments = array()) {
1184
  return array(
1185
    'command' => 'invoke',
1186
    'selector' => $selector,
1187
    'method' => $method,
1188
    'arguments' => $arguments,
1189
  );
1190
}
1191
1192
/**
1193
 * Creates a Drupal Ajax 'restripe' command.
1194
 *
1195
 * The 'restripe' command instructs the client to restripe a table. This is
1196
 * usually used after a table has been modified by a replace or append command.
1197
 *
1198
 * This command is implemented by Drupal.ajax.prototype.commands.restripe()
1199
 * defined in misc/ajax.js.
1200
 *
1201
 * @param $selector
1202
 *   A jQuery selector string.
1203
 *
1204
 * @return
1205
 *   An array suitable for use with the ajax_render() function.
1206
 */
1207
function ajax_command_restripe($selector) {
1208
  return array(
1209
    'command' => 'restripe',
1210
    'selector' => $selector,
1211
  );
1212
}