diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index 42843fa..e7db9b5 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -21,9 +21,9 @@
* forms, it can be used with the #ajax property.
* The #ajax property can be used to bind events to the Ajax framework. By
* default, #ajax uses 'system/ajax' as its path for submission and thus calls
- * ajax_form_callback() and a defined #ajax['callback'] function.
- * However, you may optionally specify a different path to request or a
- * different callback function to invoke, which can return updated HTML or can
+ * Drupal\system\FormAjaxController::content() and a defined #ajax['callback']
+ * function. However, you may optionally specify a different path to request or
+ * a different callback function to invoke, which can return updated HTML or can
* also return a richer set of
* @link ajax_commands Ajax framework commands @endlink.
*
@@ -36,16 +36,16 @@
* that element.
* - The browser submits an HTTP POST request to the 'system/ajax' Drupal
* path.
- * - The menu page callback for 'system/ajax', ajax_form_callback(), calls
- * drupal_process_form() to process the form submission and rebuild the
- * form if necessary. The form is processed in much the same way as if it
- * were submitted without Ajax, with the same #process functions and
+ * - Controller 'system/ajax', Drupal\system\FormAjaxController::content(),
+ * calls drupal_process_form() to process the form submission and rebuild
+ * the form if necessary. The form is processed in much the same way as if
+ * it were submitted without Ajax, with the same #process functions and
* validation and submission handlers called in either case, making it easy
* to create Ajax-enabled forms that degrade gracefully when JavaScript is
* disabled.
- * - After form processing is complete, ajax_form_callback() calls the
- * function named by #ajax['callback'], which returns the form element that
- * has been updated and needs to be returned to the browser, or
+ * - After form processing is complete, Drupal\system\FormAjaxController::content()
+ * calls the function named by #ajax['callback'], which returns the form
+ * element that has been updated and needs to be returned to the browser, or
* alternatively, an array of custom Ajax commands.
* - The array is serialized using ajax_render() and sent to the browser.
* - The browser unserializes the returned JSON string into an array of
@@ -123,10 +123,10 @@
* - #ajax['path']: The menu path to use for the request. This is often omitted
* and the default is used. This path should map
* to a menu page callback that returns data using ajax_render(). Defaults to
- * 'system/ajax', which invokes ajax_form_callback(), eventually calling
- * the function named in #ajax['callback']. If you use a custom
- * path, you must set up the menu entry and handle the entire callback in your
- * own code.
+ * 'system/ajax', which invokes Drupal\system\FormAjaxController::content(),
+ * eventually calling the function named in #ajax['callback']. If you use a
+ * custom path, you must set up the menu entry and handle the entire callback
+ * in your own code.
* - #ajax['wrapper']: The CSS ID of the area to be replaced by the content
* returned by the #ajax['callback'] function. The content returned from
* the callback will replace the entire element named by #ajax['wrapper'].
@@ -300,88 +300,6 @@ function ajax_render($commands = array()) {
}
/**
- * Gets a form submitted via #ajax during an Ajax callback.
- *
- * This will load a form from the form cache used during Ajax operations. It
- * pulls the form info from $_POST.
- *
- * @return
- * An array containing the $form and $form_state. Use the list() function
- * to break these apart:
- * @code
- * list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
- * @endcode
- */
-function ajax_get_form() {
- $form_state = form_state_defaults();
-
- $form_build_id = $_POST['form_build_id'];
-
- // Get the form from the cache.
- $form = form_get_cache($form_build_id, $form_state);
- if (!$form) {
- // If $form cannot be loaded from the cache, the form_build_id in $_POST
- // must be invalid, which means that someone performed a POST request onto
- // system/ajax without actually viewing the concerned form in the browser.
- // This is likely a hacking attempt as it never happens under normal
- // circumstances, so we just do nothing.
- watchdog('ajax', 'Invalid form POST data.', array(), WATCHDOG_WARNING);
- drupal_exit();
- }
-
- // Since some of the submit handlers are run, redirects need to be disabled.
- $form_state['no_redirect'] = TRUE;
-
- // When a form is rebuilt after Ajax processing, its #build_id and #action
- // should not change.
- // @see drupal_rebuild_form()
- $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
- $form_state['rebuild_info']['copy']['#action'] = TRUE;
-
- // The form needs to be processed; prepare for that by setting a few internal
- // variables.
- $form_state['input'] = $_POST;
- $form_id = $form['#form_id'];
-
- return array($form, $form_state, $form_id, $form_build_id);
-}
-
-/**
- * Page callback: Handles Ajax requests for the #ajax Form API property.
- *
- * This rebuilds the form from cache and invokes the defined #ajax['callback']
- * to return an Ajax command structure for JavaScript. In case no 'callback' has
- * been defined, nothing will happen.
- *
- * The Form API #ajax property can be set both for buttons and other input
- * elements.
- *
- * This function is also the canonical example of how to implement
- * #ajax['path']. If processing is required that cannot be accomplished with
- * a callback, re-implement this function and set #ajax['path'] to the
- * enhanced function.
- *
- * @see system_menu()
- */
-function ajax_form_callback() {
- list($form, $form_state) = ajax_get_form();
- drupal_process_form($form['#form_id'], $form, $form_state);
-
- // We need to return the part of the form (or some other content) that needs
- // to be re-rendered so the browser can update the page with changed content.
- // Since this is the generic menu callback used by many Ajax elements, it is
- // up to the #ajax['callback'] function of the element (may or may not be a
- // button) that triggered the Ajax request to determine what needs to be
- // rendered.
- if (!empty($form_state['triggering_element'])) {
- $callback = $form_state['triggering_element']['#ajax']['callback'];
- }
- if (!empty($callback) && is_callable($callback)) {
- return call_user_func_array($callback, array(&$form, &$form_state));
- }
-}
-
-/**
* Theme callback: Returns the correct theme for an Ajax request.
*
* Many different pages can invoke an Ajax request to system/ajax or another
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 23f66c2..d5cc0ec 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -463,8 +463,9 @@ function form_state_defaults() {
* workflow, to be returned for rendering.
*
* Ajax form submissions are almost always multi-step workflows, so that is one
- * common use-case during which form rebuilding occurs. See ajax_form_callback()
- * for more information about creating Ajax-enabled forms.
+ * common use-case during which form rebuilding occurs. See
+ * Drupal\system\FormAjaxController::content() for more information about
+ * creating Ajax-enabled forms.
*
* @param $form_id
* The unique string identifying the desired form. If a function
@@ -488,7 +489,7 @@ function form_state_defaults() {
* The newly built form.
*
* @see drupal_process_form()
- * @see ajax_form_callback()
+ * @see Drupal\system\FormAjaxController::content()
*/
function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
$form = drupal_retrieve_form($form_id, $form_state);
@@ -1274,9 +1275,10 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
* - If $form_state['rebuild'] is TRUE, the form is being rebuilt, and no
* redirection is done.
* - If $form_state['no_redirect'] is TRUE, redirection is disabled. This is
- * set, for instance, by ajax_get_form() to prevent redirection in Ajax
- * callbacks. $form_state['no_redirect'] should never be set or altered by
- * form builder functions or form validation/submit handlers.
+ * set, for instance, by Drupal\system\FormAjaxController::getForm() prevent
+ * redirection in Ajax callbacks. $form_state['no_redirect'] should never be
+ * set or altered by form builder functions or form validation/submit
+ * handlers.
* - If $form_state['redirect'] is set to FALSE, redirection is disabled.
* - If none of the above conditions has prevented redirection, then the
* redirect is accomplished by calling drupal_goto(), passing in the value of
diff --git a/core/modules/field/field.form.inc b/core/modules/field/field.form.inc
index 203a112..b39bdc1 100644
--- a/core/modules/field/field.form.inc
+++ b/core/modules/field/field.form.inc
@@ -119,8 +119,8 @@ function field_form_element_after_build($element, &$form_state) {
* This handler is run regardless of whether JS is enabled or not. It makes
* changes to the form state. If the button was clicked with JS disabled, then
* the page is reloaded with the complete rebuilt form. If the button was
- * clicked with JS enabled, then ajax_form_callback() calls field_add_more_js()
- * to return just the changed part of the form.
+ * clicked with JS enabled, then Drupal\system\FormAjaxController::content()
+ * calls field_add_more_js() to return just the changed part of the form.
*/
function field_add_more_submit($form, &$form_state) {
$button = $form_state['triggering_element'];
diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc
index 59d068b..6e386d9 100644
--- a/core/modules/file/file.field.inc
+++ b/core/modules/file/file.field.inc
@@ -486,12 +486,19 @@ function file_field_widget_process($element, &$form_state, $form) {
// file, the entire group of file fields is updated together.
if ($field['cardinality'] != 1) {
$parents = array_slice($element['#array_parents'], 0, -1);
- $new_path = 'file/ajax/' . implode('/', $parents) . '/' . $form['form_build_id']['#value'];
+ $new_path = 'file/ajax';
+ $new_options = array(
+ 'query' => array(
+ 'element_parents' => implode('/', $parents),
+ 'form_build_id' => $form['form_build_id']['#value'],
+ ),
+ );
$field_element = NestedArray::getValue($form, $parents);
$new_wrapper = $field_element['#id'] . '-ajax-wrapper';
foreach (element_children($element) as $key) {
if (isset($element[$key]['#ajax'])) {
$element[$key]['#ajax']['path'] = $new_path;
+ $element[$key]['#ajax']['options'] = $new_options;
$element[$key]['#ajax']['wrapper'] = $new_wrapper;
}
}
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 18dae26..000f26d 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -46,14 +46,12 @@ function file_menu() {
$items = array();
$items['file/ajax'] = array(
- 'page callback' => 'file_ajax_upload',
- 'access arguments' => array('access content'),
+ 'route_name' => 'file_ajax_upload',
'theme callback' => 'ajax_base_page_theme',
'type' => MENU_CALLBACK,
);
$items['file/progress'] = array(
- 'page callback' => 'file_ajax_progress',
- 'access arguments' => array('access content'),
+ 'route name' => 'file_ajax_progress',
'theme callback' => 'ajax_base_page_theme',
'type' => MENU_CALLBACK,
);
@@ -726,99 +724,6 @@ function file_cron() {
}
/**
- * Ajax callback: Processes file uploads and deletions.
- *
- * This rebuilds the form element for a particular field item. As long as the
- * form processing is properly encapsulated in the widget element the form
- * should rebuild correctly using FAPI without the need for additional callbacks
- * or processing.
- *
- * @see file_menu()
- */
-function file_ajax_upload() {
- $form_parents = func_get_args();
- $form_build_id = (string) array_pop($form_parents);
-
- if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
- // Invalid request.
- drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
- $response = new AjaxResponse();
- return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages')));
- }
-
- list($form, $form_state) = ajax_get_form();
-
- if (!$form) {
- // Invalid form_build_id.
- drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
- $response = new AjaxResponse();
- return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages')));
- }
-
- // Get the current element and count the number of files.
- $current_element = $form;
- foreach ($form_parents as $parent) {
- $current_element = $current_element[$parent];
- }
- $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0;
-
- // Process user input. $form and $form_state are modified in the process.
- drupal_process_form($form['#form_id'], $form, $form_state);
-
- // Retrieve the element to be rendered.
- foreach ($form_parents as $parent) {
- $form = $form[$parent];
- }
-
- // Add the special Ajax class if a new file was added.
- if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
- $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
- }
- // Otherwise just add the new content class on a placeholder.
- else {
- $form['#suffix'] .= '';
- }
-
- $output = theme('status_messages') . drupal_render($form);
- $js = drupal_add_js();
- $settings = drupal_merge_js_settings($js['settings']['data']);
-
- $response = new AjaxResponse();
- return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
-}
-
-/**
- * Ajax callback: Retrieves upload progress.
- *
- * @param $key
- * The unique key for this upload process.
- */
-function file_ajax_progress($key) {
- $progress = array(
- 'message' => t('Starting upload...'),
- 'percentage' => -1,
- );
-
- $implementation = file_progress_implementation();
- if ($implementation == 'uploadprogress') {
- $status = uploadprogress_get_info($key);
- if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
- $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['bytes_uploaded']), '@total' => format_size($status['bytes_total'])));
- $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']);
- }
- }
- elseif ($implementation == 'apc') {
- $status = apc_fetch('upload_' . $key);
- if (isset($status['current']) && !empty($status['total'])) {
- $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['current']), '@total' => format_size($status['total'])));
- $progress['percentage'] = round(100 * $status['current'] / $status['total']);
- }
- }
-
- return new JsonResponse($progress);
-}
-
-/**
* Determines the preferred upload progress implementation.
*
* @return
@@ -873,7 +778,13 @@ function file_managed_file_process($element, &$form_state, $form) {
$element['#tree'] = TRUE;
$ajax_settings = array(
- 'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
+ 'path' => 'file/ajax',
+ 'options' => array(
+ 'query' => array(
+ 'element_parents' => implode('/', $element['#array_parents']),
+ 'form_build_id' => $form['form_build_id']['#value'],
+ ),
+ ),
'wrapper' => $element['#id'] . '-ajax-wrapper',
'effect' => 'fade',
'progress' => array(
diff --git a/core/modules/file/file.routing.yml b/core/modules/file/file.routing.yml
new file mode 100644
index 0000000..0d80e55
--- /dev/null
+++ b/core/modules/file/file.routing.yml
@@ -0,0 +1,12 @@
+file_ajax_upload:
+ pattern: 'file/ajax'
+ defaults:
+ _controller: '\Drupal\file\Controller\FileWidgetAjaxController::upload'
+ requirements:
+ _permission: 'access content'
+file_ajax_progress:
+ pattern: 'file/progress'
+ defaults:
+ _controller: '\Drupal\file\Controller\FileWidgetAjaxController::progress'
+ requirements:
+ _permission: 'access content'
diff --git a/core/modules/file/lib/Drupal/file/Controller/FileWidgetAjaxController.php b/core/modules/file/lib/Drupal/file/Controller/FileWidgetAjaxController.php
new file mode 100644
index 0000000..2da8b1d
--- /dev/null
+++ b/core/modules/file/lib/Drupal/file/Controller/FileWidgetAjaxController.php
@@ -0,0 +1,114 @@
+query->get('element_parents'));
+ $form_build_id = $request->query->get('form_build_id');
+
+ if (empty($form_build_id)) {
+ // Invalid request.
+ drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
+ $response = new AjaxResponse();
+ return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages')));
+ }
+
+ try {
+ list($form, $form_state) = $this->getForm($request);
+ }
+ catch (HttpExceptionInterface $e) {
+ // Invalid form_build_id.
+ drupal_set_message(t('An unrecoverable error occurred. Use of this form has expired. Try reloading the page and submitting again.'), 'error');
+ $response = new AjaxResponse();
+ return $response->addCommand(new ReplaceCommand(NULL, theme('status_messages')));
+ }
+
+ // Get the current element and count the number of files.
+ $current_element = $form;
+ foreach ($form_parents as $parent) {
+ $current_element = $current_element[$parent];
+ }
+ $current_file_count = isset($current_element['#file_upload_delta']) ? $current_element['#file_upload_delta'] : 0;
+
+ // Process user input. $form and $form_state are modified in the process.
+ drupal_process_form($form['#form_id'], $form, $form_state);
+
+ // Retrieve the element to be rendered.
+ foreach ($form_parents as $parent) {
+ $form = $form[$parent];
+ }
+
+ // Add the special Ajax class if a new file was added.
+ if (isset($form['#file_upload_delta']) && $current_file_count < $form['#file_upload_delta']) {
+ $form[$current_file_count]['#attributes']['class'][] = 'ajax-new-content';
+ }
+ // Otherwise just add the new content class on a placeholder.
+ else {
+ $form['#suffix'] .= '';
+ }
+
+ $output = theme('status_messages') . drupal_render($form);
+ $js = drupal_add_js();
+ $settings = drupal_merge_js_settings($js['settings']['data']);
+
+ $response = new AjaxResponse();
+ return $response->addCommand(new ReplaceCommand(NULL, $output, $settings));
+ }
+
+ /**
+ * Ajax callback: Retrieves upload progress.
+ *
+ * @param $key
+ * The unique key for this upload process.
+ */
+ public function progress($key) {
+ $progress = array(
+ 'message' => t('Starting upload...'),
+ 'percentage' => -1,
+ );
+
+ $implementation = file_progress_implementation();
+ if ($implementation == 'uploadprogress') {
+ $status = uploadprogress_get_info($key);
+ if (isset($status['bytes_uploaded']) && !empty($status['bytes_total'])) {
+ $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['bytes_uploaded']), '@total' => format_size($status['bytes_total'])));
+ $progress['percentage'] = round(100 * $status['bytes_uploaded'] / $status['bytes_total']);
+ }
+ }
+ elseif ($implementation == 'apc') {
+ $status = apc_fetch('upload_' . $key);
+ if (isset($status['current']) && !empty($status['total'])) {
+ $progress['message'] = t('Uploading... (@current of @total)', array('@current' => format_size($status['current']), '@total' => format_size($status['total'])));
+ $progress['percentage'] = round(100 * $status['current'] / $status['total']);
+ }
+ }
+
+ return new JsonResponse($progress);
+ }
+}
diff --git a/core/modules/system/lib/Drupal/system/Controller/FormAjaxController.php b/core/modules/system/lib/Drupal/system/Controller/FormAjaxController.php
new file mode 100644
index 0000000..7863f38
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Controller/FormAjaxController.php
@@ -0,0 +1,98 @@
+getForm($request);
+ drupal_process_form($form['#form_id'], $form, $form_state);
+
+ // We need to return the part of the form (or some other content) that needs
+ // to be re-rendered so the browser can update the page with changed content.
+ // Since this is the generic menu callback used by many Ajax elements, it is
+ // up to the #ajax['callback'] function of the element (may or may not be a
+ // button) that triggered the Ajax request to determine what needs to be
+ // rendered.
+ if (!empty($form_state['triggering_element'])) {
+ $callback = $form_state['triggering_element']['#ajax']['callback'];
+ }
+ if (empty($callback) || !is_callable($callback)) {
+ throw new HttpException(500, t('Internal Server Error'));
+ }
+ return call_user_func_array($callback, array(&$form, &$form_state));
+ }
+
+ /**
+ * Gets a form submitted via #ajax during an Ajax callback.
+ *
+ * This will load a form from the form cache used during Ajax operations. It
+ * pulls the form info from the request body.
+ *
+ * @param Symfony\Component\HttpFoundation\Request $request
+ * The current request object.
+ *
+ * @return
+ * An array containing the $form and $form_state. Use the list() function
+ * to break these apart:
+ * @code
+ * list($form, $form_state, $form_id, $form_build_id) = $this->getForm();
+ * @endcode
+ *
+ * @throws Symfony\Component\HttpKernel\Exception\HttpExceptionInterface
+ */
+ public function getForm(Request $request) {
+ $form_state = form_state_defaults();
+ $form_build_id = $request->request->get('form_build_id');
+
+ // Get the form from the cache.
+ $form = form_get_cache($form_build_id, $form_state);
+ if (!$form) {
+ // If $form cannot be loaded from the cache, the form_build_id must be
+ // invalid, which means that someone performed a POST request onto
+ // system/ajax without actually viewing the concerned form in the browser.
+ // This is likely a hacking attempt as it never happens under normal
+ // circumstances.
+ watchdog('ajax', 'Invalid form POST data.', array(), WATCHDOG_WARNING);
+ throw new HttpException(500, t('Internal Server Error'));
+ }
+
+ // Since some of the submit handlers are run, redirects need to be disabled.
+ $form_state['no_redirect'] = TRUE;
+
+ // When a form is rebuilt after Ajax processing, its #build_id and #action
+ // should not change.
+ // @see drupal_rebuild_form()
+ $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
+ $form_state['rebuild_info']['copy']['#action'] = TRUE;
+
+ // The form needs to be processed; prepare for that by setting a few internal
+ // variables.
+ $form_state['input'] = $request->request->all();
+ $form_id = $form['#form_id'];
+
+ return array($form, $form_state, $form_id, $form_build_id);
+ }
+}
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 0d472e5..945a7ae 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -633,8 +633,7 @@ function system_menu() {
);
$items['system/ajax'] = array(
'title' => 'AHAH callback',
- 'page callback' => 'ajax_form_callback',
- 'access callback' => TRUE,
+ 'route_name' => 'system.ajax',
'theme callback' => 'ajax_base_page_theme',
'type' => MENU_CALLBACK,
'file path' => 'core/includes',
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 9859d6a..eaff17f 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -1,3 +1,9 @@
+system.ajax:
+ pattern: 'system/ajax'
+ defaults:
+ _controller: '\Drupal\system\Controller\FormAjaxController::content'
+ requirements:
+ _access: 'TRUE'
system.cron:
pattern: '/cron/{key}'
defaults: